view src/context.cpp @ 79:091d3fe3db46

start coding on new config syntax
author carl
date Sat, 16 Jul 2005 20:12:20 -0700
parents 8487650c98ee
children fa47e1e401f1
line wrap: on
line source

/*

Copyright (c) 2004 Carl Byington - 510 Software Group, released under
the GPL version 2 or any later version at your choice available at
http://www.fsf.org/licenses/gpl.txt

*/

#include "includes.h"

static char* context_version="$Id$";

char *token_black;
char *token_content;
char *token_context;
char *token_dccfrom;
char *token_dccto;
char *token_default;
char *token_dnsbl;
char *token_dnsbll;
char *token_envfrom;
char *token_envto;
char *token_filter;
char *token_host_limit;
char *token_html_limit;
char *token_html_tags;
char *token_ignore;
char *token_include;
char *token_inherit;
char *token_lbrace;
char *token_mailhost;
char *token_many;
char *token_off;
char *token_ok2;
char *token_ok;
char *token_on;
char *token_rbrace;
char *token_semi;
char *token_soft;
char *token_substitute;
char *token_tld;
char *token_unknown;
char *token_white;

string_set all_strings;     // owns all the strings, only modified by the config loader thread
const int maxlen = 1000;

DNSBL::DNSBL(char *n, char *s, char *m) {
    name    = n;
    suffix  = s;
    message = m;
}


CONFIG::CONFIG() {
    reference_count    = 0;
    generation         = 0;
    load_time          = 0;
    default_context    = NULL;
}


CONFIG::~CONFIG() {
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXT *c = *i;
        delete c;
    }
}


void CONFIG::add_context(CONTEXTP con) {
    contexts.push_back(con);
    if (!default_context && !con->get_parent()) {
        // first global context
        default_context = con;
    }
}


void CONFIG::add_to(char *to, CONTEXTP con) {
    context_map::iterator i = env_to.find(to);
    if (i != env_to.end()) {
        CONTEXTP  c = (*i).second;
        int       s = strlen(to);
        bool     at = s && (to[s-1] == '@');
        if (at && con->is_parent(c->get_parent())) {
            if (debug_syslog) {
                char oldname[maxlen];
                char newname[maxlen];
                char *oldn = c->get_full_name(oldname, maxlen);
                char *newn = con->get_full_name(newname, maxlen);
                char buf[maxlen*3];
                snprintf(buf, maxlen*3, "both %s and %s claim envelope to %s, the first one wins", oldn, newn, to);
                my_syslog(buf);
            }
            return;     // don't take over user@ entries from your ancestors children
        }
        if ((c != con) && (c != con->get_parent())) {
            char oldname[maxlen];
            char newname[maxlen];
            char *oldn = c->get_full_name(oldname, maxlen);
            char *newn = con->get_full_name(newname, maxlen);
            char buf[maxlen*3];
            snprintf(buf, maxlen*3, "both %s and %s claim envelope to %s, the second one wins", oldn, newn, to);
            my_syslog(buf);
        }
    }
    env_to[to] = con;
}


CONTEXTP CONFIG::find_context(char *to) {
    context_map::iterator i = env_to.find(to);
    if (i != env_to.end()) return (*i).second;      // found user@domain.tld key
    char *x = strchr(to, '@');
    if (x) {
        x++;
        i = env_to.find(x);
        if (i != env_to.end()) return (*i).second;  // found domain.tld key
        char y = *x;
        *x = '\0';
        i = env_to.find(to);
        *x = y;
        if (i != env_to.end()) return (*i).second;  // found user@ key
    }
    return default_context;
}


void CONFIG::dump() {
    if (default_context) default_context->dump();
    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
        CONTEXTP c = *i;
        CONTEXTP p = c->get_parent();
        if (!p && (c != default_context)) c->dump();
    }
    char buf[maxlen];
    for (context_map::iterator i=env_to.begin(); i!=env_to.end(); i++) {
        char     *to = (*i).first;
        CONTEXTP con = (*i).second;
        printf("// envelope to %s \t-> context %s \n", to, con->get_full_name(buf,maxlen));
    }
}


