1 /* $Id: ConfigFile.cpp 1649 2009-10-19 14:35:01Z terpstra $
2 *
3 * ConfigFile.cpp - Knows how to load the config file
4 *
5 * Copyright (C) 2002 - Wesley W. Terpstra
6 *
7 * License: GPL
8 *
9 * Authors: 'Wesley W. Terpstra' <wesley@terpstra.ca>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; version 2.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #define _FILE_OFFSET_BITS 64
26
27 #include "ConfigFile.h"
28 #include "XmlEscape.h"
29 #include "Summary.h"
30
31 #include <fstream>
32 #include <iostream>
33 #include <algorithm>
34 #ifdef HAVE_SYS_PARAM_H
35 #include <sys/param.h>
36 #endif
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <time.h>
41 #include <stdlib.h>
42 #include <dirent.h>
43 #include <cstring>
44
45 using namespace std;
46
47 #define ERROR error << file << ":" << c << ": "
48
49 map<string, string>* lstring::c = 0;
50
lstring(const string & fallback)51 lstring::lstring(const string& fallback)
52 {
53 s[""] = fallback;
54 }
55
simplifyPath(string & path)56 string simplifyPath(string& path)
57 {
58 string error = "";
59
60 #ifdef HAVE_REALPATH
61 char clean[PATH_MAX];
62
63 if (realpath(path.c_str(), clean) != 0)
64 {
65 path = clean;
66 return "";
67 }
68 else
69 {
70 // on linux, realpath files if the last part DNE
71 string dir(path, 0, path.rfind('/'));
72 string tag(path, dir.length(), string::npos);
73
74 if (realpath(dir.c_str(), clean) != 0)
75 {
76 path = clean + tag;
77 return "";
78 }
79 }
80
81 // realpath failed, report the problem path
82 error = clean;
83 // fall through with best attempt to clean it up anyways
84 #endif
85
86 // The least we can do is to trim trailing '/'s and simplify '//'s
87 while (path.length() && path[path.length()-1] == '/')
88 path.resize(path.length()-1);
89
90 string::size_type x = 0;
91 while ((x = path.find("//", x)) != string::npos)
92 path.erase(x, 1);
93
94 return error;
95 }
96
prep_c()97 void lstring::prep_c()
98 {
99 c = new map<string, string>();
100
101 // source: http://www.w3.org/WAI/ER/IG/ert/iso639.htm
102 static const char* code[][2] =
103 { {"abk", "ab"},
104 {"aar", "aa"},
105 {"afr", "af"},
106 {"alb", "sq"},
107 {"amh", "am"},
108 {"ara", "ar"},
109 {"arm", "hy"},
110 {"asm", "as"},
111 {"aym", "ay"},
112 {"aze", "az"},
113 {"bak", "ba"},
114 {"baq", "eu"},
115 {"ben", "bn"},
116 {"bih", "bh"},
117 {"bis", "bi"},
118 {"bre", "be"},
119 {"bul", "bg"},
120 {"bur", "my"},
121 {"bel", "be"},
122 {"cat", "ca"},
123 {"chi", "zh"},
124 {"cos", "co"},
125 {"ces", "cs"},
126 {"dan", "da"},
127 {"dut", "nl"},
128 {"dzo", "dz"},
129 {"eng", "en"},
130 {"epo", "eo"},
131 {"est", "et"},
132 {"fao", "fo"},
133 {"fij", "fj"},
134 {"fin", "fi"},
135 {"fra", "fr"},
136 {"fry", "fy"},
137 {"glg", "gl"},
138 {"geo", "ka"},
139 {"deu", "de"},
140 {"ell", "el"},
141 {"kal", "kl"},
142 {"grn", "gn"},
143 {"guj", "gu"},
144 {"hau", "ha"},
145 {"heb", "he"},
146 {"hin", "hi"},
147 {"hun", "hu"},
148 {"ice", "is"},
149 {"ind", "id"},
150 {"ina", "ia"},
151 {"iku", "iu"},
152 {"ipk", "ik"},
153 {"gai", "ga"},
154 {"ita", "it"},
155 {"jpn", "ja"},
156 {"jav", "jv"},
157 {"kan", "kn"},
158 {"kas", "ks"},
159 {"kaz", "kk"},
160 {"khm", "km"},
161 {"kin", "rw"},
162 {"kir", "ky"},
163 {"kor", "ko"},
164 {"kur", "ku"},
165 {"oci", "oc"},
166 {"lao", "lo"},
167 {"lat", "la"},
168 {"lav", "lv"},
169 {"lin", "ln"},
170 {"lit", "lt"},
171 {"mac", "mk"},
172 {"mlg", "mg"},
173 {"may", "ms"},
174 {"mlt", "ml"},
175 {"mao", "mi"},
176 {"mar", "mr"},
177 {"mol", "mo"},
178 {"mon", "mn"},
179 {"nau", "na"},
180 {"nep", "ne"},
181 {"nor", "no"},
182 {"ori", "or"},
183 {"orm", "om"},
184 {"pan", "pa"},
185 {"fas", "fa"},
186 {"pol", "pl"},
187 {"por", "pt"},
188 {"pus", "ps"},
189 {"que", "qu"},
190 {"roh", "rm"},
191 {"ron", "ro"},
192 {"run", "rn"},
193 {"rus", "ru"},
194 {"smo", "sm"},
195 {"sag", "sg"},
196 {"san", "sa"},
197 {"scr", "sh"},
198 {"sna", "sn"},
199 {"snd", "sd"},
200 {"sin", "si"},
201 {"ssw", "ss"},
202 {"slk", "sk"},
203 {"slv", "sl"},
204 {"som", "so"},
205 {"sot", "st"},
206 {"esl", "es"},
207 {"sun", "su"},
208 {"swa", "sw"},
209 {"sve", "sv"},
210 {"tgl", "tl"},
211 {"tgk", "tg"},
212 {"tam", "ta"},
213 {"tat", "tt"},
214 {"tel", "te"},
215 {"tha", "th"},
216 {"bod", "bo"},
217 {"tir", "ti"},
218 {"tog", "to"},
219 {"tso", "ts"},
220 {"tsn", "tn"},
221 {"tur", "tr"},
222 {"tuk", "tk"},
223 {"twi", "tw"},
224 {"uig", "ug"},
225 {"ukr", "uk"},
226 {"urd", "ur"},
227 {"uzb", "uz"},
228 {"vie", "vi"},
229 {"vol", "vo"},
230 {"cym", "cy"},
231 {"wol", "wo"},
232 {"xho", "xh"},
233 {"yid", "yi"},
234 {"yor", "yo"},
235 {"zha", "za"},
236 {"zul", "zu"},
237 {"sqi", "sq"},
238 {"hye", "hy"},
239 {"eus", "eu"},
240 {"mya", "my"},
241 {"zho", "zh"},
242 {"cze", "cs"},
243 {"nla", "nl"},
244 {"fre", "fr"},
245 {"kat", "ka"},
246 {"ger", "de"},
247 {"gre", "el"},
248 {"isl", "is"},
249 {"iri", "ga"},
250 {"jaw", "jv"},
251 {"mak", "mk"},
252 {"msa", "ms"},
253 {"mri", "mi"},
254 {"per", "fa"},
255 {"rum", "ro"},
256 {"slo", "sk"},
257 {"spa", "es"},
258 {"swe", "sv"},
259 {"tib", "bo"},
260 {"wel", "cy"},
261 // I think these are the same?
262 {"jw", "jv"},
263 // ditto
264 {"hr", "scc"},
265 {"sr", "scc"},
266 {"srp", "scc"},
267 {"", ""}
268 };
269
270 for (unsigned int i = 0; code[i][0][0]; ++i)
271 (*c)[code[i][0]] = code[i][1];
272 }
273
lang_normalize(string & lang)274 bool lstring::lang_normalize(string& lang)
275 {
276 if (!c) prep_c();
277
278 if (lang.length() != 2 && lang.length() != 3)
279 return false;
280
281 // lower-case it:
282 string iso(lang);
283 for (string::size_type i = 0; i < iso.length(); ++i)
284 {
285 if (iso[i] >= 'A' && iso[i] <= 'Z')
286 iso[i] = iso[i] - 'A' + 'a';
287 if (iso[i] < 'a' || iso[i] > 'z')
288 return false; // not an ISO 639 code!
289 }
290
291 // Resolve different language spellings to one spelling
292 if (c->find(iso) != c->end())
293 lang = (*c)[iso];
294 else lang = iso;
295
296 return true;
297 }
298
locale_normalize(string & locale)299 bool lstring::locale_normalize(string& locale)
300 {
301 string::size_type i, e;
302
303 if ((e = locale.find('-')) == string::npos &&
304 (e = locale.find('_')) == string::npos)
305 e = locale.length();
306
307 string lang(locale, 0, e);
308 if (!lang_normalize(lang)) return false;
309
310 string region;
311 if (e != locale.length())
312 {
313 region.assign(locale, e+1, string::npos);
314 if (region.length() != 2) return false; // not an ISO 3166 code
315
316 for (i = 0; i < 2; ++i)
317 {
318 if (region[i] >= 'a' && region[i] <= 'z')
319 region[i] = region[i] - 'a' + 'A';
320 if (region[i] < 'A' || region[i] > 'Z')
321 return false; // not an ISO 3166 code!
322 }
323
324 locale = lang + "-" + region;
325 }
326 else
327 {
328 locale = lang;
329 }
330
331 return true;
332 }
333
translate(const string & language_,const string & translation)334 bool lstring::translate(const string& language_, const string& translation)
335 {
336 string language(language_);
337 if (language != "" && !locale_normalize(language)) return false;
338
339 string::size_type i;
340
341 // this overrides whatever we have got so far
342 s[language] = translation;
343
344 // maybe a localized string for which we lack a language setting?
345 if ((i = language.find('-')) != string::npos)
346 {
347 string iso(language, 0, i);
348 if (s.find(iso) == s.end()) translate(iso, translation);
349 }
350
351 // maybe we lack a fallback language completely
352 if (s.find("") == s.end()) translate("", translation);
353
354 return true;
355 }
356
localize(const string & language_) const357 string lstring::localize(const string& language_) const
358 {
359 string language(language_);
360 if (language != "" && !locale_normalize(language)) return "(bug) bad locale";
361
362 map<string, string>::const_iterator o;
363 string::size_type i;
364
365 // correct locale? use it
366 if ((o = s.find(language)) != s.end()) return o->second;
367
368 // correct language? use it
369 if ((i = language.find('-')) != string::npos)
370 {
371 string iso(language, 0, i);
372 if ((o = s.find(iso)) != s.end()) return o->second;
373 }
374
375 // fallback? use it
376 if ((o = s.find("")) != s.end()) return o->second;
377
378 return ""; // not set!
379 }
380
is_set() const381 bool lstring::is_set() const
382 {
383 return !s.empty();
384 }
385
Config()386 Config::Config()
387 : list(0), frontend(0), group(""), error(), lists(), groups(),
388 file(""),
389 dbdir(""),
390 db_umask(-1),
391 xslt("cat -"),
392 delete_message(""),
393 pgpv_mime("off"),
394 pgpv_inline("off"),
395 admin_address(""),
396
397 archive("Unconfigured Archivew"),
398 admin_name("Unset admin name"),
399 web_cache(true),
400 hide_email(false),
401 raw_email(true),
402 modified(0)
403 {
404 }
405
prune_back(string & line)406 void prune_back(string& line)
407 {
408 // Trim off eol and whitespace
409 string::size_type whitespace = line.length();
410 while (whitespace > 0 &&
411 (line[whitespace-1] == ' ' ||
412 line[whitespace-1] == '\r' ||
413 line[whitespace-1] == '\n' ||
414 line[whitespace-1] == '\t'))
415 whitespace--;
416
417 line.resize(whitespace);
418 }
419
skip_front(const string & line,string::size_type x=0)420 string::size_type skip_front(const string& line, string::size_type x = 0)
421 {
422 // Trim off eol and whitespace
423 for (; x < line.length(); ++x)
424 if (line[x] != ' ' &&
425 line[x] != '\r' &&
426 line[x] != '\n' &&
427 line[x] != '\t')
428 break;
429
430 return x;
431 }
432
load(const string & file,bool toplevel)433 int Config::load(const string& file, bool toplevel)
434 {
435 ifstream f(file.c_str());
436 if (!f.is_open())
437 {
438 error << file << ":open: could not open!" << endl;
439 return -1;
440 }
441
442 struct stat sbuf;
443 if (stat(file.c_str(), &sbuf) < 0)
444 {
445 error << file << ":stat: could not stat!" << endl;
446 return -1;
447 }
448
449 // deal with included file's timestamps
450 if (sbuf.st_mtime > modified)
451 modified = sbuf.st_mtime;
452
453 string dir;
454 string::size_type x = file.rfind('/');
455 if (x != string::npos) dir.assign(file, 0, x+1);
456
457 string line;
458 bool ok = true;
459 int c = 0;
460
461 string val, key;
462
463 while (getline(f, line))
464 {
465 // Increment line number
466 ++c;
467
468 // Trim off the comments
469 string::size_type comment = line.find('#');
470 if (comment != string::npos) line.resize(comment);
471
472 // Clear off trailing whitespace
473 prune_back(line);
474
475 // skip empty lines
476 if (line.length() == 0) continue;
477
478 string::size_type eq = line.find('=');
479 if (eq == string::npos)
480 { // this line continues the previous one.
481 if (key == "")
482 {
483 ERROR << "No key for value '" << line << "'!" << endl;
484 ok = false;
485 }
486 else
487 {
488 string::size_type fe = skip_front(line);
489 val.append(" ");
490 val.append(line, fe, string::npos);
491 }
492 }
493 else
494 {
495 if (key != "" && process_command(file, c, key, val, dir) != 0) ok = false;
496
497 string::size_type leadin = skip_front(line);
498 key.assign(line, leadin, eq-leadin);
499 val.assign(line, skip_front(line, eq+1), string::npos);
500 prune_back(key);
501 }
502 }
503
504 if (toplevel && key == "")
505 {
506 error << file << ":eof: No values set!" << endl;
507 ok = false;
508 }
509
510 if (key != "" && process_command(file, c, key, val, dir) != 0) ok = false;
511
512 if (toplevel)
513 {
514 // do some consistency checks
515
516 // language field is required
517 Lists::const_iterator i, e;
518 for (i = lists.begin(), e = lists.end(); i != e; ++i)
519 {
520 if (i->second.languages.empty())
521 {
522 error << file << ":eof: List '" << i->first << "' has no language!" << endl;
523 return -1;
524 }
525 }
526
527 // cache directories may not be prefixes
528 Frontends::const_iterator fi, fn, fe;
529 fe = frontends.end();
530 fi = frontends.begin();
531
532 fn = fi;
533 if (fi != fe) while (1)
534 {
535 ++fn;
536 if (fn == fe) break;
537
538 if (fi->first + "/" == fn->first.substr(0, fi->first.length()+1))
539 {
540 error << file << ":eof: Frontend '" << fi->first << "' is a prefix of '" << fn->first << "', which is forbidden!" << endl;
541 return -1;
542 }
543
544 fi = fn;
545 }
546
547 this->file = file;
548 }
549
550 if (!ok) return -1;
551 return 0;
552 }
553
isSimple(const string & s)554 bool isSimple(const string& s)
555 {
556 string::size_type x;
557 for (x = 0; x < s.length(); ++x)
558 {
559 char y = s[x];
560 if (y >= 'a' && y <= 'z') continue;
561 if (y >= '0' && y <= '9') continue;
562 if (y == '.' || y == '-' || y == '_') continue;
563 return false;
564 }
565
566 return true;
567 }
568
process_command(const string & file,int c,const string & keys,const string & val,const string & dir)569 int Config::process_command(const string& file, int c, const string& keys, const string& val, const string& dir)
570 {
571 // cout << key << "-" << val << endl;
572
573 string lc; // locale code
574 string key(keys);
575
576 string::size_type o, d;
577 if ((o = key.find('[')) != string::npos &&
578 (d = key.find(']')) != string::npos &&
579 d > o)
580 {
581 // localization option
582 lc.assign(key, o+1, (d-o) - 1);
583 key.erase(o, (d-o) + 1);
584
585 if (!lstring::locale_normalize(lc))
586 {
587 ERROR << "Localization code '" << lc << "' is not valid." << endl;
588 return -1;
589 }
590 }
591
592 string::size_type len = string::npos;
593
594 if (key == "group")
595 {
596 len = 128;
597 if (!isSimple(val) || val.length() == 0)
598 {
599 ERROR << "Group id '" << val << "' is not a simple lowercase string!" << endl;
600 return -1;
601 }
602
603 if (lc != "")
604 {
605 ERROR << "group id cannot be localized" << endl;
606 return -1;
607 }
608
609 if (groups.find(val) != groups.end())
610 {
611 ERROR << "Group id '" << val << "' already exists!" << endl;
612 return -1;
613 }
614
615 group = val;
616 groups[group]; // make sure it exists
617 }
618 else if (key == "heading")
619 {
620 len = 180;
621 groups[group].heading.translate(lc, val);
622 }
623 else if (key == "list")
624 {
625 if (group == "")
626 {
627 ERROR << "List id '" << val << "' is not a member of any group!" << endl;
628 return -1;
629 }
630
631 len = 128;
632 if (!isSimple(val) || val.length() == 0)
633 {
634 ERROR << "List id '" << val << "' is not a simple lowercase string!" << endl;
635 return -1;
636 }
637 if (lc != "")
638 {
639 ERROR << "list id cannot be localized" << endl;
640 return -1;
641 }
642
643 if (lists.find(val) == lists.end())
644 {
645 groups[group].members.insert(val);
646 list = &lists[val];
647 list->mbox = val;
648 list->group = group;
649 list->offline = false;
650 }
651 else
652 {
653 ERROR << "List id '" << val << "' already exists!" << endl;
654 return -1;
655 }
656 }
657 else if (key == "title")
658 {
659 len = 180;
660
661 if (!list)
662 {
663 ERROR << "No list has been defined for title '" << val << "'!" << endl;
664 return -1;
665 }
666
667 list->title.translate(lc, val);
668 }
669 else if (key == "address")
670 {
671 if (!list)
672 {
673 ERROR << "No list has been defined for address '" << val << "'!" << endl;
674 return -1;
675 }
676 if (lc != "")
677 {
678 ERROR << "list address cannot be localized" << endl;
679 return -1;
680 }
681
682 list->address = val;
683 }
684 else if (key == "link")
685 {
686 if (!list)
687 {
688 ERROR << "No list has been defined for address '" << val << "'!" << endl;
689 return -1;
690 }
691
692 list->link.translate(lc, val);
693 }
694 else if (key == "language")
695 {
696 if (!list)
697 {
698 ERROR << "No list has been defined for language '" << val << "'!" << endl;
699 return -1;
700 }
701 if (lc != "")
702 {
703 ERROR << "list language cannot be localized" << endl;
704 return -1;
705 }
706
707 string lval(val);
708 if (!lstring::lang_normalize(lval))
709 {
710 ERROR << "Language '" << val << "' is not an ISO 639 language code!" << endl;
711 error << "Regional variants are not relevant for searches." << endl;
712 return -1;
713 }
714
715 list->languages.insert(lval);
716 }
717 else if (key == "offline")
718 {
719 if (!list)
720 {
721 ERROR << "No list has been defined for offline setting '" << val << "'!" << endl;
722 return -1;
723 }
724 if (lc != "")
725 {
726 ERROR << "list offline cannot be localized" << endl;
727 return -1;
728 }
729
730 if (val == "off" || val == "false")
731 list->offline = false;
732 else if (val == "on" || val == "true")
733 list->offline = true;
734 else
735 {
736 ERROR << "offline must be set to on/off or true/false!" << endl;
737 return -1;
738 }
739 }
740 else if (key == "description")
741 {
742 if (!list)
743 {
744 ERROR << "No list has been defined for address '" << val << "'!" << endl;
745 return -1;
746 }
747
748 list->description.translate(lc, val);
749 }
750 else if (key == "frontend")
751 {
752 len = 10240; // Long paths are ... ok
753
754 if (lc != "")
755 {
756 ERROR << "frontend path '" << val << "' cannot be localized" << endl;
757 return -1;
758 }
759
760 if (val.length() == 0 || val[0] != '/')
761 {
762 ERROR << "frontend path is not absolute '" << val << "' (must start with a '/')!" << endl;
763 return -1;
764 }
765
766 // Cleanup the path, but ignore any problems with the path
767 string sval(val);
768 simplifyPath(sval);
769
770 if (frontends.find(sval) == frontends.end())
771 {
772 frontend = &frontends[sval];
773
774 // Inherit global values
775 frontend->admin_name = admin_name;
776 frontend->admin_address = admin_address;
777 frontend->archive = archive;
778 frontend->hide_email = hide_email;
779 frontend->raw_email = raw_email;
780 frontend->web_cache = web_cache;
781 }
782 else
783 {
784 ERROR << "Frontend '" << val << "' already exists!" << endl;
785 return -1;
786 }
787 }
788 else if (key == "allow_list")
789 {
790 if (frontend == 0)
791 {
792 ERROR << "No frontend defined for allow_list = '" << val << "'" << endl;
793 return -1;
794 }
795
796 if (lists.find(val) == lists.end())
797 {
798 ERROR << "List '" << val << "' does not exist for allow_list" << endl;
799 return -1;
800 }
801
802 Frontend::Entry e;
803 e.perm = Frontend::ALLOW;
804 e.what = Frontend::LIST;
805 e.key = val;
806 frontend->entries.push_back(e);
807 }
808 else if (key == "deny_list")
809 {
810 if (frontend == 0)
811 {
812 ERROR << "No frontend defined for deny_list = '" << val << "'" << endl;
813 return -1;
814 }
815
816 if (lists.find(val) == lists.end())
817 {
818 ERROR << "List '" << val << "' does not exist for deny_list" << endl;
819 return -1;
820 }
821
822 Frontend::Entry e;
823 e.perm = Frontend::DENY;
824 e.what = Frontend::LIST;
825 e.key = val;
826 frontend->entries.push_back(e);
827 }
828 else if (key == "allow_group")
829 {
830 if (frontend == 0)
831 {
832 ERROR << "No frontend defined for allow_group = '" << val << "'" << endl;
833 return -1;
834 }
835
836 if (groups.find(val) == groups.end())
837 {
838 ERROR << "Group '" << val << "' does not exist for allow_group" << endl;
839 return -1;
840 }
841
842 Frontend::Entry e;
843 e.perm = Frontend::ALLOW;
844 e.what = Frontend::GROUP;
845 e.key = val;
846 frontend->entries.push_back(e);
847 }
848 else if (key == "deny_group")
849 {
850 if (frontend == 0)
851 {
852 ERROR << "No frontend defined for deny_group = '" << val << "'" << endl;
853 return -1;
854 }
855
856 if (groups.find(val) == groups.end())
857 {
858 ERROR << "Group '" << val << "' does not exist for deny_group" << endl;
859 return -1;
860 }
861
862 Frontend::Entry e;
863 e.perm = Frontend::DENY;
864 e.what = Frontend::GROUP;
865 e.key = val;
866 frontend->entries.push_back(e);
867 }
868 else if (key == "dbdir")
869 {
870 if (lc != "")
871 {
872 ERROR << "dbdir cannot be localized" << endl;
873 return -1;
874 }
875
876 if (val[0] == '/')
877 dbdir = val;
878 else dbdir = dir + val;
879 }
880 else if (key == "db_umask")
881 {
882 if (lc != "")
883 {
884 ERROR << "db_umask cannot be localized" << endl;
885 return -1;
886 }
887
888 char* e;
889 db_umask = strtol(val.c_str(), &e, 8);
890 if (val.length() == 0 || *e != 0)
891 {
892 ERROR << "db_mask must be given an octal number, not '" << val << "'" << endl;
893 return -1;
894 }
895 }
896 else if (key == "admin_name")
897 {
898 if (frontend == 0)
899 admin_name.translate(lc, val);
900 else frontend->admin_name.translate(lc, val);
901 }
902 else if (key == "admin_address")
903 {
904 if (lc != "")
905 {
906 ERROR << "admin_address cannot be localized" << endl;
907 return -1;
908 }
909 if (frontend == 0)
910 admin_address = val;
911 else frontend->admin_address = val;
912 }
913 else if (key == "archive")
914 {
915 if (frontend == 0)
916 archive.translate(lc, val);
917 else frontend->archive.translate(lc, val);
918 }
919 else if (key == "xslt")
920 {
921 if (lc != "")
922 {
923 ERROR << "xslt command cannot be localized" << endl;
924 return -1;
925 }
926 xslt = val;
927 }
928 else if (key == "delete_message")
929 {
930 if (lc != "")
931 {
932 ERROR << "delete_message command cannot be localized" << endl;
933 return -1;
934 }
935 delete_message = val;
936 }
937 else if (key == "pgp_verify_mime")
938 {
939 if (lc != "")
940 {
941 ERROR << "pgp_verify_mime command cannot be localized" << endl;
942 return -1;
943 }
944 pgpv_mime = val;
945 }
946 else if (key == "pgp_verify_inline")
947 {
948 if (lc != "")
949 {
950 ERROR << "pgp_verify_inline command cannot be localized" << endl;
951 return -1;
952 }
953 pgpv_inline = val;
954 }
955 else if (key == "web_cache")
956 {
957 if (lc != "")
958 {
959 ERROR << "web_cache cannot be localized" << endl;
960 return -1;
961 }
962
963 bool newval;
964 if (val == "off" || val == "false")
965 newval = false;
966 else if (val == "on" || val == "true")
967 newval = true;
968 else
969 {
970 ERROR << "web_cache must be set to on/off or true/false!" << endl;
971 return -1;
972 }
973
974 if (frontend == 0)
975 web_cache = newval;
976 else frontend->web_cache = newval;
977 }
978 else if (key == "hide_email")
979 {
980 if (lc != "")
981 {
982 ERROR << "hide_email cannot be localized" << endl;
983 return -1;
984 }
985
986 bool newval;
987 if (val == "off" || val == "false")
988 newval = false;
989 else if (val == "on" || val == "true")
990 newval = true;
991 else
992 {
993 ERROR << "hide_email must be set to on/off or true/false!" << endl;
994 return -1;
995 }
996
997 if (frontend == 0)
998 hide_email = newval;
999 else frontend->hide_email = newval;
1000 }
1001 else if (key == "raw_email")
1002 {
1003 if (lc != "")
1004 {
1005 ERROR << "raw_email cannot be localized" << endl;
1006 return -1;
1007 }
1008
1009 bool newval;
1010 if (val == "off" || val == "false")
1011 newval = false;
1012 else if (val == "on" || val == "true")
1013 newval = true;
1014 else
1015 {
1016 ERROR << "raw_email must be set to on/off or true/false!" << endl;
1017 return -1;
1018 }
1019
1020 if (frontend == 0)
1021 raw_email = newval;
1022 else frontend->raw_email = newval;
1023 }
1024 else if (key == "include")
1025 {
1026 if (lc != "")
1027 {
1028 ERROR << "include cannot be localized" << endl;
1029 return -1;
1030 }
1031 string file;
1032
1033 if (val[0] == '/')
1034 file = val;
1035 else file = dir + val;
1036
1037 DIR* d = opendir(file.c_str());
1038 if (d)
1039 {
1040 struct dirent* e;
1041 struct stat s;
1042 vector<string> paths;
1043 while ((e = readdir(d)) != 0)
1044 {
1045 int len = strlen(e->d_name);
1046 if (e->d_name[0] == '.') continue;
1047 if (len <= 5) continue;
1048 if (strcmp(".conf", &e->d_name[len-5]))
1049 continue;
1050
1051 string path = file + '/' + e->d_name;
1052 if (stat(path.c_str(), &s) != 0) continue;
1053 if (!S_ISREG(s.st_mode)) continue;
1054
1055 paths.push_back(path);
1056 }
1057 closedir(d);
1058
1059 // Include them in sorted order
1060 sort(paths.begin(), paths.end());
1061 for (vector<string>::const_iterator i = paths.begin();
1062 i != paths.end(); ++i)
1063 {
1064 if (load(*i, false) != 0)
1065 return -1;
1066 }
1067 }
1068 else
1069 {
1070 if (load(file, false) != 0)
1071 return -1;
1072 }
1073 }
1074 else
1075 {
1076 ERROR << "Unknown configuration directive '" << key << "'!" << endl;
1077 return -1;
1078 }
1079
1080 if (val.length() > len)
1081 {
1082 ERROR << "Value '" << val << "' is too long for directive '" << key << "'!" << endl;
1083 return -1;
1084 }
1085
1086 return 0;
1087 }
1088
operator <<(ostream & o,const List::SerializeMagic & lm)1089 ostream& operator << (ostream& o, const List::SerializeMagic& lm)
1090 {
1091 const List& m = lm.m;
1092 const string& l = lm.l;
1093
1094 o << "<list>"
1095 << "<id>" << m.mbox << "</id>"
1096 << "<group>" << m.group << "</group>";
1097
1098 set<string>::const_iterator i, e;
1099 for (i = m.languages.begin(), e = m.languages.end(); i != e; ++i)
1100 o << "<language>" << *i << "</language>";
1101
1102 if (m.offline)
1103 o << "<offline/>";
1104
1105 if (m.link.is_set())
1106 o << "<link>" << xmlEscape << m.link(l) << "</link>";
1107
1108 if (m.description.is_set())
1109 o << "<description>" << xmlEscape << m.description(l) << "</description>";
1110
1111 o << "<email";
1112 if (m.address.length() > 0)
1113 o << " address=\"" << xmlEscape << m.address << "\"";
1114 if (m.title.is_set())
1115 o << " name=\"" << xmlEscape << whitespace_sanitize(m.title(l)) << "\"";
1116 else
1117 o << " name=\"" << m.mbox << "\"";
1118
1119 o << "/></list>";
1120
1121 return o;
1122 }
1123
operator <<(ostream & o,const Config::SerializeMagic & cm)1124 ostream& operator << (ostream& o, const Config::SerializeMagic& cm)
1125 {
1126 const Config& c = cm.c;
1127 const string& l = cm.l;
1128
1129 // expire = time(0) + 60*5; // 5 minute cache
1130 //
1131 // tm = gmtime(&expire);
1132 // strftime(&timebuf[0], sizeof(timebuf),
1133 // "%a, %d %b %Y %H:%M:%S GMT", tm);
1134
1135 char year[40];
1136 time_t end_of_archive = time(0) + 365*24*60*60;
1137 strftime(&year[0], sizeof(year), "%Y", gmtime(&end_of_archive));
1138
1139 o << "<server>"
1140 << "<version>" << VERSION << "</version>"
1141 << "<eoa-year>" << year << "</eoa-year>"
1142 << "<doc-url>" << xmlEscape << c.docUrl << "</doc-url>"
1143 << "<cgi-url>" << xmlEscape << c.cgiUrl << "</cgi-url>"
1144 << "<command>" << c.command << "</command>"
1145 << "<options>" << xmlEscape << c.options << "</options>";
1146
1147 if (c.raw_email) o << "<raw-email/>";
1148
1149 o << "<archive>";
1150
1151 if (c.archive.is_set())
1152 o << xmlEscape << c.archive(l);
1153 else o << "Some Mailing List Archive";
1154
1155 o << "</archive><email";
1156 if (c.admin_address.length() > 0)
1157 o << " address=\"" << xmlEscape << c.admin_address << "\"";
1158 if (c.admin_name.is_set())
1159 o << " name=\"" << xmlEscape << whitespace_sanitize(c.admin_name(l)) << "\"";
1160 o << "/></server>";
1161
1162 return o;
1163 }
1164
set_permissions(const Frontend & f)1165 void Config::set_permissions(const Frontend& f)
1166 {
1167 vector<Frontend::Entry>::const_iterator ei,
1168 es = f.entries.begin(),
1169 ee = f.entries.end();
1170
1171 Lists::iterator li,
1172 ls = lists.begin(),
1173 le = lists.end();
1174
1175 // Empty list or first entry deny means default to allow
1176 bool def = (es == ee) || (es->perm == Frontend::DENY);
1177 for (li = ls; li != le; ++li) li->second.allowed = def;
1178
1179 // Walk each entry toggling permissions as we go
1180 for (ei = es; ei != ee; ++ei)
1181 {
1182 bool allowed = (ei->perm == Frontend::ALLOW);
1183
1184 if (ei->what == Frontend::LIST)
1185 {
1186 lists[ei->key].allowed = allowed;
1187 }
1188 else
1189 { // group
1190 Members::const_iterator mi,
1191 ms = groups[ei->key].members.begin(),
1192 me = groups[ei->key].members.end();
1193 for (mi = ms; mi != me; ++mi)
1194 {
1195 lists[*mi].allowed = allowed;
1196 }
1197 }
1198 }
1199
1200 // Take the specific settings for the globals
1201 archive = f.archive;
1202 admin_name = f.admin_name;
1203 admin_address = f.admin_address;
1204 hide_email = f.hide_email;
1205 raw_email = f.raw_email;
1206 web_cache = f.web_cache;
1207 }
1208