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