CONTEXT::CONTEXT(CONTEXTP parent_, char *name_) {
    parent              = parent_;
    name                = name_;
    env_from_default    = (parent) ? token_inherit : token_unknown;
    content_filtering   = (parent) ? parent->content_filtering : false;
    content_suffix      = NULL;
    content_message     = NULL;
    host_limit          = (parent) ? parent->host_limit  : 0;
    host_limit_message  = NULL;
    host_random         = (parent) ? parent->host_random : false;
    tag_limit           = (parent) ? parent->tag_limit   : 0;
    tag_limit_message   = NULL;
}


CONTEXT::~CONTEXT() {
    if (debug_syslog) {
        char buf[maxlen];
        char msg[maxlen];
        snprintf(msg, maxlen, "context::~context %s destructor", get_full_name(buf,maxlen));
        my_syslog(msg);
    }
    for (dnsblp_map::iterator i=dnsbl_names.begin(); i!=dnsbl_names.end(); i++) {
        DNSBLP d = (*i).second;
        // delete the underlying DNSBL objects.
        delete d;
    }
}


bool CONTEXT::is_parent(CONTEXTP p) {
    if (p == parent) return true;
    if (!parent) return false;
    return parent->is_parent(p);
}


char *CONTEXT::get_full_name(char *buffer, int size) {
    if (!parent) return name;
    char buf[maxlen];
    snprintf(buffer, size, "%s.%s", parent->get_full_name(buf, maxlen), name);
    return buffer;
}


bool CONTEXT::cover_env_to(char *to) {
    char buffer[maxlen];
    char *x = strchr(to, '@');
    if (x) x++;
    else   x = to;
    if (*x == '\0') return true;    // always allow covering addresses with no domain name, eg abuse@
    string_set::iterator i = env_to.find(x);
    if (i != env_to.end()) return true;
    return false;
}


char *CONTEXT::find_from(char *from) {
    char *rc = token_inherit;
    string_map::iterator i = env_from.find(from);
    if (i != env_from.end()) rc = (*i).second;  // found user@domain.tld key
    else {
        char *x = strchr(from, '@');
        if (x) {
            x++;
            i = env_from.find(x);
            if (i != env_from.end()) rc = (*i).second;  // found domain.tld key
            else {
                char y = *x;
                *x = '\0';
                i = env_from.find(from);
                *x = y;
                if (i != env_from.end()) rc = (*i).second;  // found user@ key
            }
        }
    }
    if (rc == token_inherit) rc = env_from_default;
    if ((rc == token_inherit) && parent) return parent->find_from(from);
    return (rc == token_inherit) ? token_unknown : rc;
}


CONTEXTP CONTEXT::find_context(char *from) {
    context_map::iterator i = env_from_context.find(from);
    if (i != env_from_context.end()) return (*i).second;        // found user@domain.tld key
    char *x = strchr(from, '@');
    if (x) {
        x++;
        i = env_from_context.find(x);
        if (i != env_from_context.end()) return (*i).second;    // found domain.tld key
        char y = *x;
        *x = '\0';
        i = env_from_context.find(from);
        *x = y;
        if (i != env_from_context.end()) return (*i).second;              // found user@ key
    }
    return this;
}


CONTEXTP CONTEXT::find_from_context_name(char *name) {
    context_map::iterator i = children.find(name);
    if (i != children.end()) return (*i).second;
    return NULL;
}


DNSBLP CONTEXT::find_dnsbl(char *name) {
    dnsblp_map::iterator i = dnsbl_names.find(name);
    if (i != dnsbl_names.end()) return (*i).second;
    if (parent) return parent->find_dnsbl(name);
    return NULL;
}


char* CONTEXT::get_content_suffix() {
    if (!content_suffix && parent) return parent->get_content_suffix();
    return content_suffix;
}


