changeset 51:206448c00b55 stable-1-0-12

Allow multiple contexts with independent add/remove commands.
author Carl Byington <carl@five-ten-sg.com>
date Sat, 24 Jan 2009 15:52:20 -0800
parents 75361069c6ef
children fcbc7b7efd14
files ChangeLog NEWS configure.in src/syslogconfig.cpp src/syslogconfig.h syslog2iptables.conf syslog2iptables.spec.in
diffstat 7 files changed, 407 insertions(+), 234 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Dec 24 18:40:54 2008 -0800
+++ b/ChangeLog	Sat Jan 24 15:52:20 2009 -0800
@@ -1,3 +1,6 @@
+1.12 2009-01-24
+     Allow multiple contexts with independent add/remove commands.
+
 1.11 2008-05-29
      Fixes to compile on Fedora 9 and for const correctness.
 
--- a/NEWS	Wed Dec 24 18:40:54 2008 -0800
+++ b/NEWS	Sat Jan 24 15:52:20 2009 -0800
@@ -1,3 +1,4 @@
+1.12 2009-01-24 Allow multiple contexts with independent add/remove commands.
 1.11 2008-05-29 Fixes to compile on Fedora 9 and for const correctness.
 1.10 2008-03-22 Add fixes for Solaris from sm-archive.
 1.9  2008-03-21 Add default config for bounce floods; fedora packaging
--- a/configure.in	Wed Dec 24 18:40:54 2008 -0800
+++ b/configure.in	Sat Jan 24 15:52:20 2009 -0800
@@ -1,6 +1,6 @@
 
 AC_PREREQ(2.59)
-AC_INIT(syslog2iptables,1.11,carl@five-ten-sg.com)
+AC_INIT(syslog2iptables,1.12,carl@five-ten-sg.com)
 AC_CONFIG_SRCDIR([config.h.in])
 AC_CONFIG_HEADER([config.h])
 
--- a/src/syslogconfig.cpp	Wed Dec 24 18:40:54 2008 -0800
+++ b/src/syslogconfig.cpp	Sat Jan 24 15:52:20 2009 -0800
@@ -16,6 +16,7 @@
 
 const char *token_add;
 const char *token_bucket;
+const char *token_context;
 const char *token_file;
 const char *token_ignore;
 const char *token_include;
@@ -28,40 +29,38 @@
 const char *token_semi;
 const char *token_slash;
 const char *token_threshold;
-
-struct ltint
-{
-  bool operator()(const int s1, const int s2) const
-  {
-    return (unsigned)s1 < (unsigned)s2;
-  }
-};
-
-struct bucket {
-    int  count;
-    bool latch; // true iff ever count>threshold
-};
-
 string_set  all_strings;    // owns all the strings, only modified by the config loader thread
+recorder_map    recorders;  // all the recorders are named
 const int maxlen = 1000;    // used for snprintf buffers
-typedef map<int, bucket, ltint>   ip_buckets;
-
-class IPR {
-    ip_buckets  violations;
-public:
-    void add(int ip, int amount, CONFIG &con, const char *file_name, int pattern_index, const char *message);
-    void leak(int amount, CONFIG &con);
-    void free_all(CONFIG &con);
-    void update(int ip, bool added, const char *file_name, int pattern_index, const char *message);
-    void changed(CONFIG &con, int ip, bool added);
-};
-
-IPR recorder;
 
 
 ////////////////////////////////////////////////
 //
