changeset 3:8fe310e5cd44

initial coding
author carl
date Sun, 27 Nov 2005 21:12:01 -0800
parents 6e88da080f08
children 2737ab01659a
files src/syslog2iptables.cpp src/syslogconfig.cpp src/syslogconfig.h src/tokenizer.cpp syslog2iptables.conf
diffstat 5 files changed, 426 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/src/syslog2iptables.cpp	Thu Nov 24 10:31:09 2005 -0800
+++ b/src/syslog2iptables.cpp	Sun Nov 27 21:12:01 2005 -0800
@@ -199,9 +199,9 @@
 		CONFIG *conf = new_conf();
 		if (conf) {
 			conf->dump();
-			for (int i=0; i<30; i++) {
+			for (int i=0; i<2; i++) {
 				conf->read();
-				sleep(1);
+				conf->sleep(5);
 			}
 			delete conf;
 			return 0;
--- a/src/syslogconfig.cpp	Thu Nov 24 10:31:09 2005 -0800
+++ b/src/syslogconfig.cpp	Sun Nov 27 21:12:01 2005 -0800
@@ -20,22 +20,179 @@
 
 #include "includes.h"
 #include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
 
 static char* syslogconfig_version="$Id$";
 
-char *token_cisco;
+char *token_bucket;
 char *token_file;
+char *token_ignore;
 char *token_include;
+char *token_index;
 char *token_lbrace;
-char *token_parser;
+char *token_pattern;
 char *token_rbrace;
 char *token_semi;
-char *token_ssh;
+char *token_slash;
+char *token_threshold;
+
+struct ltint
+{
+  bool operator()(const int s1, const int s2) const
+  {
+	return (unsigned)s1 < (unsigned)s2;
+  }
+};
 
 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, int, ltint>   ip_buckets;
+
+class IPR {
+	ip_buckets	violations;
+public:
+	void add(int ip, int bucket, CONFIG &con);
+	void changed(CONFIG &con);
+	void leak(int amount, CONFIG &con);
+};
+
+IPR recorder;
+
+
+////////////////////////////////////////////////
+//
+void IPR::add(int ip, int bucket, CONFIG &con) {
+	if (con.looking(ip)) {
+		ip_buckets::iterator i = violations.find(ip);
+		if (i == violations.end()) violations[ip] = bucket;
+		else {
+			(*i).second += bucket;
+			if ((*i).second > con.get_threshold()) changed(con);
+		}
+	}
+}
+
+
+void IPR::leak(int amount, CONFIG &con) {
+	bool ch = false;
+	for (ip_buckets::iterator i=violations.begin(); i!=violations.end(); ) {
+		int ip = (*i).first;
+		int n  = (*i).second;
+		in_addr ad;
+		ad.s_addr = htonl(ip);
+		char buf[maxlen];
+		snprintf(buf, maxlen, "leak %s with %d count", inet_ntoa(ad), n);
+		my_syslog(buf);
+		if (n <= amount) {
+			ch = true;
+			violations.erase(i++);
+		}
+		else {
+			(*i).second -= amount;
+			i++;
+		}
+	}
+	if (ch) changed(con);
+}
+
+
+void IPR::changed(CONFIG &con) {
+	my_syslog("**** dump");
+	for (ip_buckets::iterator i=violations.begin(); i!=violations.end(); i++) {
+		int ip = (*i).first;
+		int n  = (*i).second;
+		in_addr ad;
+		ad.s_addr = htonl(ip);
+		char buf[maxlen];
+		snprintf(buf, maxlen, "%s with %d count", inet_ntoa(ad), n);
+		my_syslog(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 bucket_) {
+	pattern = pattern_;
+	index	= index_;
+	bucket	= bucket_;
+	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) {
+	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;
+		//	char bu[maxlen];
+		//	snprintf(bu, maxlen, "re match from %d to %d", s, e);
+		//	my_syslog(bu);
+			if (s != -1) {
+				my_syslog(buf);
+				buf[e] = '\0';
+				int ip = ip_address(buf+s);
+				if (ip) {
+					my_syslog(buf+s);
+					recorder.add(ip, bucket, con);
+				}
+				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, bucket);
+	printf("%s }; \n", indent);
+}
+
+
+////////////////////////////////////////////////
+//
 CONFIG::CONFIG() {
 	reference_count    = 0;
 	generation		   = 0;
@@ -48,6 +205,7 @@
 		SYSLOGCONFIG *c = *i;
 		delete c;
 	}
+	ignore.clear();
 }
 
 
@@ -56,7 +214,23 @@
 }
 
 
+void CONFIG::add_pair(IPPAIR pair) {
+	ignore.push_back(pair);
+}
+
+
 void CONFIG::dump() {
+	printf(" threshold %d; \n\n", threshold);
+
+	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);
@@ -65,16 +239,36 @@
 
 
 void CONFIG::read() {
+	while (true) {
+		bool have = false;
 	for (syslogconfig_list::iterator i=syslogconfigs.begin(); i!=syslogconfigs.end(); i++) {
 		SYSLOGCONFIGP c = *i;
-		c->read();
+			have |= c->read(*this);
+		}
+		break;	//!!
+		if (!have) break;
 	}
 }
 
 
-SYSLOGCONFIG::SYSLOGCONFIG(TOKEN &tok, char *file_name_, parser_style parser_) {
+void CONFIG::sleep(int duration) {
+	::sleep(duration);
+	recorder.leak(duration, *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_) {
 	file_name = file_name_;
-	parser	  = parser_;
 	fd		  = open(file_name, O_RDONLY);
 	len 	  = 0;
 	if (fd == -1) {
@@ -83,7 +277,7 @@
 		tok.token_error(buf);
 	}
 	else {
-		lseek(fd, 0, SEEK_END);
+		//lseek(fd, 0, SEEK_END);	//!!
 	}
 }
 
@@ -91,31 +285,45 @@
 SYSLOGCONFIG::~SYSLOGCONFIG() {
 	if (fd != -1) close(fd);
 	fd = -1;
+	for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) {
+		PATTERN *p = *i;
+		delete p;
+	}
 }
 
 
-void SYSLOGCONFIG::read() {
-	if (failed()) return;
-	int n = ::read(fd, buf, buflen-len);
-	if (n > 0) {
+void SYSLOGCONFIG::add_pattern(PATTERNP pat) {
+	patterns.push_back(pat);
+}
+
+
+bool SYSLOGCONFIG::read(CONFIG &con) {
+	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();	// process null terminated string
+			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;
 	}
+	return have;
 }
 
 
-void SYSLOGCONFIG::process() {
-	my_syslog(buf);
+void SYSLOGCONFIG::process(CONFIG &con) {
+	for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) {
+		PATTERN *p = *i;
+		if (p->process(buf, con)) break;
+	}
 }
 
 
@@ -126,13 +334,9 @@
 	indent[i] = '\0';
 	char buf[maxlen];
 	printf("%s file \"%s\" {\n", indent, file_name);
-		switch (parser) {
-			case cisco:
-				printf("%s     parser cisco; \n", indent);
-				break;
-			case ssh:
-				printf("%s     parser ssh; \n", indent);
-				break;
+	for (pattern_list::iterator i=patterns.begin(); i!=patterns.end(); i++) {
+		PATTERN *p = *i;
+		p->dump(level);
 		}
 	printf("%s }; \n", indent);
 }
@@ -182,38 +386,128 @@
 
 ////////////////////////////////////////////////
 //
-bool parse_syslogconfig(TOKEN &tok, CONFIG &dc);
-bool parse_syslogconfig(TOKEN &tok, CONFIG &dc) {
-	char *name = tok.next();
-	parser_style parser;
+bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con);
+bool parse_pattern(TOKEN &tok, SYSLOGCONFIG &con) {
+	char *pat = tok.next();
+	int  ind, buc;
 	if (!tsa(tok, token_lbrace)) return false;
 	while (true) {
 		char *have = tok.next();
-		if (have == token_parser) {
+		if (!have) break;
+		if (have == token_rbrace) break;
+		if (have == token_index) {
 			have = tok.next();
-				 if (have == token_cisco) parser = cisco;
-			else if (have == token_ssh)   parser = ssh;
-			else {
-				tok.token_error("cisco/ssh", have);
-				return false;
-			}
+			ind  = atoi(have);
 			if (!tsa(tok, token_semi)) return false;
 		}
-		else if (have == token_rbrace) {
-			break;	// done
+		else if (have == token_bucket) {
+			have = tok.next();
+			buc  = atoi(have);
+			if (!tsa(tok, token_semi)) return false;
 		}
 		else {
-			tok.token_error("file keyword", have);
+			tok.token_error("index/bucket", have);
 			return false;
 		}
 	}
 	if (!tsa(tok, token_semi)) return false;
-	SYSLOGCONFIGP con = new SYSLOGCONFIG(tok, name, parser);
+	PATTERNP patt = new PATTERN(tok, pat, ind, buc);
+	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;
 }
 
@@ -227,15 +521,20 @@
 	while (true) {
 		char *have = tok.next();
 		if (!have) break;
-		if (have == token_file) {
-			if (!parse_syslogconfig(tok, dc)) {
-				tok.token_error("load_conf() failed to parse syslogconfig");
-				return false;
+		if (have == token_threshold) {
+			have = tok.next();
+			dc.set_threshold(atoi(have));
+			if (!tsa(tok, token_semi)) return false;
 			}
-			else count++;
+		else if (have == token_ignore) {
+			if (!parse_ignore(tok, dc)) return false;
+		}
+		else if (have == token_file) {
+			if (!parse_syslogconfig(tok, dc)) return false;
+			count++;
 		}
 		else {
-			tok.token_error(token_file, have);
+			tok.token_error("threshold/ignore/file", have);
 			return false;
 		}
 	}
@@ -248,12 +547,15 @@
 // init the tokens
 //
 void token_init() {
-	token_cisco 	 = register_string("cisco");
+	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_parser	 = register_string("parser");
+	token_pattern	 = register_string("pattern");
 	token_rbrace	 = register_string("}");
 	token_semi		 = register_string(";");
-	token_ssh		 = register_string("ssh");
+	token_slash 	 = register_string("/");
+	token_threshold  = register_string("threshold");
 }
--- a/src/syslogconfig.h	Thu Nov 24 10:31:09 2005 -0800
+++ b/src/syslogconfig.h	Sun Nov 27 21:12:01 2005 -0800
@@ -23,60 +23,94 @@
 
 #include "tokenizer.h"
 #include <map>
+#include <regex.h>
 
 
 class SYSLOGCONFIG;
+class CONFIG;
+
+struct IPPAIR {
+	int first;
+	int last;
+	int cidr;
+};
+
+class PATTERN {
+	char *			pattern;	// owned by the string table
+	regex_t 		re;
+	int 			index;		// zero based substring of the regex match that contains the ip address or hostname
+	int 			bucket; 	// count to add to the ip address leaky bucket
+public:
+	~PATTERN();
+	PATTERN(TOKEN &tok, char *pattern_, int index_, int bucket_);
+	bool	process(char *buf, CONFIG &con);
+	void	dump(int level);
+};
 
 typedef SYSLOGCONFIG *			SYSLOGCONFIGP;
+typedef PATTERN *				PATTERNP;
 typedef list<SYSLOGCONFIGP> 	syslogconfig_list;
-enum parser_style {cisco, ssh};
+typedef list<IPPAIR>			ippair_list;
+typedef list<PATTERNP>			pattern_list;
 const int buflen = 1024;
 
 class SYSLOGCONFIG {
 	char *			file_name;	// name of the syslog file
-	parser_style	parser;
+	pattern_list	patterns;	// owns the patterns
 	int 			fd;
 	int 			len;		// bytes in the buffer
 	char			buf[buflen];
 public:
-	SYSLOGCONFIG(TOKEN &tok, char *file_name_, parser_style parser_);
+	SYSLOGCONFIG(TOKEN &tok, char *file_name_);
 	~SYSLOGCONFIG();
+	void	add_pattern(PATTERNP pat);
+	bool	failed()	{ return (fd == -1); };
+	bool	read(CONFIG &con);
+	void	process(CONFIG &con);
 	void	dump(int level);
-	bool	failed()	{ return (fd == -1); };
-	void	read();
-	void	process();
 };
 
-struct CONFIG {
+class CONFIG {
+public:
 	// the only mutable stuff once it has been loaded from the config file
 	int 				reference_count;	// protected by the global config_mutex
 	// all the rest is constant after loading from the config file
 	int 				generation;
 	time_t				load_time;
 	string_set			config_files;
-	syslogconfig_list	syslogconfigs;		 // owns all the syslogconfigs, not just top level syslogconfigs
+	int 				threshold;
+	ippair_list 		ignore; 			// owns all the ippairs
+	syslogconfig_list	syslogconfigs;		// owns all the syslogconfigs
 
 	CONFIG();
 	~CONFIG();
+	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();
+	void	sleep(int duration);
+	bool	looking(int ip);
 };
 
 void discard(string_set &s);
 char* register_string(string_set &s, char *name);
 char* register_string(char *name);
-CONFIG *parse_config(char *fn);
+int   ip_address(char *have);
 bool  load_conf(CONFIG &dc, char *fn);
 void  token_init();
 
-extern char *token_cisco;
+extern char *token_bucket;
 extern char *token_file;
+extern char *token_ignore;
 extern char *token_include;
+extern char *token_index;
 extern char *token_lbrace;
-extern char *token_parser;
+extern char *token_pattern;
 extern char *token_rbrace;
 extern char *token_semi;
-extern char *token_ssh;
+extern char *token_slash;
+extern char *token_threshold;
 
 #endif
--- a/src/tokenizer.cpp	Thu Nov 24 10:31:09 2005 -0800
+++ b/src/tokenizer.cpp	Sun Nov 27 21:12:01 2005 -0800
@@ -89,7 +89,7 @@
 	{ s_single,   s_term,	  s_string,   s_single,  s_eol, 	 },  // 0x2C ,
 	{ s_single,   s_token,	  s_string,   s_single,  s_eol, 	 },  // 0x2D -
 	{ s_single,   s_token,	  s_string,   s_single,  s_eol, 	 },  // 0x2E .
-	{ s_slash,	  s_token,	  s_string,   s_slash,	 s_eol, 	 },  // 0x2F /
+	{ s_slash,	  s_term,	  s_string,   s_slash,	 s_eol, 	 },  // 0x2F /
 	{ s_token,	  s_token,	  s_string,   s_token,	 s_eol, 	 },  // 0x30 0
 	{ s_token,	  s_token,	  s_string,   s_token,	 s_eol, 	 },  // 0x31 1
 	{ s_token,	  s_token,	  s_string,   s_token,	 s_eol, 	 },  // 0x32 2
@@ -523,7 +523,7 @@
 
 void TOKEN::token_error(const char *want, const char *have) {
 	token_error();
-	token_error("expecting %s, found %s \n", want, have);
+	token_error("expecting %s, found %s", want, have);
 }
 
 
@@ -535,7 +535,7 @@
 		if (i != filenames.begin()) {
 			char *fn = (*i);
 			int   li = (*j);
-			token_error("\n    included from line %d in file %s -- ", li, fn);
+			token_error("    included from line %d in file %s -- ", li, fn);
 		}
 	}
 }
--- a/syslog2iptables.conf	Thu Nov 24 10:31:09 2005 -0800
+++ b/syslog2iptables.conf	Sun Nov 27 21:12:01 2005 -0800
@@ -1,7 +1,26 @@
-file "/var/log/cisco.log" {
-    parser cisco;
+threshold 600;
+
+ignore {
+    127.0.0.0/8;        // localhost
+    205.147.40.32/26;   // 510sg
+    205.147.0.100/24;   // digilink
+    205.147.39.128/25;  // ams
 };
 
-file "/var/log/messages" {
-    parser ssh;
+file "/var/log/cisco.log" {
+    pattern "Internet_Firewall denied (tcp|udp) ([^(]*)" {
+        index 2;    // zero based
+        bucket 200;
+    };
 };
+
+file "messages.log" {
+    pattern "sshd.pam_unix.*authentication failure.*rhost=(.*) user=" {
+        index 1;    // zero based
+        bucket 300;
+    };
+    pattern "sshd.pam_unix.*authentication failure.*rhost=(.*)$" {
+        index 1;    // zero based
+        bucket 300;
+    };
+};