char* CONTEXT::get_content_message() {
    if (!content_message && parent) return parent->get_content_message();
    return content_message;
}


string_set& CONTEXT::get_content_host_ignore() {
    if (content_host_ignore.empty() && parent) return parent->get_content_host_ignore();
    return content_host_ignore;
}


string_set& CONTEXT::get_content_tlds() {
    if (content_tlds.empty() && parent) return parent->get_content_tlds();
    return content_tlds;
}


string_set& CONTEXT::get_html_tags() {
    if (html_tags.empty() && parent) return parent->get_html_tags();
    return html_tags;
}


dnsblp_list& CONTEXT::get_dnsbl_list() {
    if (dnsbl_list.empty() && parent) return parent->get_dnsbl_list();
    return dnsbl_list;
}


bool CONTEXT::acceptable_content(recorder &memory, char *&msg) {
    if (memory.excessive_bad_tags(tag_limit)) {
        msg = tag_limit_message;
        return false;
    }
    if (!host_random && memory.excessive_hosts(host_limit)) {
        msg = host_limit_message;
        return false;
    }
}


void CONTEXT::dump(int level) {
    char indent[maxlen];
    int i = min(maxlen-1, level*4);
    memset(indent, ' ', i);
    indent[i] = '\0';
    char buf[maxlen];
    char *fullname = get_full_name(buf,maxlen);
    printf("%s context %s { \t// %s\n", indent, name, fullname);

    for (dnsblp_map::iterator i=dnsbl_names.begin(); i!=dnsbl_names.end(); i++) {
        char *n = (*i).first;
        DNSBL &d = *(*i).second;
        printf("%s     dnsbl %s %s \"%s\"; \n", indent, n, d.suffix, d.message);
    }

    if (!dnsbl_list.empty()) {
        printf("%s     dnsbl_list", indent);
        for (dnsblp_list::iterator i=dnsbl_list.begin(); i!=dnsbl_list.end(); i++) {
            DNSBL &d = *(*i);
            printf(" %s", d.name);
        }
        printf("; \n");
    }

    if (content_filtering) {
        printf("%s     content on { \n", indent, env_from_default);
        if (content_suffix) {
            printf("%s         filter %s \"%s\"; \n", indent, content_suffix, content_message);
        }
        if (!content_host_ignore.empty()) {
            printf("%s         ignore { \n", indent);
            for (string_set::iterator i=content_host_ignore.begin(); i!=content_host_ignore.end(); i++) {
                printf("%s             %s; \n", indent, *i);
            }
            printf("%s         }; \n", indent);
        }
        if (!content_tlds.empty()) {
            printf("%s         tld { \n", indent);
            printf("%s             ", indent);
            for (string_set::iterator i=content_tlds.begin(); i!=content_tlds.end(); i++) {
                printf("%s; ", *i);
            }
            printf("\n%s         }; \n", indent);
        }
        if (!html_tags.empty()) {
            printf("%s         html_tags { \n", indent);
            printf("%s             ", indent);
            for (string_set::iterator i=html_tags.begin(); i!=html_tags.end(); i++) {
                printf("%s; ", *i);
            }
            printf("\n%s         }; \n", indent);
        }
        if (host_limit_message) {
            printf("%s         host_limit on %d \"%s\"; \n", indent, host_limit, host_limit_message);
        }
        else if (host_random) {
            printf("%s         host_limit soft %d; \n", indent, host_limit);
        }
        else {
            printf("%s         host_limit off; \n", indent);
        }
        if (tag_limit_message) {
            printf("%s         html_limit on %d \"%s\"; \n", indent, tag_limit, tag_limit_message);
        }
        else {
            printf("%s         html_limit off; \n", indent);
        }
        printf("%s     }; \n", indent);
        }
    else {
        printf("%s     content off {}; \n", indent, env_from_default);
    }

    printf("%s     env_to { \t// %s\n", indent, fullname);
    for (string_set::iterator i=env_to.begin(); i!=env_to.end(); i++) {
        printf("%s         %s; \n", indent, *i);
    }
    printf("%s     }; \n", indent);

    for (context_map::iterator i=children.begin(); i!=children.end(); i++) {
        CONTEXTP c = (*i).second;
        c->dump(level+1);
    }

    printf("%s     env_from %s { \t// %s\n", indent, env_from_default, fullname);
    if (!env_from.empty()) {
        printf("%s     // white/black/unknown \n", indent);
        for (string_map::iterator i=env_from.begin(); i!=env_from.end(); i++) {
            char *f = (*i).first;
            char *t = (*i).second;
            printf("%s         %s \t%s; \n", indent, f, t);
        }
    }
    if (!env_from_context.empty()) {
        printf("%s     // child contexts \n", indent);
        for (context_map::iterator j=env_from_context.begin(); j!=env_from_context.end(); j++) {
            char    *f = (*j).first;
            CONTEXTP t = (*j).second;
            printf("%s         %s \t%s; \n", indent, f, t->name);
        }
    }
    printf("%s     }; \n", indent);

    printf("%s }; \n", indent);
}