-void IPR::add(int ip, int amount, CONFIG &con, const char *file_name, int pattern_index, const char *message) {
+
+IPR::IPR() {
+    reference_count = 0;
+}
+
+IPR* IPR::find(const char* name) {
+    recorder_map::iterator m = recorders.find(name);
+    if (m == recorders.end()) recorders[name] = new IPR;
+    recorders[name]->reference(1);
+    return recorders[name];
+}
+
+
+void IPR::release(const char* name) {
+    recorder_map::iterator m = recorders.find(name);
+    IPR* i = (*m).second;
+    int r = i->reference(-1);
+    if (r == 0) {
+        delete i;
+        recorders.erase(m);
+    }
+}
+
+
+void IPR::add(int ip, int amount, CONTEXT &con, const char *file_name, int pattern_index, const char *message) {
     if (con.looking(ip)) {
         ip_buckets::iterator i = violations.find(ip);
         if (i == violations.end()) {
@@ -91,7 +90,7 @@
 }
 
 
-void IPR::leak(int amount, CONFIG &con) {
+void IPR::leak(int amount, CONTEXT &con) {
     for (ip_buckets::iterator i=violations.begin(); i!=violations.end(); ) {
         int    ip = (*i).first;
         bucket &b = (*i).second;
@@ -110,7 +109,7 @@
 }
 
 
-void IPR::free_all(CONFIG &con) {
+void IPR::free_all(CONTEXT &con) {
     if (debug_syslog > 2) {
         my_syslog("syslog2iptables shutting down");
     }
@@ -141,7 +140,7 @@
 }
 
 
-void IPR::changed(CONFIG &con, int ip, bool added) {
+void IPR::changed(CONTEXT &con, int ip, bool added) {
     int t = con.get_threshold();
     char buf[maxlen];
     if (added) {
@@ -203,7 +202,7 @@
 }
 
 
-bool PATTERN::process(char *buf, CONFIG &con, const char *file_name, int pattern_index) {
+bool PATTERN::process(char *buf, CONTEXT &con, const char *file_name, int pattern_index) {
     if (pattern) {
         const int nmatch = index+1;
         regmatch_t match[nmatch];
@@ -217,7 +216,7 @@
                 buf[e] = '\0';
                 int ip = ip_address(buf+s);
                 if (ip) {
-                    recorder.add(ip, amount, con, file_name, pattern_index, message);
+                    con.recorder->add(ip, amount, con, file_name, pattern_index, message);
                 }
                 return true;
             }
@@ -242,58 +241,65 @@
 
 ////////////////////////////////////////////////
 //
-CONFIG::CONFIG() {
-    reference_count    = 0;
-    generation         = 0;
-    load_time          = 0;
+CONTEXT::CONTEXT(const char *nam) {
+    name               = nam;
     threshold          = 500;
     add_command        = "/sbin/iptables -I INPUT --src %s --jump DROP";
     remove_command     = "/sbin/iptables -D INPUT --src %s --jump DROP";
+    recorder = IPR::find(name);
 }
 
 
-CONFIG::~CONFIG() {
+////////////////////////////////////////////////
+//
+CONTEXT::~CONTEXT() {
+    ignore.clear();
     for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) {
         SYSLOGCONFIG *c = *i;
         delete c;
     }
-    ignore.clear();
+    IPR::release(name);
 }
 
 
-void CONFIG::add_syslogconfig(SYSLOGCONFIGP con) {
+void CONTEXT::add_syslogconfig(SYSLOGCONFIGP con) {
     syslogconfigs.push_back(con);
 }
 
 
-void CONFIG::add_pair(IPPAIR pair) {
+void CONTEXT::add_pair(IPPAIR pair) {
     ignore.push_back(pair);
 }
 
 
-void CONFIG::dump() {
-    printf(" threshold %d; \n\n", threshold);
+void CONTEXT::dump() {
+    string indents("    ");
+    const char *indent = indents.c_str();
 
-    printf(" add_command \"%s\"; \n",      add_command);
-    printf(" remove_command \"%s\"; \n\n", remove_command);
+    printf("context %s {\n", name);
+    printf("%s threshold %d; \n\n", indent, threshold);
 
-    printf(" ignore { \n");
+    printf("%s add_command \"%s\"; \n",      indent, add_command);
+    printf("%s remove_command \"%s\"; \n\n", indent, remove_command);
+
+    printf("%s ignore { \n", indent);
     for (ippair_list::iterator i=ignore.begin(); i!=ignore.end(); i++) {
         IPPAIR &p = *i;
         in_addr ip;
         ip.s_addr = htonl(p.first);
-        printf("     %s/%d; \n", inet_ntoa(ip), p.cidr);
+        printf("%s     %s/%d; \n", indent, inet_ntoa(ip), p.cidr);
     }
-    printf(" }; \n\n");
+    printf("%s }; \n\n", indent);
 
     for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) {
         SYSLOGCONFIGP c = *i;
-        c->dump(0);
+        c->dump(1);
     }
+    printf("}; \n\n");
 }
 
 
-void CONFIG::read() {
+void CONTEXT::read(CONFIG &con) {
     while (true) {
         bool have = false;
         for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) {
@@ -305,19 +311,18 @@
 }
 
 
-void CONFIG::sleep(int duration, time_t &previous) {
-    ::sleep(duration);
-    time_t now = time(NULL);
-    recorder.leak(now-previous, *this);
-    previous = now;
+void CONTEXT::free_all() {
+    recorder->free_all(*this);
 }
 
 
-void CONFIG::free_all() {
-    recorder.free_all(*this);
+void CONTEXT::leak(int delta) {
+    recorder->leak(delta, *this);
+
 }
 
-bool CONFIG::looking(int ip) {
+
+bool CONTEXT::looking(int ip) {
     for (ippair_list::iterator i=ignore.begin(); i!=ignore.end(); i++) {
         IPPAIR &p = *i;
         if ((p.first <= ip) && (ip <= p.last)) return false;
@@ -327,6 +332,58 @@
 
 ////////////////////////////////////////////////
 //
+CONFIG::CONFIG() {
+    reference_count    = 0;
+    generation         = 0;
+    load_time          = 0;
+}
+
+
+CONFIG::~CONFIG() {
+    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
+        CONTEXT *c = *i;
+        delete c;
+    }
+}
+
+
+void CONFIG::dump() {
+    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
+        CONTEXTP c = *i;
+        c->dump();
+    }
+}
+
+
+void CONFIG::read() {
+    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
+        CONTEXT *c = *i;
+        c->read(*this);
+    }
+}
+
+
+void CONFIG::sleep(int duration, time_t &previous) {
+    ::sleep(duration);
+    time_t now = time(NULL);
+    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
+        CONTEXT *c = *i;
+        c->leak(now-previous);
+    }
+    previous = now;
+}
+
+
+void CONFIG::free_all() {
+    for (context_list::iterator i=contexts.begin(); i!=contexts.end(); i++) {
+        CONTEXT *c = *i;
+        c->free_all();
+    }
+}
+
+
+////////////////////////////////////////////////
+//
 SYSLOGCONFIG::SYSLOGCONFIG(TOKEN &tok, const char *file_name_) {
     tokp      = &tok;
     file_name = file_name_;
@@ -374,7 +431,7 @@
 }
 
 
-bool SYSLOGCONFIG::read(CONFIG &con) {
+bool SYSLOGCONFIG::read(CONTEXT &con) {
     if (failed()) {
         open(false);
         if (failed()) return false;
@@ -428,7 +485,7 @@
 }
 
 
-void SYSLOGCONFIG::process(CONFIG &con) {
+void SYSLOGCONFIG::process(CONTEXT &con) {
     int pi=0;
     for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) {
         PATTERN *p = *i;
@@ -504,8 +561,8 @@
 
 ////////////////////////////////////////////////
 //
-bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con);
-bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con) {
+bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con, CONTEXT &me);
+bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con, CONTEXT &me) {
     const char *pat = tok.next();
     int  ind = 0;
     int  buc = 0;
@@ -543,8 +600,8 @@
 
 ////////////////////////////////////////////////
 //
-bool parse_ignore(TOKEN &tok, CONFIG &dc);
-bool parse_ignore(TOKEN &tok, CONFIG &dc) {
+bool parse_ignore(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_ignore(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
     if (!tsa(tok, token_lbrace)) return false;
     while (true) {
         const char *have = tok.next();
@@ -600,7 +657,36 @@
         pair.first = ipaddr;
         pair.last  = ipaddr | masks[mask];
         pair.cidr  = mask;
-        dc.add_pair(pair);
+        me.add_pair(pair);
+    }
+    if (!tsa(tok, token_semi)) return false;
+    return true;
+}
+
+
+////////////////////////////////////////////////
+//
+bool parse_syslogconfig(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_syslogconfig(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+    const char *name = tok.next();
+    if (!tsa(tok, token_lbrace)) return false;
+    SYSLOGCONFIGP con = new SYSLOGCONFIG(tok, name);
+    if (con->failed()) {
+        delete con;
+        return false;
+    }
+    me.add_syslogconfig(con);
+    while (true) {
+        const char *have = tok.next();
+        if (!have) break;
+        if (have == token_rbrace) break;
+        if (have == token_pattern) {
+            if (!parse_pattern(tok, *con, me)) return false;
+        }
+        else {
+            tok.token_error("pattern", have);
+            return false;
+        }
     }
     if (!tsa(tok, token_semi)) return false;
     return true;
@@ -609,29 +695,47 @@
 
 ////////////////////////////////////////////////
 //
-bool parse_syslogconfig(TOKEN &tok, CONFIG &dc);
-bool parse_syslogconfig(TOKEN &tok, CONFIG &dc) {
+bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent);
+bool parse_context(TOKEN &tok, CONFIG &dc, CONTEXTP parent) {
     const char *name = tok.next();
     if (!tsa(tok, token_lbrace)) return false;
-    SYSLOGCONFIGP con = new SYSLOGCONFIG(tok, name);
-    if (con->failed()) {
+    CONTEXTP con = new CONTEXT(name);
+
+    while (true) {
+        const char *have = tok.next();
+        if (!have) break;
+        if (have == token_rbrace) break;  // done
+        if (have == token_threshold) {
+            have = tok.next();
+            con->set_threshold(atoi(have));
+            if (!tsa(tok, token_semi)) return false;
+        }
+        else if (have == token_ignore) {
+            if (!parse_ignore(tok, dc, *con)) return false;
+        }
+        else if (have == token_add) {
+            have = tok.next();
+            con->set_add(have);
+            if (!tsa(tok, token_semi)) return false;
+        }
+        else if (have == token_remove) {
+            have = tok.next();
+            con->set_remove(have);
+            if (!tsa(tok, token_semi)) return false;
+        }
+        else if (have == token_file) {
+            if (!parse_syslogconfig(tok, dc, *con)) return false;
+        }
+        else {
+            tok.token_error("threshold/ignore/add_command/remove_command/file", have);
+            return false;
+        }
+    }
+    if (!tsa(tok, token_semi)) {
         delete con;
         return false;
     }
-    dc.add_syslogconfig(con);
-    while (true) {
-        const char *have = tok.next();
-        if (!have) break;
-        if (have == token_rbrace) break;
-        if (have == token_pattern) {
-            if (!parse_pattern(tok, *con)) return false;
-        }
-        else {
-            tok.token_error("pattern", have);
-            return false;
-        }
-    }
-    if (!tsa(tok, token_semi)) return false;
+    dc.add_context(con);
     return true;
 }
 
@@ -645,35 +749,20 @@
     while (true) {
         const char *have = tok.next();
         if (!have) break;
-        if (have == token_threshold) {
-            have = tok.next();
-            dc.set_threshold(atoi(have));
-            if (!tsa(tok, token_semi)) return false;
-        }
-        else if (have == token_ignore) {
-            if (!parse_ignore(tok, dc)) return false;
+        if (have == token_context) {
+            if (!parse_context(tok, dc, NULL)) {
+                tok.token_error("load_conf() failed to parse context");
+                return false;
         }
-        else if (have == token_add) {
-            have = tok.next();
-            dc.set_add(have);
-            if (!tsa(tok, token_semi)) return false;
-        }
-        else if (have == token_remove) {
-            have = tok.next();
-            dc.set_remove(have);
-            if (!tsa(tok, token_semi)) return false;
-        }
-        else if (have == token_file) {
-            if (!parse_syslogconfig(tok, dc)) return false;
-            count++;
+            else count++;
         }
         else {
-            tok.token_error("threshold/ignore/add_command/remove_command/file", have);
+            tok.token_error(token_context, have);
             return false;
         }
     }
-    tok.token_error("load_conf() found %d syslog files in %s", count, fn);
-    return (!dc.syslogconfigs.empty());
+    tok.token_error("load_conf() found %d contexts in %s", count, fn);
+    return (!dc.contexts.empty());
 }
 
 
@@ -683,6 +772,7 @@
 void token_init() {
     token_add        = register_string("add_command");
     token_bucket     = register_string("bucket");
+    token_context    = register_string("context");
     token_file       = register_string("file");
     token_ignore     = register_string("ignore");
     token_include    = register_string("include");
--- a/src/syslogconfig.h	Wed Dec 24 18:40:54 2008 -0800
+++ b/src/syslogconfig.h	Sat Jan 24 15:52:20 2009 -0800
@@ -8,6 +8,7 @@
 
 
 class SYSLOGCONFIG;
+class CONTEXT;
 class CONFIG;
 
 struct IPPAIR {
@@ -25,12 +26,46 @@
 public:
     ~PATTERN();
     PATTERN(TOKEN &tok, const char *pattern_, int index_, int amount_, const char *msg_);
-    bool    process(char *buf, CONFIG &con, const char *file_name, int pattern_index);
+    bool    process(char *buf, CONTEXT &con, const char *file_name, int pattern_index);
     void    dump(int level);
 };
 
+struct ltint
+{
+  bool operator()(const int s1, const int s2) const
+  {
+    return (unsigned)s1 < (unsigned)s2;
+  }
+};
+
+struct bucket {
+    int  count;
+    bool latch; // true iff ever count>threshold
+};
+
+typedef map<int, bucket, ltint>   ip_buckets;
+
+class IPR {
+    int         reference_count;    // number of contexts using this recorder
+    ip_buckets  violations;
+public:
+    IPR();
+    int  reference(int delta)   {reference_count += delta; return reference_count;};
+    void add(int ip, int amount, CONTEXT &con, const char *file_name, int pattern_index, const char *message);
+    void leak(int amount, CONTEXT &con);
+    void free_all(CONTEXT &con);
+    void update(int ip, bool added, const char *file_name, int pattern_index, const char *message);
+    void changed(CONTEXT &con, int ip, bool added);
+    static IPR* find(const char* name);
+    static void release(const char* name);
+};
+
+
 typedef SYSLOGCONFIG *          SYSLOGCONFIGP;
 typedef PATTERN *               PATTERNP;
+typedef CONTEXT *               CONTEXTP;
+typedef map<const char *, IPR*> recorder_map;
+typedef list<CONTEXTP>          context_list;
 typedef list<SYSLOGCONFIGP>     syslogconfig_list;
 typedef list<IPPAIR>            ippair_list;
 typedef list<PATTERNP>          pattern_list;
@@ -49,13 +84,40 @@
     ~SYSLOGCONFIG();
     bool    failed()    { return (fd == -1); };
     void    open(bool msg);
-    bool    read(CONFIG &con);
+    bool    read(CONTEXT &con);
     void    close();
     void    add_pattern(PATTERNP pat);
-    void    process(CONFIG &con);
+    void    process(CONTEXT &con);
     void    dump(int level);
 };
 
+
+class CONTEXT {
+public:
+    const char *        name;               // name of this context
+    int                 threshold;
+    ippair_list         ignore;             // owns all the ippairs
+    const char *        add_command;        // owned by the string table
+    const char *        remove_command;     // ""
+    IPR *               recorder;           // used to record violations
+    syslogconfig_list   syslogconfigs;      // owns all the syslogconfigs
+
+    CONTEXT(const char *nam);
+    ~CONTEXT();
+    void    set_add(const char *add)        { add_command    = add;        };
+    void    set_remove(const char *remove)  { remove_command = remove;     };
+    void    set_threshold(int threshold_)   { threshold      = threshold_; };
+    int     get_threshold()                 { return threshold;            };
+    void    add_syslogconfig(SYSLOGCONFIGP con);
+    void    add_pair(IPPAIR pair);
+    void    dump();
+    void    read(CONFIG &con);
+    void    free_all();
+    void    leak(int delta);
+    bool    looking(int ip);
+};
+
+
 class CONFIG {
 public:
     // the only mutable stuff once it has been loaded from the config file
@@ -64,25 +126,15 @@
     int                 generation;
     time_t              load_time;
     string_set          config_files;
-    int                 threshold;
-    ippair_list         ignore;             // owns all the ippairs
-    const char *        add_command;        // owned by the string table
-    const char *        remove_command;     // ""
-    syslogconfig_list   syslogconfigs;      // owns all the syslogconfigs
+    context_list        contexts;
 
     CONFIG();
     ~CONFIG();
-    void    set_add(const char *add)        { add_command    = add;        };
-    void    set_remove(const char *remove)  { remove_command = remove;     };
-    void    set_threshold(int threshold_)   { threshold      = threshold_; };
-    int     get_threshold()                 { return threshold;            };
-    void    add_syslogconfig(SYSLOGCONFIGP con);
-    void    add_pair(IPPAIR pair);
+    void    add_context(CONTEXTP con)  {contexts.push_back(con);} ;
     void    dump();
     void    read();
     void    sleep(int duration, time_t &previous);
     void    free_all();
-    bool    looking(int ip);
 };
 
 void        discard(string_set &s);
@@ -95,6 +147,7 @@
 
 extern const char *token_add;
 extern const char *token_bucket;
+extern const char *token_context;
 extern const char *token_file;
 extern const char *token_ignore;
 extern const char *token_include;
--- a/syslog2iptables.conf	Wed Dec 24 18:40:54 2008 -0800
+++ b/syslog2iptables.conf	Sat Jan 24 15:52:20 2009 -0800
@@ -1,3 +1,24 @@
+context dns {
+    threshold 1100;
+
+    add_command    "/sbin/iptables -I INPUT --protocol udp --destination-port 53 --src %s --jump DROP";
+    remove_command "/sbin/iptables -D INPUT --protocol udp --destination-port 53 --src %s --jump DROP";
+
+    ignore {
+        127.0.0.0/8;        // localhost
+    };
+
+    file "/var/log/messages" {
+        pattern "named.*client (.*)#.*query.*cache.*denied" {
+            index 1;    // zero based
+            bucket 400;
+            message "DNS attack";
+        };
+    };
+};
+
+
+context general {
 threshold 550;
 
 add_command    "/sbin/iptables -I INPUT --src %s --jump DROP";
@@ -121,3 +142,5 @@
 //          message "ssh failed password";
 //      };
 //  };
+};
+
--- a/syslog2iptables.spec.in	Wed Dec 24 18:40:54 2008 -0800
+++ b/syslog2iptables.spec.in	Sat Jan 24 15:52:20 2009 -0800
@@ -69,6 +69,9 @@
 
 
 %changelog
+* Sat Jan 24 2009 Carl Byington <carl@five-ten-sg.com> - 1.12-1
+- Allow multiple contexts with independent add/remove commands.
+
 * Thu May 29 2008 Carl Byington <carl@five-ten-sg.com> - 1.11-1
 - Fix to compile on Fedora 9 and for const correctness.