]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/configparser.cpp
ede5281eef008a77576d80c0ae5c876565b622a2
[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-2019 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 == '#'))
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
237                                 {
238                                         insp::flat_map<std::string, std::string>::iterator var = stack.vars.find(varname);
239                                         if (var == stack.vars.end())
240                                                 throw CoreException("Undefined XML entity reference '&" + varname + ";'");
241                                         value.append(var->second);
242                                 }
243                         }
244                         else if (ch == '\\' && (flags & FLAG_USE_COMPAT))
245                         {
246                                 int esc = next();
247                                 if (esc == 'n')
248                                         value.push_back('\n');
249                                 else if (isalpha(esc))
250                                         throw CoreException("Unknown escape character \\" + std::string(1, esc));
251                                 else
252                                         value.push_back(esc);
253                         }
254                         else if (ch == '"')
255                                 break;
256                         else if (ch != '\r')
257                                 value.push_back(ch);
258                 }
259
260                 if (items->find(key) != items->end())
261                         throw CoreException("Duplicate key '" + key + "' found");
262
263                 (*items)[key] = value;
264                 return true;
265         }
266
267         void dotag()
268         {
269                 last_tag = current;
270                 std::string name;
271                 nextword(name);
272
273                 int spc = next();
274                 if (spc == '>')
275                         unget(spc);
276                 else if (!isspace(spc))
277                         throw CoreException("Invalid character in tag name");
278
279                 if (name.empty())
280                         throw CoreException("Empty tag name");
281
282                 ConfigItems* items;
283                 tag = ConfigTag::create(name, current.name, current.line, items);
284
285                 while (kv(items))
286                 {
287                         // Do nothing here (silences a GCC warning).
288                 }
289
290                 if (name == mandatory_tag)
291                 {
292                         // Found the mandatory tag
293                         mandatory_tag.clear();
294                 }
295
296                 if (stdalgo::string::equalsci(name, "include"))
297                 {
298                         stack.DoInclude(tag, flags);
299                 }
300                 else if (stdalgo::string::equalsci(name, "files"))
301                 {
302                         for(ConfigItems::iterator i = items->begin(); i != items->end(); i++)
303                         {
304                                 stack.DoReadFile(i->first, i->second, flags, false);
305                         }
306                 }
307                 else if (stdalgo::string::equalsci(name, "execfiles"))
308                 {
309                         for(ConfigItems::iterator i = items->begin(); i != items->end(); i++)
310                         {
311                                 stack.DoReadFile(i->first, i->second, flags, true);
312                         }
313                 }
314                 else if (stdalgo::string::equalsci(name, "define"))
315                 {
316                         if (flags & FLAG_USE_COMPAT)
317                                 throw CoreException("<define> tags may only be used in XML-style config (add <config format=\"xml\">)");
318                         std::string varname = tag->getString("name");
319                         std::string value = tag->getString("value");
320                         if (varname.empty())
321                                 throw CoreException("Variable definition must include variable name");
322                         stack.vars[varname] = value;
323                 }
324                 else if (stdalgo::string::equalsci(name, "config"))
325                 {
326                         std::string format = tag->getString("format");
327                         if (stdalgo::string::equalsci(format, "xml"))
328                                 flags &= ~FLAG_USE_COMPAT;
329                         else if (stdalgo::string::equalsci(format, "compat"))
330                                 flags |= FLAG_USE_COMPAT;
331                         else if (!format.empty())
332                                 throw CoreException("Unknown configuration format " + format);
333                 }
334                 else
335                 {
336                         stack.output.insert(std::make_pair(name, tag));
337                 }
338                 // this is not a leak; reference<> takes care of the delete
339                 tag = NULL;
340         }
341
342         bool outer_parse()
343         {
344                 try
345                 {
346                         while (1)
347                         {
348                                 int ch = next(true);
349                                 switch (ch)
350                                 {
351                                         case EOF:
352                                                 // this is the one place where an EOF is not an error
353                                                 if (!mandatory_tag.empty())
354                                                         throw CoreException("Mandatory tag \"" + mandatory_tag + "\" not found");
355                                                 return true;
356                                         case '#':
357                                                 comment();
358                                                 break;
359                                         case '<':
360                                                 dotag();
361                                                 break;
362                                         case ' ':
363                                         case '\r':
364                                         case '\t':
365                                         case '\n':
366                                                 break;
367                                         case 0xFE:
368                                         case 0xFF:
369                                                 stack.errstr << "Do not save your files as UTF-16 or UTF-32, use UTF-8!\n";
370                                                 /*@fallthrough@*/
371                                         default:
372                                                 throw CoreException("Syntax error - start of tag expected");
373                                 }
374                         }
375                 }
376                 catch (CoreException& err)
377                 {
378                         stack.errstr << err.GetReason() << " at " << current.str();
379                         if (tag)
380                                 stack.errstr << " (inside tag " << tag->tag << " at line " << tag->src_line << ")\n";
381                         else
382                                 stack.errstr << " (last tag was on line " << last_tag.line << ")\n";
383                 }
384                 return false;
385         }
386 };
387
388 void ParseStack::DoInclude(ConfigTag* tag, int flags)
389 {
390         if (flags & FLAG_NO_INC)
391                 throw CoreException("Invalid <include> tag in file included with noinclude=\"yes\"");
392
393         std::string mandatorytag;
394         tag->readString("mandatorytag", mandatorytag);
395
396         std::string name;
397         if (tag->readString("file", name))
398         {
399                 if (tag->getBool("noinclude", false))
400                         flags |= FLAG_NO_INC;
401                 if (tag->getBool("noexec", false))
402                         flags |= FLAG_NO_EXEC;
403
404                 if (!ParseFile(ServerInstance->Config->Paths.PrependConfig(name), flags, mandatorytag))
405                         throw CoreException("Included");
406         }
407         else if (tag->readString("directory", name))
408         {
409                 if (tag->getBool("noinclude", false))
410                         flags |= FLAG_NO_INC;
411                 if (tag->getBool("noexec", false))
412                         flags |= FLAG_NO_EXEC;
413
414                 const std::string includedir = ServerInstance->Config->Paths.PrependConfig(name);
415                 std::vector<std::string> files;
416                 if (!FileSystem::GetFileList(includedir, files, "*.conf"))
417                         throw CoreException("Unable to read directory for include: " + includedir);
418
419                 std::sort(files.begin(), files.end()); 
420                 for (std::vector<std::string>::const_iterator iter = files.begin(); iter != files.end(); ++iter)
421                 {
422                         const std::string path = includedir + '/' + *iter;
423                         if (!ParseFile(path, flags, mandatorytag))
424                                 throw CoreException("Included");
425                 }
426         }
427         else if (tag->readString("executable", name))
428         {
429                 if (flags & FLAG_NO_EXEC)
430                         throw CoreException("Invalid <include:executable> tag in file included with noexec=\"yes\"");
431                 if (tag->getBool("noinclude", false))
432                         flags |= FLAG_NO_INC;
433                 if (tag->getBool("noexec", true))
434                         flags |= FLAG_NO_EXEC;
435
436                 if (!ParseFile(name, flags, mandatorytag, true))
437                         throw CoreException("Included");
438         }
439 }
440
441 void ParseStack::DoReadFile(const std::string& key, const std::string& name, int flags, bool exec)
442 {
443         if (flags & FLAG_NO_INC)
444                 throw CoreException("Invalid <files> tag in file included with noinclude=\"yes\"");
445         if (exec && (flags & FLAG_NO_EXEC))
446                 throw CoreException("Invalid <execfiles> tag in file included with noexec=\"yes\"");
447
448         std::string path = ServerInstance->Config->Paths.PrependConfig(name);
449         FileWrapper file(exec ? popen(name.c_str(), "r") : fopen(path.c_str(), "r"), exec);
450         if (!file)
451                 throw CoreException("Could not read \"" + path + "\" for \"" + key + "\" file");
452
453         file_cache& cache = FilesOutput[key];
454         cache.clear();
455
456         char linebuf[5120];
457         while (fgets(linebuf, sizeof(linebuf), file))
458         {
459                 size_t len = strlen(linebuf);
460                 if (len)
461                 {
462                         if (linebuf[len-1] == '\n')
463                                 len--;
464                         cache.push_back(std::string(linebuf, len));
465                 }
466         }
467 }
468
469 bool ParseStack::ParseFile(const std::string& path, int flags, const std::string& mandatory_tag, bool isexec)
470 {
471         ServerInstance->Logs->Log("CONFIG", LOG_DEBUG, "Reading (isexec=%d) %s", isexec, path.c_str());
472         if (stdalgo::isin(reading, path))
473                 throw CoreException((isexec ? "Executable " : "File ") + path + " is included recursively (looped inclusion)");
474
475         /* It's not already included, add it to the list of files we've loaded */
476
477         FileWrapper file((isexec ? popen(path.c_str(), "r") : fopen(path.c_str(), "r")), isexec);
478         if (!file)
479                 throw CoreException("Could not read \"" + path + "\" for include");
480
481         reading.push_back(path);
482         Parser p(*this, flags, file, path, mandatory_tag);
483         bool ok = p.outer_parse();
484         reading.pop_back();
485         return ok;
486 }
487
488 bool ConfigTag::readString(const std::string& key, std::string& value, bool allow_lf)
489 {
490         for(ConfigItems::iterator j = items.begin(); j != items.end(); ++j)
491         {
492                 if(j->first != key)
493                         continue;
494                 value = j->second;
495                 if (!allow_lf && (value.find('\n') != std::string::npos))
496                 {
497                         ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
498                                 " contains a linefeed, and linefeeds in this value are not permitted -- stripped to spaces.");
499                         for (std::string::iterator n = value.begin(); n != value.end(); n++)
500                                 if (*n == '\n')
501                                         *n = ' ';
502                 }
503                 return true;
504         }
505         return false;
506 }
507
508 std::string ConfigTag::getString(const std::string& key, const std::string& def, const TR1NS::function<bool(const std::string&)>& validator)
509 {
510         std::string res;
511         if (!readString(key, res))
512                 return def;
513
514         if (!validator(res))
515         {
516                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The value of <%s:%s> is not valid; value set to %s.",
517                         tag.c_str(), key.c_str(), def.c_str());
518                 return def;
519         }
520         return res;
521 }
522
523 std::string ConfigTag::getString(const std::string& key, const std::string& def, size_t minlen, size_t maxlen)
524 {
525         std::string res;
526         if (!readString(key, res))
527                 return def;
528
529         if (res.length() < minlen || res.length() > maxlen)
530         {
531                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "WARNING: The length of <%s:%s> is not between %ld and %ld; value set to %s.",
532                         tag.c_str(), key.c_str(), minlen, maxlen, def.c_str());
533                 return def;
534         }
535         return res;
536 }
537
538 namespace
539 {
540         /** Check for an invalid magnitude specifier. If one is found a warning is logged and the
541          * value is corrected (set to \p def).
542          * @param tag The tag name; used in the warning message.
543          * @param key The key name; used in the warning message.
544          * @param val The full value set in the config as a string.
545          * @param num The value to verify and modify if needed.
546          * @param def The default value, \p res will be set to this if \p tail does not contain a.
547          *            valid magnitude specifier.
548          * @param tail The location in the config value at which the magnifier is located.
549          */
550         template <typename Numeric>
551         void CheckMagnitude(const std::string& tag, const std::string& key, const std::string& val, Numeric& num, Numeric def, const char* tail)
552         {
553                 // If this is NULL then no magnitude specifier was given.
554                 if (!*tail)
555                         return;
556
557                 switch (toupper(*tail))
558                 {
559                         case 'K':
560                                 num *= 1024;
561                                 return;
562
563                         case 'M':
564                                 num *= 1024 * 1024;
565                                 return;
566
567                         case 'G':
568                                 num *= 1024 * 1024 * 1024;
569                                 return;
570                 }
571
572                 const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + val + " contains an invalid magnitude specifier '"
573                         + tail + "'; value set to " + ConvToStr(def) + ".";
574                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message);
575                 num = def;
576         }
577
578         /** Check for an out of range value. If the value falls outside the boundaries a warning is
579          * logged and the value is corrected (set to \p def).
580          * @param tag The tag name; used in the warning message.
581          * @param key The key name; used in the warning message.
582          * @param num The value to verify and modify if needed.
583          * @param def The default value, \p res will be set to this if (min <= res <= max) doesn't hold true.
584          * @param min Minimum accepted value for \p res.
585          * @param max Maximum accepted value for \p res.
586          */
587         template <typename Numeric>
588         void CheckRange(const std::string& tag, const std::string& key, Numeric& num, Numeric def, Numeric min, Numeric max)
589         {
590                 if (num >= min && num <= max)
591                         return;
592
593                 const std::string message = "WARNING: <" + tag + ":" + key + "> value of " + ConvToStr(num) + " is not between "
594                         + ConvToStr(min) + " and " + ConvToStr(max) + "; value set to " + ConvToStr(def) + ".";
595                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, message);
596                 num = def;
597         }
598 }
599
600 long ConfigTag::getInt(const std::string &key, long def, long min, long max)
601 {
602         std::string result;
603         if(!readString(key, result))
604                 return def;
605
606         const char* res_cstr = result.c_str();
607         char* res_tail = NULL;
608         long res = strtol(res_cstr, &res_tail, 0);
609         if (res_tail == res_cstr)
610                 return def;
611
612         CheckMagnitude(tag, key, result, res, def, res_tail);
613         CheckRange(tag, key, res, def, min, max);
614         return res;
615 }
616
617 unsigned long ConfigTag::getUInt(const std::string& key, unsigned long def, unsigned long min, unsigned long max)
618 {
619         std::string result;
620         if (!readString(key, result))
621                 return def;
622
623         const char* res_cstr = result.c_str();
624         char* res_tail = NULL;
625         unsigned long res = strtoul(res_cstr, &res_tail, 0);
626         if (res_tail == res_cstr)
627                 return def;
628
629         CheckMagnitude(tag, key, result, res, def, res_tail);
630         CheckRange(tag, key, res, def, min, max);
631         return res;
632 }
633
634 unsigned long ConfigTag::getDuration(const std::string& key, unsigned long def, unsigned long min, unsigned long max)
635 {
636         std::string duration;
637         if (!readString(key, duration))
638                 return def;
639
640         unsigned long ret;
641         if (!InspIRCd::Duration(duration, ret))
642         {
643                 ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
644                         " is not a duration; value set to " + ConvToStr(def) + ".");
645                 return def;
646         }
647
648         CheckRange(tag, key, ret, def, min, max);
649         return ret;
650 }
651
652 double ConfigTag::getFloat(const std::string& key, double def, double min, double max)
653 {
654         std::string result;
655         if (!readString(key, result))
656                 return def;
657
658         double res = strtod(result.c_str(), NULL);
659         CheckRange(tag, key, res, def, min, max);
660         return res;
661 }
662
663 bool ConfigTag::getBool(const std::string &key, bool def)
664 {
665         std::string result;
666         if(!readString(key, result))
667                 return def;
668
669         if (result == "yes" || result == "true" || result == "1" || result == "on")
670                 return true;
671         if (result == "no" || result == "false" || result == "0" || result == "off")
672                 return false;
673
674         ServerInstance->Logs->Log("CONFIG", LOG_DEFAULT, "Value of <" + tag + ":" + key + "> at " + getTagLocation() +
675                 " is not valid, ignoring");
676         return def;
677 }
678
679 std::string ConfigTag::getTagLocation()
680 {
681         return src_name + ":" + ConvToStr(src_line);
682 }
683
684 ConfigTag* ConfigTag::create(const std::string& Tag, const std::string& file, int line, ConfigItems*& Items)
685 {
686         ConfigTag* rv = new ConfigTag(Tag, file, line);
687         Items = &rv->items;
688         return rv;
689 }
690
691 ConfigTag::ConfigTag(const std::string& Tag, const std::string& file, int line)
692         : tag(Tag), src_name(file), src_line(line)
693 {
694 }
695
696 OperInfo::OperInfo(const std::string& Name)
697         : name(Name)
698 {
699 }
700
701 std::string OperInfo::getConfig(const std::string& key)
702 {
703         std::string rv;
704         if (type_block)
705                 type_block->readString(key, rv);
706         if (oper_block)
707                 oper_block->readString(key, rv);
708         return rv;
709 }