Mercurial > syslog2iptables
view src/syslogconfig.cpp @ 46:4eaee1fcffa7 stable-1-10
more fixes for Solaris
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Sat, 22 Mar 2008 11:32:28 -0700 |
parents | d9ae11033b4b |
children | ba0259c9e411 |
line wrap: on
line source
/* Copyright (c) 2007 Carl Byington - 510 Software Group, released under the GPL version 3 or any later version at your choice available at http://www.gnu.org/licenses/gpl-3.0.txt */ #include "includes.h" #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <limits.h> char *token_add; char *token_bucket; char *token_file; char *token_ignore; char *token_include; char *token_index; char *token_lbrace; char *token_message; char *token_pattern; char *token_rbrace; char *token_remove; char *token_semi; char *token_slash; 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 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, char *file_name, int pattern_index, char *message); void leak(int amount, CONFIG &con); void free_all(CONFIG &con); void update(int ip, bool added, char *file_name, int pattern_index, char *message); void changed(CONFIG &con, int ip, bool added); }; IPR recorder; //////////////////////////////////////////////// // void IPR::add(int ip, int amount, CONFIG &con, char *file_name, int pattern_index, char *message) { if (con.looking(ip)) { ip_buckets::iterator i = violations.find(ip); if (i == violations.end()) { bucket b; b.count = amount; b.latch = (con.get_threshold() <= b.count); violations[ip] = b; if (b.latch) { update(ip, true, file_name, pattern_index, message); changed(con, ip, true); } } else { bucket &b = (*i).second; if (b.count < (INT_MAX-amount)) { int t = con.get_threshold(); int c = b.count; b.count += amount; if ((!b.latch) && (c < t) && (t <= b.count)) { b.latch = true; update(ip, true, file_name, pattern_index, message); changed(con, ip, true); } } } } } void IPR::leak(int amount, CONFIG &con) { for (ip_buckets::iterator i=violations.begin(); i!=violations.end(); ) { int ip = (*i).first; bucket &b = (*i).second; if (b.count <= amount) { if (b.latch) { update(ip, false, NULL, 0, NULL); changed(con, ip, false); } violations.erase(i++); } else { b.count -= amount; i++; } } } void IPR::free_all(CONFIG &con) { if (debug_syslog > 2) { my_syslog("syslog2iptables shutting down"); } for (ip_buckets::iterator i=violations.begin(); i!=violations.end(); i++) { int ip = (*i).first; bucket &b = (*i).second; if (b.latch) { update(ip, false, NULL, 0, NULL); changed(con, ip, false); } } violations.clear(); } void IPR::update(int ip, bool added, char *file_name, int pattern_index, char *message) { if (debug_syslog > 2) { char buf[maxlen]; in_addr ad; ad.s_addr = htonl(ip); if (added) { if (message) snprintf(buf, maxlen, "dropping traffic from/to %s based on %s in %s", inet_ntoa(ad), message, file_name); else snprintf(buf, maxlen, "dropping traffic from/to %s based on pattern match %d in %s", inet_ntoa(ad), pattern_index, file_name); } else snprintf(buf, maxlen, "allowing traffic from/to %s", inet_ntoa(ad)); my_syslog(buf); } } void IPR::changed(CONFIG &con, int ip, bool added) { int t = con.get_threshold(); char buf[maxlen]; if (added) { bucket &b = violations[ip]; if (con.looking(ip) && (b.count > t)) { in_addr ad; ad.s_addr = htonl(ip); snprintf(buf, maxlen, con.add_command, inet_ntoa(ad)); system(buf); } } else { in_addr ad; ad.s_addr = htonl(ip); snprintf(buf, maxlen, con.remove_command, inet_ntoa(ad)); system(buf); } } //////////////////////////////////////////////// // int ip_address(char *have); int ip_address(char *have) { int ipaddr = 0; in_addr ip; if (inet_aton(have, &ip)) ipaddr = ip.s_addr; else { struct hostent *host = gethostbyname(have); if (host && host->h_addrtype == AF_INET) memcpy(&ipaddr, host->h_addr, sizeof(ipaddr)); } return ntohl(ipaddr); } //////////////////////////////////////////////// // PATTERN::PATTERN(TOKEN &tok, char *pattern_, int index_, int amount_, char *msg_) { pattern = pattern_; index = index_; amount = amount_; message = msg_; if (pattern) { int rc = regcomp(&re, pattern, REG_ICASE | REG_EXTENDED); if (rc) { char bu[maxlen]; regerror(rc, &re, bu, maxlen); char buf[maxlen]; snprintf(buf, sizeof(buf), "pattern %s not valid - %s", pattern, bu); tok.token_error(buf); pattern = NULL; } } } PATTERN::~PATTERN() { regfree(&re); } bool PATTERN::process(char *buf, CONFIG &con, char *file_name, int pattern_index) { if (pattern) { const int nmatch = index+1; regmatch_t match[nmatch]; if (0 == regexec(&re, buf, nmatch, match, 0)) { int s = match[index].rm_so; int e = match[index].rm_eo; if (s != -1) { if (debug_syslog > 3) { my_syslog(buf); // show lines with matches } buf[e] = '\0'; int ip = ip_address(buf+s); if (ip) { recorder.add(ip, amount, con, file_name, pattern_index, message); } return true; } } } return false; } void PATTERN::dump(int level) { char indent[maxlen]; int i = min(maxlen-1, level*4); memset(indent, ' ', i); indent[i] = '\0'; printf("%s pattern \"%s\" {; \n", indent, pattern); printf("%s index %d; \n", indent, index); printf("%s bucket %d; \n", indent, amount); if (message) printf("%s message \"%s\"; \n", indent, message); printf("%s }; \n", indent); } //////////////////////////////////////////////// // CONFIG::CONFIG() { reference_count = 0; generation = 0; load_time = 0; threshold = 500; add_command = "/sbin/iptables -I INPUT --src %s --jump DROP"; remove_command = "/sbin/iptables -D INPUT --src %s --jump DROP"; } CONFIG::~CONFIG() { for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) { SYSLOGCONFIG *c = *i; delete c; } ignore.clear(); } void CONFIG::add_syslogconfig(SYSLOGCONFIGP con) { syslogconfigs.push_back(con); } void CONFIG::add_pair(IPPAIR pair) { ignore.push_back(pair); } void CONFIG::dump() { printf(" threshold %d; \n\n", threshold); printf(" add_command \"%s\"; \n", add_command); printf(" remove_command \"%s\"; \n\n", remove_command); printf(" ignore { \n"); 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(" }; \n\n"); for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) { SYSLOGCONFIGP c = *i; c->dump(0); } } void CONFIG::read() { while (true) { bool have = false; for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) { SYSLOGCONFIGP c = *i; have |= c->read(*this); } if (!have) break; } } void CONFIG::sleep(int duration, time_t &previous) { ::sleep(duration); time_t now = time(NULL); recorder.leak(now-previous, *this); previous = now; } void CONFIG::free_all() { recorder.free_all(*this); } bool CONFIG::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; } return true; } //////////////////////////////////////////////// // SYSLOGCONFIG::SYSLOGCONFIG(TOKEN &tok, char *file_name_) { tokp = &tok; file_name = file_name_; open(true); } SYSLOGCONFIG::~SYSLOGCONFIG() { close(); for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) { PATTERN *p = *i; delete p; } } void SYSLOGCONFIG::open(bool msg) { fd = ::open(file_name, O_RDONLY); len = 0; if (fd == -1) { if (msg) { char buf[maxlen]; snprintf(buf, sizeof(buf), "syslog file %s not readable", file_name); tokp->token_error(buf); } } else { if (debug_syslog > 1) { snprintf(buf, sizeof(buf), "syslog file %s opened", file_name); my_syslog(buf); } lseek(fd, 0, SEEK_END); if (fstat(fd, &openfdstat)) { close(); snprintf(buf, sizeof(buf), "syslog file %s cannot stat after open", file_name); tokp->token_error(buf); } // specify that this fd gets closed on exec, so that selinux // won't complain about iptables trying to read log files. int oldflags = fcntl(fd, F_GETFD, 0); if (oldflags >= 0) { fcntl(fd, F_SETFD, oldflags | FD_CLOEXEC); } } } bool SYSLOGCONFIG::read(CONFIG &con) { if (failed()) { open(false); if (failed()) return false; } int n = ::read(fd, buf+len, buflen-len); bool have = (n > 0); if (have) { len += n; while (true) { char *p = (char*)memchr(buf, '\n', len); if (!p) break; n = p-buf; *p = '\0'; process(con); // process null terminated string len -= n+1; memmove(buf, p+1, len); } // no <lf> in a full buffer if (len == buflen) len = 0; } else { // check for file close struct stat filenamest; if (0 == stat(file_name, &filenamest)) { if ((filenamest.st_dev != openfdstat.st_dev) || (filenamest.st_ino != openfdstat.st_ino)) { close(); } } else { // filename no longer exists close(); } } return have; } void SYSLOGCONFIG::close() { if (debug_syslog > 1) { snprintf(buf, sizeof(buf), "syslog file %s closed", file_name); my_syslog(buf); } if (fd != -1) ::close(fd); fd = -1; } void SYSLOGCONFIG::add_pattern(PATTERNP pat) { patterns.push_back(pat); } void SYSLOGCONFIG::process(CONFIG &con) { int pi=0; for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) { PATTERN *p = *i; if (p->process(buf, con, file_name, pi)) break; pi++; } } void SYSLOGCONFIG::dump(int level) { char indent[maxlen]; int i = min(maxlen-1, level*4); memset(indent, ' ', i); indent[i] = '\0'; char buf[maxlen]; printf("%s file \"%s\" {\n", indent, file_name); for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) { PATTERN *p = *i; p->dump(level+1); } 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); } //////////////////////////////////////////////// // clear all global strings, helper for valgrind checking // void clear_strings() { discard(all_strings); } //////////////////////////////////////////////// // 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_pattern(TOKEN &tok, SYSLOGCONFIG &con); bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con) { char *pat = tok.next(); int ind, buc; char *msg = NULL; if (!tsa(tok, token_lbrace)) return false; while (true) { char *have = tok.next(); if (!have) break; if (have == token_rbrace) break; if (have == token_index) { have = tok.next(); ind = atoi(have); if (!tsa(tok, token_semi)) return false; } else if (have == token_bucket) { have = tok.next(); buc = atoi(have); if (!tsa(tok, token_semi)) return false; } else if (have == token_message) { msg = tok.next(); if (!tsa(tok, token_semi)) return false; } else { tok.token_error("index/bucket", have); return false; } } if (!tsa(tok, token_semi)) return false; PATTERNP patt = new PATTERN(tok, pat, ind, buc, msg); con.add_pattern(patt); return true; } //////////////////////////////////////////////// // bool parse_ignore(TOKEN &tok, CONFIG &dc); bool parse_ignore(TOKEN &tok, CONFIG &dc) { if (!tsa(tok, token_lbrace)) return false; while (true) { char *have = tok.next(); if (!have) break; if (have == token_rbrace) break; int ipaddr = ip_address(have); if (ipaddr == 0) { tok.token_error("ip address", have); return false; } if (!tsa(tok, token_slash)) return false; have = tok.next(); int mask = atoi(have); if ((mask < 8) || (mask > 32)) { tok.token_error("cidr 8..32 value", have); return false; } if (!tsa(tok, token_semi)) return false; IPPAIR pair; const int masks[33] = {0xffffffff, // 0 0x7fffffff, // 1 0x3fffffff, // 2 0x1fffffff, // 3 0x0fffffff, // 4 0x07ffffff, // 5 0x03ffffff, // 6 0x01ffffff, // 7 0x00ffffff, // 8 0x007fffff, // 9 0x003fffff, // 10 0x001fffff, // 11 0x000fffff, // 12 0x0007ffff, // 13 0x0003ffff, // 14 0x0001ffff, // 15 0x0000ffff, // 16 0x00007fff, // 17 0x00003fff, // 18 0x00001fff, // 19 0x00000fff, // 20 0x000007ff, // 21 0x000003ff, // 22 0x000001ff, // 23 0x000000ff, // 24 0x0000007f, // 25 0x0000003f, // 26 0x0000001f, // 27 0x0000000f, // 28 0x00000007, // 29 0x00000003, // 30 0x00000001, // 31 0x00000000}; // 32 pair.first = ipaddr; pair.last = ipaddr | masks[mask]; pair.cidr = mask; dc.add_pair(pair); } if (!tsa(tok, token_semi)) return false; return true; } //////////////////////////////////////////////// // bool parse_syslogconfig(TOKEN &tok, CONFIG &dc); bool parse_syslogconfig(TOKEN &tok, CONFIG &dc) { char *name = tok.next(); if (!tsa(tok, token_lbrace)) return false; SYSLOGCONFIGP con = new SYSLOGCONFIG(tok, name); if (con->failed()) { delete con; return false; } dc.add_syslogconfig(con); while (true) { 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; return true; } //////////////////////////////////////////////// // parse a config file // bool load_conf(CONFIG &dc, char *fn) { int count = 0; TOKEN tok(fn, &dc.config_files); while (true) { 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; } 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 { tok.token_error("threshold/ignore/add_command/remove_command/file", have); return false; } } tok.token_error("load_conf() found %d syslog files in %s", count, fn); return (!dc.syslogconfigs.empty()); } //////////////////////////////////////////////// // init the tokens // void token_init() { token_add = register_string("add_command"); token_bucket = register_string("bucket"); token_file = register_string("file"); token_ignore = register_string("ignore"); token_include = register_string("include"); token_index = register_string("index"); token_lbrace = register_string("{"); token_message = register_string("message"); token_pattern = register_string("pattern"); token_rbrace = register_string("}"); token_remove = register_string("remove_command"); token_semi = register_string(";"); token_slash = register_string("/"); token_threshold = register_string("threshold"); }