////////////////////////////////////////////////
// helper to discard the strings held by a string_set
//
void discard(string_set &s) {
    for (string_set::iterator i=s.begin(); i!=s.end(); i++) {
        free(*i);
    }
    s.clear();
}


////////////////////////////////////////////////
// helper to register a string in a string set
//
char* register_string(string_set &s, char *name) {
    string_set::iterator i = s.find(name);
    if (i != s.end()) return *i;
    char *x = strdup(name);
    s.insert(x);
    return x;
}


////////////////////////////////////////////////
// register a global string
//
char* register_string(char *name) {
    return register_string(all_strings, name);
}


////////////////////////////////////////////////
//
bool tsa(TOKEN &tok, char *token);
bool tsa(TOKEN &tok, char *token) {
    char *have = tok.next();
    if (have == token) return true;
    tok.token_error(token, have);
    return false;
}


////////////////////////////////////////////////
//
bool parse_dnsbl(TOKEN &tok, CONFIG &dc, CONTEXT &me);
bool parse_dnsbl(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
    char *name = tok.next();
    char *suf = tok.next();
    char *msg = tok.next();
    if (!tsa(tok, token_semi)) return false;
    DNSBLP dns = new DNSBL(name, suf, msg);
    me.add_dnsbl(name, dns);
    return true;
}


////////////////////////////////////////////////
//
bool parse_dnsbll(TOKEN &tok, CONFIG &dc, CONTEXT &me);
bool parse_dnsbll(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_semi) break;
        DNSBLP dns = me.find_dnsbl(have);
        if (dns) {
            me.add_dnsbl(dns);
        }
        else {
            tok.token_error("dnsbl name", have);
            return false;
        }
    }
    return true;
}


