summaryrefslogtreecommitdiffstats
path: root/lurker/common/ConfigFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lurker/common/ConfigFile.cpp')
-rw-r--r--lurker/common/ConfigFile.cpp1207
1 files changed, 1207 insertions, 0 deletions
diff --git a/lurker/common/ConfigFile.cpp b/lurker/common/ConfigFile.cpp
new file mode 100644
index 0000000..b781cc7
--- /dev/null
+++ b/lurker/common/ConfigFile.cpp
@@ -0,0 +1,1207 @@
+/* $Id: ConfigFile.cpp 1649 2009-10-19 14:35:01Z terpstra $
+ *
+ * ConfigFile.cpp - Knows how to load the config file
+ *
+ * Copyright (C) 2002 - Wesley W. Terpstra
+ *
+ * License: GPL
+ *
+ * Authors: 'Wesley W. Terpstra' <wesley@terpstra.ca>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define _FILE_OFFSET_BITS 64
+
+#include "ConfigFile.h"
+#include "XmlEscape.h"
+#include "Summary.h"
+
+#include <fstream>
+#include <iostream>
+#include <algorithm>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <cstring>
+
+using namespace std;
+
+#define ERROR error << file << ":" << c << ": "
+
+map<string, string>* lstring::c = 0;
+
+lstring::lstring(const string& fallback)
+{
+ s[""] = fallback;
+}
+
+string simplifyPath(string& path)
+{
+ string error = "";
+
+#ifdef HAVE_REALPATH
+ char clean[PATH_MAX];
+
+ if (realpath(path.c_str(), clean) != 0)
+ {
+ path = clean;
+ return "";
+ }
+ else
+ {
+ // on linux, realpath files if the last part DNE
+ string dir(path, 0, path.rfind('/'));
+ string tag(path, dir.length(), string::npos);
+
+ if (realpath(dir.c_str(), clean) != 0)
+ {
+ path = clean + tag;
+ return "";
+ }
+ }
+
+ // realpath failed, report the problem path
+ error = clean;
+ // fall through with best attempt to clean it up anyways
+#endif
+
+ // The least we can do is to trim trailing '/'s and simplify '//'s
+ while (path.length() && path[path.length()-1] == '/')
+ path.resize(path.length()-1);
+
+ string::size_type x = 0;
+ while ((x = path.find("//", x)) != string::npos)
+ path.erase(x, 1);
+
+ return error;
+}
+
+void lstring::prep_c()
+{
+ c = new map<string, string>();
+
+ // source: http://www.w3.org/WAI/ER/IG/ert/iso639.htm
+ static const char* code[][2] =
+ { {"abk", "ab"},
+ {"aar", "aa"},
+ {"afr", "af"},
+ {"alb", "sq"},
+ {"amh", "am"},
+ {"ara", "ar"},
+ {"arm", "hy"},
+ {"asm", "as"},
+ {"aym", "ay"},
+ {"aze", "az"},
+ {"bak", "ba"},
+ {"baq", "eu"},
+ {"ben", "bn"},
+ {"bih", "bh"},
+ {"bis", "bi"},
+ {"bre", "be"},
+ {"bul", "bg"},
+ {"bur", "my"},
+ {"bel", "be"},
+ {"cat", "ca"},
+ {"chi", "zh"},
+ {"cos", "co"},
+ {"ces", "cs"},
+ {"dan", "da"},
+ {"dut", "nl"},
+ {"dzo", "dz"},
+ {"eng", "en"},
+ {"epo", "eo"},
+ {"est", "et"},
+ {"fao", "fo"},
+ {"fij", "fj"},
+ {"fin", "fi"},
+ {"fra", "fr"},
+ {"fry", "fy"},
+ {"glg", "gl"},
+ {"geo", "ka"},
+ {"deu", "de"},
+ {"ell", "el"},
+ {"kal", "kl"},
+ {"grn", "gn"},
+ {"guj", "gu"},
+ {"hau", "ha"},
+ {"heb", "he"},
+ {"hin", "hi"},
+ {"hun", "hu"},
+ {"ice", "is"},
+ {"ind", "id"},
+ {"ina", "ia"},
+ {"iku", "iu"},
+ {"ipk", "ik"},
+ {"gai", "ga"},
+ {"ita", "it"},
+ {"jpn", "ja"},
+ {"jav", "jv"},
+ {"kan", "kn"},
+ {"kas", "ks"},
+ {"kaz", "kk"},
+ {"khm", "km"},
+ {"kin", "rw"},
+ {"kir", "ky"},
+ {"kor", "ko"},
+ {"kur", "ku"},
+ {"oci", "oc"},
+ {"lao", "lo"},
+ {"lat", "la"},
+ {"lav", "lv"},
+ {"lin", "ln"},
+ {"lit", "lt"},
+ {"mac", "mk"},
+ {"mlg", "mg"},
+ {"may", "ms"},
+ {"mlt", "ml"},
+ {"mao", "mi"},
+ {"mar", "mr"},
+ {"mol", "mo"},
+ {"mon", "mn"},
+ {"nau", "na"},
+ {"nep", "ne"},
+ {"nor", "no"},
+ {"ori", "or"},
+ {"orm", "om"},
+ {"pan", "pa"},
+ {"fas", "fa"},
+ {"pol", "pl"},
+ {"por", "pt"},
+ {"pus", "ps"},
+ {"que", "qu"},
+ {"roh", "rm"},
+ {"ron", "ro"},
+ {"run", "rn"},
+ {"rus", "ru"},
+ {"smo", "sm"},
+ {"sag", "sg"},
+ {"san", "sa"},
+ {"scr", "sh"},
+ {"sna", "sn"},
+ {"snd", "sd"},
+ {"sin", "si"},
+ {"ssw", "ss"},
+ {"slk", "sk"},
+ {"slv", "sl"},
+ {"som", "so"},
+ {"sot", "st"},
+ {"esl", "es"},
+ {"sun", "su"},
+ {"swa", "sw"},
+ {"sve", "sv"},
+ {"tgl", "tl"},
+ {"tgk", "tg"},
+ {"tam", "ta"},
+ {"tat", "tt"},
+ {"tel", "te"},
+ {"tha", "th"},
+ {"bod", "bo"},
+ {"tir", "ti"},
+ {"tog", "to"},
+ {"tso", "ts"},
+ {"tsn", "tn"},
+ {"tur", "tr"},
+ {"tuk", "tk"},
+ {"twi", "tw"},
+ {"uig", "ug"},
+ {"ukr", "uk"},
+ {"urd", "ur"},
+ {"uzb", "uz"},
+ {"vie", "vi"},
+ {"vol", "vo"},
+ {"cym", "cy"},
+ {"wol", "wo"},
+ {"xho", "xh"},
+ {"yid", "yi"},
+ {"yor", "yo"},
+ {"zha", "za"},
+ {"zul", "zu"},
+ {"sqi", "sq"},
+ {"hye", "hy"},
+ {"eus", "eu"},
+ {"mya", "my"},
+ {"zho", "zh"},
+ {"cze", "cs"},
+ {"nla", "nl"},
+ {"fre", "fr"},
+ {"kat", "ka"},
+ {"ger", "de"},
+ {"gre", "el"},
+ {"isl", "is"},
+ {"iri", "ga"},
+ {"jaw", "jv"},
+ {"mak", "mk"},
+ {"msa", "ms"},
+ {"mri", "mi"},
+ {"per", "fa"},
+ {"rum", "ro"},
+ {"slo", "sk"},
+ {"spa", "es"},
+ {"swe", "sv"},
+ {"tib", "bo"},
+ {"wel", "cy"},
+ // I think these are the same?
+ {"jw", "jv"},
+ // ditto
+ {"hr", "scc"},
+ {"sr", "scc"},
+ {"srp", "scc"},
+ {"", ""}
+ };
+
+ for (unsigned int i = 0; code[i][0][0]; ++i)
+ (*c)[code[i][0]] = code[i][1];
+}
+
+bool lstring::lang_normalize(string& lang)
+{
+ if (!c) prep_c();
+
+ if (lang.length() != 2 && lang.length() != 3)
+ return false;
+
+ // lower-case it:
+ string iso(lang);
+ for (string::size_type i = 0; i < iso.length(); ++i)
+ {
+ if (iso[i] >= 'A' && iso[i] <= 'Z')
+ iso[i] = iso[i] - 'A' + 'a';
+ if (iso[i] < 'a' || iso[i] > 'z')
+ return false; // not an ISO 639 code!
+ }
+
+ // Resolve different language spellings to one spelling
+ if (c->find(iso) != c->end())
+ lang = (*c)[iso];
+ else lang = iso;
+
+ return true;
+}
+
+bool lstring::locale_normalize(string& locale)
+{
+ string::size_type i, e;
+
+ if ((e = locale.find('-')) == string::npos &&
+ (e = locale.find('_')) == string::npos)
+ e = locale.length();
+
+ string lang(locale, 0, e);
+ if (!lang_normalize(lang)) return false;
+
+ string region;
+ if (e != locale.length())
+ {
+ region.assign(locale, e+1, string::npos);
+ if (region.length() != 2) return false; // not an ISO 3166 code
+
+ for (i = 0; i < 2; ++i)
+ {
+ if (region[i] >= 'a' && region[i] <= 'z')
+ region[i] = region[i] - 'a' + 'A';
+ if (region[i] < 'A' || region[i] > 'Z')
+ return false; // not an ISO 3166 code!
+ }
+
+ locale = lang + "-" + region;
+ }
+ else
+ {
+ locale = lang;
+ }
+
+ return true;
+}
+
+bool lstring::translate(const string& language_, const string& translation)
+{
+ string language(language_);
+ if (language != "" && !locale_normalize(language)) return false;
+
+ string::size_type i;
+
+ // this overrides whatever we have got so far
+ s[language] = translation;
+
+ // maybe a localized string for which we lack a language setting?
+ if ((i = language.find('-')) != string::npos)
+ {
+ string iso(language, 0, i);
+ if (s.find(iso) == s.end()) translate(iso, translation);
+ }
+
+ // maybe we lack a fallback language completely
+ if (s.find("") == s.end()) translate("", translation);
+
+ return true;
+}
+
+string lstring::localize(const string& language_) const
+{
+ string language(language_);
+ if (language != "" && !locale_normalize(language)) return "(bug) bad locale";
+
+ map<string, string>::const_iterator o;
+ string::size_type i;
+
+ // correct locale? use it
+ if ((o = s.find(language)) != s.end()) return o->second;
+
+ // correct language? use it
+ if ((i = language.find('-')) != string::npos)
+ {
+ string iso(language, 0, i);
+ if ((o = s.find(iso)) != s.end()) return o->second;
+ }
+
+ // fallback? use it
+ if ((o = s.find("")) != s.end()) return o->second;
+
+ return ""; // not set!
+}
+
+bool lstring::is_set() const
+{
+ return !s.empty();
+}
+
+Config::Config()
+ : list(0), frontend(0), group(""), error(), lists(), groups(),
+ file(""),
+ dbdir(""),
+ db_umask(-1),
+ xslt("cat -"),
+ delete_message(""),
+ pgpv_mime("off"),
+ pgpv_inline("off"),
+ admin_address(""),
+
+ archive("Unconfigured Archivew"),
+ admin_name("Unset admin name"),
+ web_cache(true),
+ hide_email(false),
+ raw_email(true),
+ modified(0)
+{
+}
+
+void prune_back(string& line)
+{
+ // Trim off eol and whitespace
+ string::size_type whitespace = line.length();
+ while (whitespace > 0 &&
+ (line[whitespace-1] == ' ' ||
+ line[whitespace-1] == '\r' ||
+ line[whitespace-1] == '\n' ||
+ line[whitespace-1] == '\t'))
+ whitespace--;
+
+ line.resize(whitespace);
+}
+
+string::size_type skip_front(const string& line, string::size_type x = 0)
+{
+ // Trim off eol and whitespace
+ for (; x < line.length(); ++x)
+ if (line[x] != ' ' &&
+ line[x] != '\r' &&
+ line[x] != '\n' &&
+ line[x] != '\t')
+ break;
+
+ return x;
+}
+
+int Config::load(const string& file, bool toplevel)
+{
+ ifstream f(file.c_str());
+ if (!f.is_open())
+ {
+ error << file << ":open: could not open!" << endl;
+ return -1;
+ }
+
+ struct stat sbuf;
+ if (stat(file.c_str(), &sbuf) < 0)
+ {
+ error << file << ":stat: could not stat!" << endl;
+ return -1;
+ }
+
+ // deal with included file's timestamps
+ if (sbuf.st_mtime > modified)
+ modified = sbuf.st_mtime;
+
+ string dir;
+ string::size_type x = file.rfind('/');
+ if (x != string::npos) dir.assign(file, 0, x+1);
+
+ string line;
+ bool ok = true;
+ int c = 0;
+
+ string val, key;
+
+ while (getline(f, line))
+ {
+ // Increment line number
+ ++c;
+
+ // Trim off the comments
+ string::size_type comment = line.find('#');
+ if (comment != string::npos) line.resize(comment);
+
+ // Clear off trailing whitespace
+ prune_back(line);
+
+ // skip empty lines
+ if (line.length() == 0) continue;
+
+ string::size_type eq = line.find('=');
+ if (eq == string::npos)
+ { // this line continues the previous one.
+ if (key == "")
+ {
+ ERROR << "No key for value '" << line << "'!" << endl;
+ ok = false;
+ }
+ else
+ {
+ string::size_type fe = skip_front(line);
+ val.append(" ");
+ val.append(line, fe, string::npos);
+ }
+ }
+ else
+ {
+ if (key != "" && process_command(file, c, key, val, dir) != 0) ok = false;
+
+ string::size_type leadin = skip_front(line);
+ key.assign(line, leadin, eq-leadin);
+ val.assign(line, skip_front(line, eq+1), string::npos);
+ prune_back(key);
+ }
+ }
+
+ if (toplevel && key == "")
+ {
+ error << file << ":eof: No values set!" << endl;
+ ok = false;
+ }
+
+ if (key != "" && process_command(file, c, key, val, dir) != 0) ok = false;
+
+ if (toplevel)
+ {
+ // do some consistency checks
+
+ // language field is required
+ Lists::const_iterator i, e;
+ for (i = lists.begin(), e = lists.end(); i != e; ++i)
+ {
+ if (i->second.languages.empty())
+ {
+ error << file << ":eof: List '" << i->first << "' has no language!" << endl;
+ return -1;
+ }
+ }
+
+ // cache directories may not be prefixes
+ Frontends::const_iterator fi, fn, fe;
+ fe = frontends.end();
+ fi = frontends.begin();
+
+ fn = fi;
+ if (fi != fe) while (1)
+ {
+ ++fn;
+ if (fn == fe) break;
+
+ if (fi->first + "/" == fn->first.substr(0, fi->first.length()+1))
+ {
+ error << file << ":eof: Frontend '" << fi->first << "' is a prefix of '" << fn->first << "', which is forbidden!" << endl;
+ return -1;
+ }
+
+ fi = fn;
+ }
+
+ this->file = file;
+ }
+
+ if (!ok) return -1;
+ return 0;
+}
+
+bool isSimple(const string& s)
+{
+ string::size_type x;
+ for (x = 0; x < s.length(); ++x)
+ {
+ char y = s[x];
+ if (y >= 'a' && y <= 'z') continue;
+ if (y >= '0' && y <= '9') continue;
+ if (y == '.' || y == '-' || y == '_') continue;
+ return false;
+ }
+
+ return true;
+}
+
+int Config::process_command(const string& file, int c, const string& keys, const string& val, const string& dir)
+{
+// cout << key << "-" << val << endl;
+
+ string lc; // locale code
+ string key(keys);
+
+ string::size_type o, d;
+ if ((o = key.find('[')) != string::npos &&
+ (d = key.find(']')) != string::npos &&
+ d > o)
+ {
+ // localization option
+ lc.assign(key, o+1, (d-o) - 1);
+ key.erase(o, (d-o) + 1);
+
+ if (!lstring::locale_normalize(lc))
+ {
+ ERROR << "Localization code '" << lc << "' is not valid." << endl;
+ return -1;
+ }
+ }
+
+ string::size_type len = string::npos;
+
+ if (key == "group")
+ {
+ len = 128;
+ if (!isSimple(val) || val.length() == 0)
+ {
+ ERROR << "Group id '" << val << "' is not a simple lowercase string!" << endl;
+ return -1;
+ }
+
+ if (lc != "")
+ {
+ ERROR << "group id cannot be localized" << endl;
+ return -1;
+ }
+
+ if (groups.find(val) != groups.end())
+ {
+ ERROR << "Group id '" << val << "' already exists!" << endl;
+ return -1;
+ }
+
+ group = val;
+ groups[group]; // make sure it exists
+ }
+ else if (key == "heading")
+ {
+ len = 180;
+ groups[group].heading.translate(lc, val);
+ }
+ else if (key == "list")
+ {
+ if (group == "")
+ {
+ ERROR << "List id '" << val << "' is not a member of any group!" << endl;
+ return -1;
+ }
+
+ len = 128;
+ if (!isSimple(val) || val.length() == 0)
+ {
+ ERROR << "List id '" << val << "' is not a simple lowercase string!" << endl;
+ return -1;
+ }
+ if (lc != "")
+ {
+ ERROR << "list id cannot be localized" << endl;
+ return -1;
+ }
+
+ if (lists.find(val) == lists.end())
+ {
+ groups[group].members.insert(val);
+ list = &lists[val];
+ list->mbox = val;
+ list->group = group;
+ list->offline = false;
+ }
+ else
+ {
+ ERROR << "List id '" << val << "' already exists!" << endl;
+ return -1;
+ }
+ }
+ else if (key == "title")
+ {
+ len = 180;
+
+ if (!list)
+ {
+ ERROR << "No list has been defined for title '" << val << "'!" << endl;
+ return -1;
+ }
+
+ list->title.translate(lc, val);
+ }
+ else if (key == "address")
+ {
+ if (!list)
+ {
+ ERROR << "No list has been defined for address '" << val << "'!" << endl;
+ return -1;
+ }
+ if (lc != "")
+ {
+ ERROR << "list address cannot be localized" << endl;
+ return -1;
+ }
+
+ list->address = val;
+ }
+ else if (key == "link")
+ {
+ if (!list)
+ {
+ ERROR << "No list has been defined for address '" << val << "'!" << endl;
+ return -1;
+ }
+
+ list->link.translate(lc, val);
+ }
+ else if (key == "language")
+ {
+ if (!list)
+ {
+ ERROR << "No list has been defined for language '" << val << "'!" << endl;
+ return -1;
+ }
+ if (lc != "")
+ {
+ ERROR << "list language cannot be localized" << endl;
+ return -1;
+ }
+
+ string lval(val);
+ if (!lstring::lang_normalize(lval))
+ {
+ ERROR << "Language '" << val << "' is not an ISO 639 language code!" << endl;
+ error << "Regional variants are not relevant for searches." << endl;
+ return -1;
+ }
+
+ list->languages.insert(lval);
+ }
+ else if (key == "offline")
+ {
+ if (!list)
+ {
+ ERROR << "No list has been defined for offline setting '" << val << "'!" << endl;
+ return -1;
+ }
+ if (lc != "")
+ {
+ ERROR << "list offline cannot be localized" << endl;
+ return -1;
+ }
+
+ if (val == "off" || val == "false")
+ list->offline = false;
+ else if (val == "on" || val == "true")
+ list->offline = true;
+ else
+ {
+ ERROR << "offline must be set to on/off or true/false!" << endl;
+ return -1;
+ }
+ }
+ else if (key == "description")
+ {
+ if (!list)
+ {
+ ERROR << "No list has been defined for address '" << val << "'!" << endl;
+ return -1;
+ }
+
+ list->description.translate(lc, val);
+ }
+ else if (key == "frontend")
+ {
+ len = 10240; // Long paths are ... ok
+
+ if (lc != "")
+ {
+ ERROR << "frontend path '" << val << "' cannot be localized" << endl;
+ return -1;
+ }
+
+ if (val.length() == 0 || val[0] != '/')
+ {
+ ERROR << "frontend path is not absolute '" << val << "' (must start with a '/')!" << endl;
+ return -1;
+ }
+
+ // Cleanup the path, but ignore any problems with the path
+ string sval(val);
+ simplifyPath(sval);
+
+ if (frontends.find(sval) == frontends.end())
+ {
+ frontend = &frontends[sval];
+
+ // Inherit global values
+ frontend->admin_name = admin_name;
+ frontend->admin_address = admin_address;
+ frontend->archive = archive;
+ frontend->hide_email = hide_email;
+ frontend->raw_email = raw_email;
+ frontend->web_cache = web_cache;
+ }
+ else
+ {
+ ERROR << "Frontend '" << val << "' already exists!" << endl;
+ return -1;
+ }
+ }
+ else if (key == "allow_list")
+ {
+ if (frontend == 0)
+ {
+ ERROR << "No frontend defined for allow_list = '" << val << "'" << endl;
+ return -1;
+ }
+
+ if (lists.find(val) == lists.end())
+ {
+ ERROR << "List '" << val << "' does not exist for allow_list" << endl;
+ return -1;
+ }
+
+ Frontend::Entry e;
+ e.perm = Frontend::ALLOW;
+ e.what = Frontend::LIST;
+ e.key = val;
+ frontend->entries.push_back(e);
+ }
+ else if (key == "deny_list")
+ {
+ if (frontend == 0)
+ {
+ ERROR << "No frontend defined for deny_list = '" << val << "'" << endl;
+ return -1;
+ }
+
+ if (lists.find(val) == lists.end())
+ {
+ ERROR << "List '" << val << "' does not exist for deny_list" << endl;
+ return -1;
+ }
+
+ Frontend::Entry e;
+ e.perm = Frontend::DENY;
+ e.what = Frontend::LIST;
+ e.key = val;
+ frontend->entries.push_back(e);
+ }
+ else if (key == "allow_group")
+ {
+ if (frontend == 0)
+ {
+ ERROR << "No frontend defined for allow_group = '" << val << "'" << endl;
+ return -1;
+ }
+
+ if (groups.find(val) == groups.end())
+ {
+ ERROR << "Group '" << val << "' does not exist for allow_group" << endl;
+ return -1;
+ }
+
+ Frontend::Entry e;
+ e.perm = Frontend::ALLOW;
+ e.what = Frontend::GROUP;
+ e.key = val;
+ frontend->entries.push_back(e);
+ }
+ else if (key == "deny_group")
+ {
+ if (frontend == 0)
+ {
+ ERROR << "No frontend defined for deny_group = '" << val << "'" << endl;
+ return -1;
+ }
+
+ if (groups.find(val) == groups.end())
+ {
+ ERROR << "Group '" << val << "' does not exist for deny_group" << endl;
+ return -1;
+ }
+
+ Frontend::Entry e;
+ e.perm = Frontend::DENY;
+ e.what = Frontend::GROUP;
+ e.key = val;
+ frontend->entries.push_back(e);
+ }
+ else if (key == "dbdir")
+ {
+ if (lc != "")
+ {
+ ERROR << "dbdir cannot be localized" << endl;
+ return -1;
+ }
+
+ if (val[0] == '/')
+ dbdir = val;
+ else dbdir = dir + val;
+ }
+ else if (key == "db_umask")
+ {
+ if (lc != "")
+ {
+ ERROR << "db_umask cannot be localized" << endl;
+ return -1;
+ }
+
+ char* e;
+ db_umask = strtol(val.c_str(), &e, 8);
+ if (val.length() == 0 || *e != 0)
+ {
+ ERROR << "db_mask must be given an octal number, not '" << val << "'" << endl;
+ return -1;
+ }
+ }
+ else if (key == "admin_name")
+ {
+ if (frontend == 0)
+ admin_name.translate(lc, val);
+ else frontend->admin_name.translate(lc, val);
+ }
+ else if (key == "admin_address")
+ {
+ if (lc != "")
+ {
+ ERROR << "admin_address cannot be localized" << endl;
+ return -1;
+ }
+ if (frontend == 0)
+ admin_address = val;
+ else frontend->admin_address = val;
+ }
+ else if (key == "archive")
+ {
+ if (frontend == 0)
+ archive.translate(lc, val);
+ else frontend->archive.translate(lc, val);
+ }
+ else if (key == "xslt")
+ {
+ if (lc != "")
+ {
+ ERROR << "xslt command cannot be localized" << endl;
+ return -1;
+ }
+ xslt = val;
+ }
+ else if (key == "delete_message")
+ {
+ if (lc != "")
+ {
+ ERROR << "delete_message command cannot be localized" << endl;
+ return -1;
+ }
+ delete_message = val;
+ }
+ else if (key == "pgp_verify_mime")
+ {
+ if (lc != "")
+ {
+ ERROR << "pgp_verify_mime command cannot be localized" << endl;
+ return -1;
+ }
+ pgpv_mime = val;
+ }
+ else if (key == "pgp_verify_inline")
+ {
+ if (lc != "")
+ {
+ ERROR << "pgp_verify_inline command cannot be localized" << endl;
+ return -1;
+ }
+ pgpv_inline = val;
+ }
+ else if (key == "web_cache")
+ {
+ if (lc != "")
+ {
+ ERROR << "web_cache cannot be localized" << endl;
+ return -1;
+ }
+
+ bool newval;
+ if (val == "off" || val == "false")
+ newval = false;
+ else if (val == "on" || val == "true")
+ newval = true;
+ else
+ {
+ ERROR << "web_cache must be set to on/off or true/false!" << endl;
+ return -1;
+ }
+
+ if (frontend == 0)
+ web_cache = newval;
+ else frontend->web_cache = newval;
+ }
+ else if (key == "hide_email")
+ {
+ if (lc != "")
+ {
+ ERROR << "hide_email cannot be localized" << endl;
+ return -1;
+ }
+
+ bool newval;
+ if (val == "off" || val == "false")
+ newval = false;
+ else if (val == "on" || val == "true")
+ newval = true;
+ else
+ {
+ ERROR << "hide_email must be set to on/off or true/false!" << endl;
+ return -1;
+ }
+
+ if (frontend == 0)
+ hide_email = newval;
+ else frontend->hide_email = newval;
+ }
+ else if (key == "raw_email")
+ {
+ if (lc != "")
+ {
+ ERROR << "raw_email cannot be localized" << endl;
+ return -1;
+ }
+
+ bool newval;
+ if (val == "off" || val == "false")
+ newval = false;
+ else if (val == "on" || val == "true")
+ newval = true;
+ else
+ {
+ ERROR << "raw_email must be set to on/off or true/false!" << endl;
+ return -1;
+ }
+
+ if (frontend == 0)
+ raw_email = newval;
+ else frontend->raw_email = newval;
+ }
+ else if (key == "include")
+ {
+ if (lc != "")
+ {
+ ERROR << "include cannot be localized" << endl;
+ return -1;
+ }
+ string file;
+
+ if (val[0] == '/')
+ file = val;
+ else file = dir + val;
+
+ DIR* d = opendir(file.c_str());
+ if (d)
+ {
+ struct dirent* e;
+ struct stat s;
+ vector<string> paths;
+ while ((e = readdir(d)) != 0)
+ {
+ int len = strlen(e->d_name);
+ if (e->d_name[0] == '.') continue;
+ if (len <= 5) continue;
+ if (strcmp(".conf", &e->d_name[len-5]))
+ continue;
+
+ string path = file + '/' + e->d_name;
+ if (stat(path.c_str(), &s) != 0) continue;
+ if (!S_ISREG(s.st_mode)) continue;
+
+ paths.push_back(path);
+ }
+ closedir(d);
+
+ // Include them in sorted order
+ sort(paths.begin(), paths.end());
+ for (vector<string>::const_iterator i = paths.begin();
+ i != paths.end(); ++i)
+ {
+ if (load(*i, false) != 0)
+ return -1;
+ }
+ }
+ else
+ {
+ if (load(file, false) != 0)
+ return -1;
+ }
+ }
+ else
+ {
+ ERROR << "Unknown configuration directive '" << key << "'!" << endl;
+ return -1;
+ }
+
+ if (val.length() > len)
+ {
+ ERROR << "Value '" << val << "' is too long for directive '" << key << "'!" << endl;
+ return -1;
+ }
+
+ return 0;
+}
+
+ostream& operator << (ostream& o, const List::SerializeMagic& lm)
+{
+ const List& m = lm.m;
+ const string& l = lm.l;
+
+ o << "<list>"
+ << "<id>" << m.mbox << "</id>"
+ << "<group>" << m.group << "</group>";
+
+ set<string>::const_iterator i, e;
+ for (i = m.languages.begin(), e = m.languages.end(); i != e; ++i)
+ o << "<language>" << *i << "</language>";
+
+ if (m.offline)
+ o << "<offline/>";
+
+ if (m.link.is_set())
+ o << "<link>" << xmlEscape << m.link(l) << "</link>";
+
+ if (m.description.is_set())
+ o << "<description>" << xmlEscape << m.description(l) << "</description>";
+
+ o << "<email";
+ if (m.address.length() > 0)
+ o << " address=\"" << xmlEscape << m.address << "\"";
+ if (m.title.is_set())
+ o << " name=\"" << xmlEscape << whitespace_sanitize(m.title(l)) << "\"";
+ else
+ o << " name=\"" << m.mbox << "\"";
+
+ o << "/></list>";
+
+ return o;
+}
+
+ostream& operator << (ostream& o, const Config::SerializeMagic& cm)
+{
+ const Config& c = cm.c;
+ const string& l = cm.l;
+
+ // expire = time(0) + 60*5; // 5 minute cache
+ //
+ // tm = gmtime(&expire);
+ // strftime(&timebuf[0], sizeof(timebuf),
+ // "%a, %d %b %Y %H:%M:%S GMT", tm);
+
+ char year[40];
+ time_t end_of_archive = time(0) + 365*24*60*60;
+ strftime(&year[0], sizeof(year), "%Y", gmtime(&end_of_archive));
+
+ o << "<server>"
+ << "<version>" << VERSION << "</version>"
+ << "<eoa-year>" << year << "</eoa-year>"
+ << "<doc-url>" << xmlEscape << c.docUrl << "</doc-url>"
+ << "<cgi-url>" << xmlEscape << c.cgiUrl << "</cgi-url>"
+ << "<command>" << c.command << "</command>"
+ << "<options>" << xmlEscape << c.options << "</options>";
+
+ if (c.raw_email) o << "<raw-email/>";
+
+ o << "<archive>";
+
+ if (c.archive.is_set())
+ o << xmlEscape << c.archive(l);
+ else o << "Some Mailing List Archive";
+
+ o << "</archive><email";
+ if (c.admin_address.length() > 0)
+ o << " address=\"" << xmlEscape << c.admin_address << "\"";
+ if (c.admin_name.is_set())
+ o << " name=\"" << xmlEscape << whitespace_sanitize(c.admin_name(l)) << "\"";
+ o << "/></server>";
+
+ return o;
+}
+
+void Config::set_permissions(const Frontend& f)
+{
+ vector<Frontend::Entry>::const_iterator ei,
+ es = f.entries.begin(),
+ ee = f.entries.end();
+
+ Lists::iterator li,
+ ls = lists.begin(),
+ le = lists.end();
+
+ // Empty list or first entry deny means default to allow
+ bool def = (es == ee) || (es->perm == Frontend::DENY);
+ for (li = ls; li != le; ++li) li->second.allowed = def;
+
+ // Walk each entry toggling permissions as we go
+ for (ei = es; ei != ee; ++ei)
+ {
+ bool allowed = (ei->perm == Frontend::ALLOW);
+
+ if (ei->what == Frontend::LIST)
+ {
+ lists[ei->key].allowed = allowed;
+ }
+ else
+ { // group
+ Members::const_iterator mi,
+ ms = groups[ei->key].members.begin(),
+ me = groups[ei->key].members.end();
+ for (mi = ms; mi != me; ++mi)
+ {
+ lists[*mi].allowed = allowed;
+ }
+ }
+ }
+
+ // Take the specific settings for the globals
+ archive = f.archive;
+ admin_name = f.admin_name;
+ admin_address = f.admin_address;
+ hide_email = f.hide_email;
+ raw_email = f.raw_email;
+ web_cache = f.web_cache;
+}