]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/configparser.cpp
9f5a49f1daffc65d401304f36803fb982d268a3d
[user/henk/code/inspircd.git] / src / configparser.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2013-2014, 2016-2020 Sadie Powell <sadie@witchery.services>
6  *   Copyright (C) 2013 ChrisTX <xpipe@hotmail.de>
7  *   Copyright (C) 2012-2014 Attila Molnar <attilamolnar@hush.com>
8  *   Copyright (C) 2012 Robby <robby@chatbelgie.be>
9  *   Copyright (C) 2010 Craig Edwards <brain@inspircd.org>
10  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
11  *
12  * This file is part of InspIRCd.  InspIRCd is free software: you can
13  * redistribute it and/or modify it under the terms of the GNU General Public
14  * License as published by the Free Software Foundation, version 2.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24
25
26 #include "inspircd.h"
27 #include <fstream>
28 #include "configparser.h"
29
30 enum ParseFlags
31 {
32         // Legacy config parsing should be used.
33         FLAG_USE_COMPAT = 1,
34
35         // Executable includes are disabled.
36         FLAG_NO_EXEC = 2,
37
38         // All includes are disabled.
39         FLAG_NO_INC = 4,
40
41         // &env.FOO; is disabled.
42         FLAG_NO_ENV = 8,
43
44         // It's okay if an include doesn't exist.
45         FLAG_MISSING_OKAY = 16
46 };
47
48 // Represents the position within a config file.
49 struct FilePosition
50 {
51         // The name of the file which is being read.
52         std::string name;
53
54         // The line of the file that this position points to.
55         unsigned int line;
56
57         // The column of the file that this position points to.
58         unsigned int column;
59
60         FilePosition(const std::string& Name)
61                 : name(Name)
62                 , line(1)
63                 , column(1)
64         {
65         }
66
67         /** Returns a string that represents this file position. */
68         std::string str()
69         {
70                 return name + ":" + ConvToStr(line) + ":" + ConvToStr(column);
71         }
72 };
73
74 // RAII wrapper for FILE* which closes the file when it goes out of scope.
75 class FileWrapper
76 {
77  private:
78         // Whether this file handle should be closed with pclose.
79         bool close_with_pclose;
80
81         // The file handle which is being wrapped.
82         FILE* const file;
83
84  public:
85         FileWrapper(FILE* File, bool CloseWithPClose = false)
86                 : close_with_pclose(CloseWithPClose)
87                 , file(File)
88         {
89         }
90
91         // Operator which determines whether the file is open.
92         operator bool() { return (file != NULL); }
93
94         // Operator which retrieves the underlying FILE pointer.
95         operator FILE*() { return file; }
96
97         ~FileWrapper()
98         {
99                 if (!file)
100                         return;
101
102                 if (close_with_pclose)
103                         pclose(file);
104                 else
105                         fclose(file);
106         }
107 };
108
109
110 struct Parser
111 {
112         ParseStack& stack;
113         int flags;
114         FILE* const file;
115         FilePosition current;
116         FilePosition last_tag;
117         reference<ConfigTag> tag;
118         int ungot;
119         std::string mandatory_tag;
120
121         Parser(ParseStack& me, int myflags, FILE* conf, const std::string& name, const std::string& mandatorytag)
122                 : stack(me), flags(myflags), file(conf), current(name), last_tag(name), ungot(-1), mandatory_tag(mandatorytag)
123         { }
124
125         int next(bool eof_ok = false)
126         {
127                 if (ungot != -1)
128                 {
129                         int ch = ungot;
130                         ungot = -1;
131                         return ch;
132                 }
133                 int ch = fgetc(file);
134                 if (ch == EOF && !eof_ok)
135                 {
136                         throw CoreException("Unexpected end-of-file");
137                 }
138                 else if (ch == '\n')
139                 {
140                         current.line++;
141                         current.column = 0;
142                 }
143                 else
144                 {
145                         current.column++;
146                 }
147                 return ch;
148         }
149
150         void unget(int ch)
151         {
152                 if (ungot != -1)
153                         throw CoreException("INTERNAL ERROR: cannot unget twice");
154                 ungot = ch;
155         }
156
157         void comment()
158         {
159                 while (1)
160                 {
161                         int ch = next();
162                         if (ch == '\n')
163                                 return;
164                 }
165         }
166
167         bool wordchar(char ch)
168         {
169                 return isalnum(ch)
170                         || ch == '-'
171                         || ch == '.'
172                         || ch == '_';
173         }
174
175         void nextword(std::string& rv)
176         {
177                 int ch = next();
178                 while (isspace(ch))
179                         ch = next();
180                 while (wordchar(ch))
181                 {
182                         rv.push_back(ch);
183                         ch = next();
184                 }
185                 unget(ch);
186         }
187
188         bool kv(ConfigItems* items)
189         {
190                 std::string key;
191                 nextword(key);
192                 int ch = next();
193                 if (ch == '>' && key.empty())
194                 {
195                         return false;
196                 }
197                 else if (ch == '#' && key.empty())
198                 {
199                         comment();
200                         return true;
201                 }
202                 else if (ch != '=')
203                 {
204                         throw CoreException("Invalid character " + std::string(1, ch) + " in key (" + key + ")");
205                 }
206
207                 std::string value;
208                 ch = next();
209                 if (ch != '"')
210                 {
211                         throw CoreException("Invalid character in value of <" + tag->tag + ":" + key + ">");
212                 }
213                 while (1)
214                 {
215                         ch = next();
216                         if (ch == '&' && !(flags & FLAG_USE_COMPAT))
217                         {
218                                 std::string varname;
219                                 while (1)
220                                 {
221                                         ch = next();
222                                         if (wordchar(ch) || (varname.empty() && ch == '#'))
223                                                 varname.push_back(ch);
224                                         else if (ch == ';')
225                                                 break;
226                                         else
227                                         {
228                                                 stack.errstr << "Invalid XML entity name in value of <" + tag->tag + ":" + key + ">\n"
229                                                         << "To include an ampersand or quote, use &amp; or &quot;\n";
230                                                 throw CoreException("Parse error");
231                                         }
232                                 }
233                                 if (varname.empty())
234                                         throw CoreException("Empty XML entity reference");
235                                 else if (varname[0] == '#' && (varname.size() == 1 || (varname.size() == 2 && varname[1] == 'x')))
236                                         throw CoreException("Empty numeric character reference");
237                                 else if (varname[0] == '#')
238                                 {
239                                         const char* cvarname = varname.c_str();
240                                         char* endptr;
241                                         unsigned long lvalue;
242                                         if (cvarname[1] == 'x')
243                                                 lvalue = strtoul(cvarname + 2, &endptr, 16);
244                                         else
245                                                 lvalue = strtoul(cvarname + 1, &endptr, 10);
246                                         if (*endptr != '\0' || lvalue > 255)
247                                                 throw CoreException("Invalid numeric character reference '&" + varname + ";'");
248                                         value.push_back(static_cast<char>(lvalue));
249                                 }
250                                 else if (varname.compare(0, 4, "env.") == 0)
251                                 {
252                                         if (flags & FLAG_NO_ENV)
253                                                 throw CoreException("XML environment entity reference in file included with noenv=\"yes\"");
254
255                                         const char* envstr = getenv(varname.c_str() + 4);
256                                         if (!envstr)
257                                                 throw CoreException("Undefined XML environment entity reference '&" + varname + ";'");
258
259                                         value.append(envstr);
260                                 }
261                                 else
262                                 {
263                                         insp::flat_map<std::string, std::string>::iterator var = stack.vars.find(varname);
264                                         if (var == stack.vars.end())
265                                                 throw CoreException("Undefined XML entity reference '&" + varname + ";'");
266                                         value.append(var->second);
267                                 }
268                         }
269                         else if (ch == '\\' && (flags & FLAG_USE_COMPAT))
270                         {
271                                 int esc = next();
272                                 if (esc == 'n')
273                                         value.push_back('\n');
274                                 else if (isalpha(esc))
275                                         throw CoreException("Unknown escape character \\" + std::string(1, esc));
276                                 else
277                                         value.push_back(esc);
278                         }
279                         else if (ch == '"')
280                                 break;
281                         else if (ch != '\r')
282                                 value.push_back(ch);
283                 }
284
285                 if (items->find(key) != items->end())
286                         throw CoreException("Duplicate key '" + key + "' found");
287
288                 (*items)[key] = value;
289                 return true;
290         }
291
292         void dotag()
293         {
294                 last_tag = current;
295                 std::string name;
296                 nextword(name);
297
298                 int spc = next();
299                 if (spc == '>')
300                         unget(spc);
301                 else if (!isspace(spc))
302                         throw CoreException("Invalid character in tag name");
303
304                 if (name.empty())
305                         throw CoreException("Empty tag name");
306
307                 ConfigItems* items;
308                 tag = ConfigTag::create(name, current.name, current.line, items);
309
310                 while (kv(items))
311                 {
312                         // Do nothing here (silences a GCC warning).
313                 }
314
315                 if (name == mandatory_tag)
316                 {
317                         // Found the mandatory tag
318                         mandatory_tag.clear();
319                 }
320
321                 if (stdalgo::string::equalsci(name, "include"))
322                 {
323                         stack.DoInclude(tag, flags);
324                 }
325                 else if (stdalgo::string::equalsci(name, "files"))
326                 {
327                         for(ConfigItems::iterator i = items->begin(); i != items->end(); i++)
328                         {
329                                 stack.DoReadFile(i->first, i->second, flags, false);
330                         }
331                 }
332                 else if (stdalgo::string::equalsci(name, "execfiles"))
333                 {
334                         for(ConfigItems::iterator i = items->begin(); i != items->end(); i++)
335                         {
336                                 stack.DoReadFile(i->first, i->second, flags, true);
337                         }
338                 }
339                 else if (stdalgo::string::equalsci(name, "define"))
340                 {
341                         if (flags & FLAG_USE_COMPAT)
342                                 throw CoreException("<define> tags may only be used in XML-style config (add <config format=\"xml\">)");
343                         std::string varname = tag->getString("name");
344                         std::string value = tag->getString("value");
345                         if (varname.empty())
346                                 throw CoreException("Variable definition must include variable name");
347                         stack.vars[varname] = value;
348                 }
349                 else if (stdalgo::string::equalsci(name, "config"))
350                 {
351                         std::string format = tag->getString("format");
352                         if (stdalgo::string::equalsci(format, "xml"))
353                                 flags &= ~FLAG_USE_COMPAT;
354                         else if (stdalgo::string::equalsci(format, "compat"))
355                                 flags |= FLAG_USE_COMPAT;
356                         else if (!format.empty())
357                                 throw CoreException("Unknown configuration format " + format);
358                 }
359                 else
360                 {
361                         stack.output.insert(std::make_pair(name, tag));
362                 }
363                 // this is not a leak; reference<> takes care of the delete
364                 tag = NULL;
365         }
366
367         bool outer_parse()
368         {
369                 try
370                 {
371                         while (1)
372                         {
373                                 int ch = next(true);
374                                 switch (ch)
375                                 {
376                                         case EOF:
377                                                 // this is the one place where an EOF is not an error
378                                                 if (!mandatory_tag.empty())
379                                                         throw CoreException("Mandatory tag \"" + mandatory_tag + "\" not found");
380                                                 return true;
381                                         case '#':
382                                                 comment();
383                                                 break;
384                                         case '<':
385                                                 dotag();
386                                                 break;
387                                         case ' ':
388                                         case '\r':
389                                         case '\t':
390                                         case '\n':
391                                                 break;
392                                         case 0xFE:
393                                         case 0xFF:
394                                                 stack.errstr << "Do not save your files as UTF-16 or UTF-32, use UTF-8!\n";
395                                                 /*@fallthrough@*/
396                                         default:
397                                                 throw CoreException("Syntax error - start of tag expected");
398                                 }
399                         }
400                 }
401                 catch (CoreException& err)
402                 {
403                         stack.errstr << err.GetReason() << " at " << current.str();
404                         if (tag)
405                                 stack.errstr << " (inside tag " << tag->tag << " at line " << tag->src_line << ")\n";
406                         else
407                                 stack.errstr << " (last tag was on line " << last_tag.line << ")\n";
408                 }
409                 return false;
410         }
411 };
412
413 void ParseStack::DoInclude(ConfigTag* tag, int flags)
414 {
415         if (flags & FLAG_NO_INC)
416                 throw CoreException("Invalid <include> tag in file included with noinclude=\"yes\"");
417
418         std::string mandatorytag;
419         tag->readString("mandatorytag", mandatorytag);
420
421         std::string name;
422         if (tag->readString("file", name))
423         {
424                 if (tag->getBool("noinclude", false))
425                         flags |= FLAG_NO_INC;
426
427                 if (tag->getBool("noexec", false))
428                         flags |= FLAG_NO_EXEC;
429
430                 if (tag->getBool("noenv", false))
431                         flags |= FLAG_NO_ENV;
432
433                 if (tag->getBool("missingokay", false))
434                         flags |= FLAG_MISSING_OKAY;
435                 else
436                         flags &= ~FLAG_MISSING_OKAY;
437
438                 if (!ParseFile(ServerInstance->Config->Paths.PrependConfig(name), flags, mandatorytag))
439                         throw CoreException("Included");
440         }
441         else if (tag->readString("directory", name))
442         {
443                 if (tag->getBool("noinclude", false))
444                         flags |= FLAG_NO_INC;
445                 if (tag->getBool("noexec", false))
446                         flags |= FLAG_NO_EXEC;
447                 if (tag->getBool("noenv", false))
448                         flags |= FLAG_NO_ENV;
449
450                 const std::string includedir = ServerInstance->Config->Paths.PrependConfig(name);
451                 std::vector<std::string> files;
452                 if (!FileSystem::GetFileList(includedir, files, "*.conf"))
453                         throw CoreException("Unable to read directory for include: " + includedir);
454
455                 std::sort(files.begin(), files.end());
456                 for (std::vector<std::string>::const_iterator iter = files.begin(); iter != files.end(); ++iter)
457                 {
458                         const std::string path = includedir + '/' + *iter;
459                         if (!ParseFile(path, flags, mandatorytag))
460                                 throw CoreException("Included");
461                 }
462         }
463         else if (tag->readString("executable", name))
464         {
465                 if (flags & FLAG_NO_EXEC)
466                         throw CoreException("Invalid <include:executable> tag in file included with noexec=\"yes\"");
467                 if (tag->getBool("noinclude", false))
468                         flags |= FLAG_NO_INC;
469                 if (tag->getBool("noexec", true))
470                         flags |= FLAG_NO_EXEC;
471                 if (tag->getBool("noenv", true))
472                         flags |= FLAG_NO_ENV;
473
474                 if (!ParseFile(name, flags, mandatorytag, true))
475                         throw CoreException("Included");
476         }
477 }
478
479 void ParseStack::DoReadFile(const std::string& key, const std::string& name, int flags, bool exec)
480 {
481         if (flags & FLAG_NO_INC)
482                 throw CoreException("Invalid <files> tag in file included with noinclude=\"yes\"");
483         if (exec && (flags & FLAG_NO_EXEC))
484                 throw CoreException("Invalid <execfiles> tag in file included with noexec=\"yes\"");
485
486         std::string path = ServerInstance->Config->Paths.PrependConfig(name);
487         FileWrapper file(exec ? popen(name.c_str(), "r") : fopen(path.c_str(), "r"), exec);
488         if (!file)
489                 throw CoreException("Could not read \"" + path + "\" for \"" + key + "\" file");
490
491         file_cache& cache = FilesOutput[key];
492         cache.clear();
493
494         char linebuf[5120];
495         while (fgets(linebuf, sizeof(linebuf), file))
496         {
497                 size_t len = strlen(linebuf);
498                 if (len)
499                 {
500                         if (linebuf[len-1] == '\n')
501                                 len--;
502                         cache.push_back(std::string(linebuf, len));
503                 }
504         }
505 }
506
507 bool ParseStack::ParseFile(const std::string& path, int flags, const std::string& mandatory_tag, bool isexec)
508 {
509         ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Reading (isexec=%d) %s", isexec, path.c_str());
510         if (stdalgo::isin(reading, path))
511                 throw CoreException((isexec ? "Executable " : "File ") + path + " is included recursively (looped inclusion)");
512
513         /* It's not already included, add it to the list of files we've loaded */
514
515         FileWrapper file((isexec ? popen(path.c_str(), "r") : fopen(path.c_str(), "r")), isexec);
516         if (!file)
517         {
518                 if (flags & FLAG_MISSING_OKAY)
519                         return true;
520
521                 throw CoreException("Could not read \"" + path + "\" for include");
522         }
523
524         reading.push_back(path);
525         Parser p(*this, flags, file, path, mandatory_tag);
526         bool ok = p.outer_parse();
527         reading.pop_back();
528         return ok;
529 }
530
531 bool ConfigTag::readString(const std::string& key, std::string& value, bool allow_lf)
532 {
533         for(ConfigItems::iterator j = items.begin(); j != items.end(); ++j)
534         {
535                 if(j->first != key)
536                         continue;
537                 value = j->second;
538                 if (!allow_lf && (value.find('\n') != std::string::npos))
539                 {
540                         ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
541                                 " contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces.");
542                         for (std::string::iterator n = value.begin(); n != value.end(); n++)
543                                 if (*n == '\n')
544                                         *n = ' ';
545                 }
546                 return true;
547         }
548         return false;
549 }
550
551 std::string ConfigTag::getString(const std::string& key, const std::string& def, const TR1NS::function<bool(const std::string&)>& validator)
552 {
553         std::string res;
554         if (!readString(key, res))
555                 return def;
556
557         if (!validator(res))
558         {
559                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The value of <%s:%s> is not valid; value set to %s.",
560                         tag.c_str(), key.c_str(), def.c_str());
561                 return def;
562         }
563         return res;
564 }
565
566 std::string ConfigTag::getString(const std::string& key, const std::string& def, size_t minlen, size_t maxlen)
567 {
568         std::string res;
569         if (!readString(key, res))
570                 return def;
571
572         if (res.length() < minlen || res.length() > maxlen)
573         {
574                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The length of <%s:%s> is not between %ld and %ld; value set to %s.",
575                         tag.c_str(), key.c_str(), minlen, maxlen, def.c_str());
576                 return def;
577         }
578         return res;
579 }
580
581 namespace
582 {
583         /** Check for an invalid magnitude specifier. If one is found a warning is logged and the
584          * value is corrected (set to \p def).
585          * @param tag The tag name; used in the warning message.
586          * @param key The key name; used in the warning message.
587          * @param val The full value set in the config as a string.
588          * @param num The value to verify and modify if needed.
589          * @param def The default value, \p res will be set to this if \p tail does not contain a.
590          *            valid magnitude specifier.
591          * @param tail The location in the config value at which the magnifier is located.
592          */
593         template <typename Numeric>
594         void CheckMagnitude(const std::string& tag, const std::string& key, const std::string& val, Numeric& num, Numeric def, const char* tail)
595         {
596                 // If this is NULL then no magnitude specifier was given.
597                 if (!*tail)
598                         return;
599
600                 switch (toupper(*tail))
601                 {
602                         case 'K':
603                                 num *= 1024;
604                                 return;
605
606                         case 'M':
607                                 num *= 1024 * 1024;
608                                 return;
609
610                         case 'G':
611                                 num *= 1024 * 1024 * 1024;
612                                 return;
613                 }
614
615                 const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + val + " contains an invalid magnitude specifier '"
616                         + tail + "'; value set to " + ConvToStr(def) + ".";
617                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message);
618                 num = def;
619         }
620
621         /** Check for an out of range value. If the value falls outside the boundaries a warning is
622          * logged and the value is corrected (set to \p def).
623          * @param tag The tag name; used in the warning message.
624          * @param key The key name; used in the warning message.
625          * @param num The value to verify and modify if needed.
626          * @param def The default value, \p res will be set to this if (min <= res <= max) doesn't hold true.
627          * @param min Minimum accepted value for \p res.
628          * @param max Maximum accepted value for \p res.
629          */
630         template <typename Numeric>
631         void CheckRange(const std::string& tag, const std::string& key, Numeric& num, Numeric def, Numeric min, Numeric max)
632         {
633                 if (num >= min && num <= max)
634                         return;
635
636                 const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + ConvToStr(num) + " is not between "
637                         + ConvToStr(min) + " and " + ConvToStr(max) + "; value set to " + ConvToStr(def) + ".";
638                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message);
639                 num = def;
640         }
641 }
642
643 long ConfigTag::getInt(const std::string &key, long def, long min, long max)
644 {
645         std::string result;
646         if(!readString(key, result) || result.empty())
647                 return def;
648
649         const char* res_cstr = result.c_str();
650         char* res_tail = NULL;
651         long res = strtol(res_cstr, &res_tail, 0);
652         if (res_tail == res_cstr)
653                 return def;
654
655         CheckMagnitude(tag, key, result, res, def, res_tail);
656         CheckRange(tag, key, res, def, min, max);
657         return res;
658 }
659
660 unsigned long ConfigTag::getUInt(const std::string& key, unsigned long def, unsigned long min, unsigned long max)
661 {
662         std::string result;
663         if (!readString(key, result) || result.empty())
664                 return def;
665
666         const char* res_cstr = result.c_str();
667         char* res_tail = NULL;
668         unsigned long res = strtoul(res_cstr, &res_tail, 0);
669         if (res_tail == res_cstr)
670                 return def;
671
672         CheckMagnitude(tag, key, result, res, def, res_tail);
673         CheckRange(tag, key, res, def, min, max);
674         return res;
675 }
676
677 unsigned long ConfigTag::getDuration(const std::string& key, unsigned long def, unsigned long min, unsigned long max)
678 {
679         std::string duration;
680         if (!readString(key, duration) || duration.empty())
681                 return def;
682
683         unsigned long ret;
684         if (!InspIRCd::Duration(duration, ret))
685         {
686                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
687                         " is not a duration; value set to " + ConvToStr(def) + ".");
688                 return def;
689         }
690
691         CheckRange(tag, key, ret, def, min, max);
692         return ret;
693 }
694
695 double ConfigTag::getFloat(const std::string& key, double def, double min, double max)
696 {
697         std::string result;
698         if (!readString(key, result))
699                 return def;
700
701         double res = strtod(result.c_str(), NULL);
702         CheckRange(tag, key, res, def, min, max);
703         return res;
704 }
705
706 bool ConfigTag::getBool(const std::string &key, bool def)
707 {
708         std::string result;
709         if(!readString(key, result) || result.empty())
710                 return def;
711
712         if (stdalgo::string::equalsci(result, "yes") || stdalgo::string::equalsci(result, "true") || stdalgo::string::equalsci(result, "on") || result == "1")
713                 return true;
714
715         if (stdalgo::string::equalsci(result, "no") || stdalgo::string::equalsci(result, "false") || stdalgo::string::equalsci(result, "off") || result == "0")
716                 return false;
717
718         ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
719                 " is not valid, ignoring");
720         return def;
721 }
722
723 std::string ConfigTag::getTagLocation()
724 {
725         return src_name + ":" + ConvToStr(src_line);
726 }
727
728 ConfigTag* ConfigTag::create(const std::string& Tag, const std::string& file, int line, ConfigItems*& Items)
729 {
730         ConfigTag* rv = new ConfigTag(Tag, file, line);
731         Items = &rv->items;
732         return rv;
733 }
734
735 ConfigTag::ConfigTag(const std::string& Tag, const std::string& file, int line)
736         : tag(Tag), src_name(file), src_line(line)
737 {
738 }
739
740 OperInfo::OperInfo(const std::string& Name)
741         : name(Name)
742 {
743 }
744
745 std::string OperInfo::getConfig(const std::string& key)
746 {
747         std::string rv;
748         if (type_block)
749                 type_block->readString(key, rv);
750         if (oper_block)
751                 oper_block->readString(key, rv);
752         return rv;
753 }