////////////////////////////////////////////////
//
bool parse_content(TOKEN &tok, CONFIG &dc, CONTEXT &me);
bool parse_content(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
    char *setting = tok.next();
    if (setting == token_on) {
        me.set_content_filtering(true);
    }
    else if (setting == token_off) {
        me.set_content_filtering(false);
    }
    else {
        tok.token_error("on/off", setting);
        return false;
    }
    if (!tsa(tok, token_lbrace)) return false;
    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_filter) {
            char *suffix = tok.next();
            char *messag = tok.next();
            me.set_content_suffix(suffix);
            me.set_content_message(messag);
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_ignore) {
            if (!tsa(tok, token_lbrace)) return false;
            while (true) {
                if (!have) break;
                char *have = tok.next();
                if (have == token_rbrace) break;  // done
                me.add_ignore(have);
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_tld) {
            if (!tsa(tok, token_lbrace)) return false;
            while (true) {
                char *have = tok.next();
                if (!have) break;
                if (have == token_rbrace) break;  // done
                me.add_tld(have);
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_html_limit) {
            have = tok.next();
            if (have == token_on) {
                me.set_tag_limit(tok.nextint());
                me.set_tag_message(tok.next());
            }
            else if (have == token_off) {
                me.set_tag_limit(0);
                me.set_tag_message(NULL);
            }
            else {
                tok.token_error("on/off", have);
                return false;
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_html_tags) {
            if (!tsa(tok, token_lbrace)) return false;
            while (true) {
                char *have = tok.next();
                if (!have) break;
                if (have == token_rbrace) {
                    break;  // done
                }
                else {
                    me.add_tag(have);                           // base version
                    char buf[200];
                    snprintf(buf, sizeof(buf), "/%s", have);
                    me.add_tag(register_string(buf));           // leading /
                    snprintf(buf, sizeof(buf), "%s/", have);
                    me.add_tag(register_string(buf));           // trailing /
                }
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_host_limit) {
            have = tok.next();
            if (have == token_on) {
                me.set_host_limit(tok.nextint());
                me.set_host_message(tok.next());
                me.set_host_random(false);
            }
            else if (have == token_off) {
                me.set_host_limit(0);
                me.set_host_message(NULL);
                me.set_host_random(false);
            }
            else if (have == token_soft) {
                me.set_host_limit(tok.nextint());
                me.set_host_message(NULL);
                me.set_host_random(true);
            }
            else {
                tok.token_error("on/off/soft", have);
                return false;
            }
            if (!tsa(tok, token_semi)) return false;
        }
        else if (have == token_rbrace) {
            break;  // done
        }
        else {
            tok.token_error("content keyword", have);
            return false;
        }
    }
    return tsa(tok, token_semi);
}


////////////////////////////////////////////////
//
bool parse_envto(TOKEN &tok, CONFIG &dc, CONTEXT &me);
bool parse_envto(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
    if (!tsa(tok, token_lbrace)) return false;
    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_rbrace) break;
        if (have == token_semi) {
            // optional separators
        }
        else if (have == token_dccto) {
            char *flavor = tok.next();
            if (!tsa(tok, token_lbrace)) return false;
            bool keeping = false;
            while (true) {
                char *have = tok.next();
                if (!have) break;
                if (have == token_rbrace) break;
                if (have == flavor) {
                    keeping = true;
                    continue;
                }
                else if ((have == token_ok) || (have == token_ok2) || (have == token_many)) {
                    keeping = false;
                    continue;
                }
                if (have == token_envto) {
                    have = tok.next();
                    if (keeping) {
                        if (me.allow_env_to(have)) {
                            me.add_to(have);
                            dc.add_to(have, &me);
                        }
                    }
                }
                else if (have == token_substitute) {
                    if (tok.next() == token_mailhost) {
                        have = tok.next();
                        if (keeping) {
                            if (me.allow_env_to(have)) {
                                me.add_to(have);
                                dc.add_to(have, &me);
                            }
                        }
                    }
                }
                tok.skipeol();
            }
        }
        else if (me.allow_env_to(have)) {
            me.add_to(have);
            dc.add_to(have, &me);
        }
        else {
            tok.token_error("user@ or user@domain.tld or domain.tld where domain.tld allowed by parent context", have);
            return false;
        }
    }
    return tsa(tok, token_semi);
}


