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