////////////////////////////////////////////////
//
bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me);
bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
    char *st = tok.next();
    if ((st == token_black) || (st == token_white) || (st == token_unknown) || (st == token_inherit)) {
        me.set_from_default(st);
    }
    else {
        tok.push(st);
    }
    if (!tsa(tok, token_lbrace)) return false;
    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_rbrace) break;
        if (have == token_semi) {
            // optional separators
        }
        else if (have == token_dccfrom) {
            if (!tsa(tok, token_lbrace)) return false;
            bool keeping = false;
            bool many = false;
            while (true) {
                char *have = tok.next();
                if (!have) break;
                if (have == token_rbrace) break;
                if (have == token_ok) {
                    keeping = true;
                    many    = false;
                    continue;
                }
                else if (have == token_many) {
                    keeping = true;
                    many    = true;
                    continue;
                }
                else if (have == token_ok2) {
                    keeping = false;
                    continue;
                }
                if (have == token_envfrom) {
                    have = tok.next();
                    if (keeping) {
                        me.add_from(have, (many) ? token_black : token_white);
                    }
                }
                else if (have == token_substitute) {
                    if (tok.next() == token_mailhost) {
                        have = tok.next();
                        me.add_from(have, (many) ? token_black : token_white);
                    }
                }
                tok.skipeol();
            }
        }
        else {
            // may be a valid email address or domain name
            char *st = tok.next();
            if ((st == token_black) || (st == token_white) || (st == token_unknown)) {
                me.add_from(have, st);
            }
            else {
                CONTEXTP con = me.find_from_context_name(st);
                if (con) {
                    me.add_from_context(have, con);
                }
                else {
                    tok.token_error("white/black/unknown or child context name", st);
                    return false;
                }
            }
        }
    }
    return tsa(tok, token_semi);
}


////////////////////////////////////////////////
//
bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent);
bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent) {
    char *name = tok.next();
    if (!tsa(tok, token_lbrace)) return false;
    CONTEXTP con = new CONTEXT(parent, name);

    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_rbrace) break;  // done
        if (have == token_dnsbl) {
            if (!parse_dnsbl(tok, dc, *con)) return false;
        }
        else if (have == token_dnsbll) {
            if (!parse_dnsbll(tok, dc, *con)) return false;
        }
        else if (have == token_content) {
            if (!parse_content(tok, dc, *con)) return false;
        }
        else if (have == token_envto) {
            if (!parse_envto(tok, dc, *con)) return false;
        }
        else if (have == token_envfrom) {
            if (!parse_envfrom(tok, dc, *con)) return false;
        }
        else if (have == token_context) {
            if (!parse_context(tok, dc, con)) return false;
        }
        else {
            tok.token_error("context keyword", have);
            return false;
        }
    }

    if (!tsa(tok, token_semi)) {
        delete con;
        return false;
    }
    dc.add_context(con);
    if (parent) parent->add_context(con);
    return true;
}


////////////////////////////////////////////////
// parse a config file
//
bool load_conf(CONFIG &dc, char *fn) {
    TOKEN tok(fn, &dc.config_files);
    while (true) {
        char *have = tok.next();
        if (!have) break;
        if (have == token_context) {
            if (!parse_context(tok, dc, NULL)) {
                return false;
            }
        }
        else {
            tok.token_error(token_context, have);
            return false;
        }
    }
    return (dc.default_context) ? true : false;
}


////////////////////////////////////////////////
// init the tokens
//
void token_init() {
    token_black      = register_string("black");
    token_content    = register_string("content");
    token_context    = register_string("context");
    token_dccfrom    = register_string("dcc_from");
    token_dccto      = register_string("dcc_to");
    token_default    = register_string("default");
    token_dnsbl      = register_string("dnsbl");
    token_dnsbll     = register_string("dnsbl_list");
    token_envfrom    = register_string("env_from");
    token_envto      = register_string("env_to");
    token_filter     = register_string("filter");
    token_host_limit = register_string("host_limit");
    token_html_limit = register_string("html_limit");
    token_html_tags  = register_string("html_tags");
    token_ignore     = register_string("ignore");
    token_include    = register_string("include");
    token_inherit    = register_string("inherit");
    token_lbrace     = register_string("{");
    token_mailhost   = register_string("mail_host");
    token_many       = register_string("many");
    token_off        = register_string("off");
    token_ok         = register_string("ok");
    token_ok2        = register_string("ok2");
    token_on         = register_string("on");
    token_rbrace     = register_string("}");
    token_semi       = register_string(";");
    token_soft       = register_string("soft");
    token_substitute = register_string("substitute");
    token_tld        = register_string("tld");
    token_unknown    = register_string("unknown");
    token_white      = register_string("white");
}