diff --git a/client/actionreader.py b/client/actionreader.py index 5b620d82..9be467c6 100644 --- a/client/actionreader.py +++ b/client/actionreader.py @@ -32,9 +32,10 @@ logSys = logging.getLogger("fail2ban.client.config") class ActionReader(ConfigReader): - def __init__(self, file, name): + def __init__(self, action, name): ConfigReader.__init__(self) - self.file = file + self.file = action[0] + self.cInfo = action[1] self.name = name def setFile(self, file): @@ -58,7 +59,12 @@ class ActionReader(ConfigReader): ["string", "actioncheck", ""], ["string", "actionban", ""], ["string", "actionunban", ""]] - self.opts = ConfigReader.getOptions(self, "DEFAULT", opts, pOpts) + self.opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) + + if self.has_section("Init"): + for opt in self.options("Init"): + if not self.cInfo.has_key(opt): + self.cInfo[opt] = self.get("Init", opt) def convert(self): head = ["set", self.name] @@ -75,5 +81,10 @@ class ActionReader(ConfigReader): stream.append(head + ["actionban", self.file, self.opts[opt]]) elif opt == "actionunban": stream.append(head + ["actionunban", self.file, self.opts[opt]]) + # cInfo + if self.cInfo: + for p in self.cInfo: + stream.append(head + ["setcinfo", self.file, p, self.cInfo[p]]) + return stream \ No newline at end of file diff --git a/client/fail2banreader.py b/client/fail2banreader.py index a99c3001..1f31b650 100644 --- a/client/fail2banreader.py +++ b/client/fail2banreader.py @@ -40,7 +40,7 @@ class Fail2banReader(ConfigReader): def getOptions(self): opts = [["int", "loglevel", 1]] - self.opts = ConfigReader.getOptions(self, "DEFAULT", opts) + self.opts = ConfigReader.getOptions(self, "Definition", opts) def convert(self): stream = list() diff --git a/client/filterreader.py b/client/filterreader.py index 79f517b8..fbca9bef 100644 --- a/client/filterreader.py +++ b/client/filterreader.py @@ -59,7 +59,7 @@ class FilterReader(ConfigReader): ["string", "failregex", ""], ["int", "maxtime", 600], ["int", "maxretry", 3]] - self.opts = ConfigReader.getOptions(self, "DEFAULT", opts, pOpts) + self.opts = ConfigReader.getOptions(self, "Definition", opts, pOpts) def convert(self): stream = list() diff --git a/client/jailreader.py b/client/jailreader.py index 52a62d16..afcf029e 100644 --- a/client/jailreader.py +++ b/client/jailreader.py @@ -24,7 +24,7 @@ __date__ = "$Date: 2005/11/20 17:07:47 $" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import logging +import logging, re from configreader import ConfigReader from filterreader import FilterReader from actionreader import ActionReader @@ -68,7 +68,8 @@ class JailReader(ConfigReader): # Read action for act in self.opts["action"].split(): - action = ActionReader(act, self.name) + splitAct = JailReader.splitAction(act) + action = ActionReader(splitAct, self.name) action.read() action.getOptions(self.opts) self.actions.append(action) @@ -84,4 +85,13 @@ class JailReader(ConfigReader): for action in self.actions: stream.extend(action.convert()) return stream - \ No newline at end of file + + @staticmethod + def splitAction(action): + m = re.match("^(\w+)(?:\[(.*)\])?$", action) + d = dict() + if m.group(2) <> None: + for param in m.group(2).split(','): + p = param.split('=') + d[p[0]] = p[1] + return [m.group(1), d] diff --git a/client/jailsreader.py b/client/jailsreader.py index b092cf21..8e23ea61 100644 --- a/client/jailsreader.py +++ b/client/jailsreader.py @@ -42,7 +42,7 @@ class JailsReader(ConfigReader): def getOptions(self): opts = [] - self.opts = ConfigReader.getOptions(self, "DEFAULT", opts) + self.opts = ConfigReader.getOptions(self, "Definition", opts) for sec in self.sections(): jail = JailReader(sec) diff --git a/config/action.d/dummy.conf b/config/action.d/dummy.conf index 9f95871f..010d2b27 100644 --- a/config/action.d/dummy.conf +++ b/config/action.d/dummy.conf @@ -1,18 +1,11 @@ -[DEFAULT] - -name = temporary - -# Option: protocol -# Notes.: internally used by config reader for interpolations. -# Values: [ tcp | udp | icmp | all ] Default: tcp -# -protocol = tcp +[Definition] # Option: fwstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD Default: # actionstart = touch /tmp/fail2ban.dummy + echo "" >> /tmp/fail2ban.dummy # Option: fwend # Notes.: command executed once at the end of Fail2Ban @@ -48,3 +41,7 @@ actionban = echo "+" >> /tmp/fail2ban.dummy # Default: iptables -D INPUT -s -j DROP # actionunban = echo "-" >> /tmp/fail2ban.dummy + +[Init] + +init = 123 diff --git a/config/action.d/foo.conf b/config/action.d/foo.conf index 6be4c389..1eb14993 100644 --- a/config/action.d/foo.conf +++ b/config/action.d/foo.conf @@ -1,6 +1,4 @@ -[DEFAULT] - -name = temporary +[Definition] # Option: protocol # Notes.: internally used by config reader for interpolations. @@ -18,13 +16,13 @@ actionstart = touch /tmp/fail2ban.foo # Notes.: command executed once at the end of Fail2Ban # Values: CMD Default: # -actionstop = rm /tmp/fail2ban.foo +actionstop = rm -f /tmp/fail2ban.foo # Option: fwcheck # Notes.: command executed once before each fwban command # Values: CMD Default: # -actioncheck = +actioncheck = [ -e "/tmp/fail2ban.foo" ] # Option: fwban # Notes.: command executed when banning an IP. Take care that the diff --git a/config/action.d/hostsdeny.conf b/config/action.d/hostsdeny.conf new file mode 100644 index 00000000..6af73ecf --- /dev/null +++ b/config/action.d/hostsdeny.conf @@ -0,0 +1,59 @@ +[Definition] + +# Option: fwstart +# Notes.: command executed once at the start of Fail2Ban. +# Values: CMD Default: +# +actionstart = touch + +# Option: fwend +# Notes.: command executed once at the end of Fail2Ban +# Values: CMD Default: +# +actionstop = rm -f + +# Option: fwcheck +# Notes.: command executed once before each fwban command +# Values: CMD Default: +# +actioncheck = + +# Option: fwban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: IP address +# number of failures +# unix timestamp of the last failure +# unix timestamp of the ban time +# Values: CMD +# Default: iptables -I INPUT 1 -s -j DROP +# +actionban = IP= && + echo "ALL: $IP" >> + +# Option: fwunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: IP address +# unix timestamp of the ban time +# unix timestamp of the unban time +# Values: CMD +# Default: iptables -D INPUT -s -j DROP +# +actionunban = IP= && + grep -v "ALL: $IP" > && + mv + +[Init] + +# Option: file +# Notes.: hosts.deny file path. +# Values: STR Default: /etc/hosts.deny +# +file = /etc/hosts.deny + +# Option: file +# Notes.: hosts.deny temporary file path. +# Values: STR Default: /etc/hostsdeny.failban +# +tmpfile = /tmp/hosts.deny.tmp diff --git a/config/action.d/iptables.conf b/config/action.d/iptables.conf index 2da845f4..9baa9c4f 100644 --- a/config/action.d/iptables.conf +++ b/config/action.d/iptables.conf @@ -1,35 +1,26 @@ -[DEFAULT] - -name = temporary -port = 22 - -# Option: protocol -# Notes.: internally used by config reader for interpolations. -# Values: [ tcp | udp | icmp | all ] Default: tcp -# -protocol = tcp +[Definition] # Option: fwstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD Default: # -actionstart = iptables -N fail2ban-%(name)s - iptables -A fail2ban-%(name)s -j RETURN - iptables -I INPUT -p %(protocol)s --dport %(port)s -j fail2ban-%(name)s +actionstart = iptables -N fail2ban- + iptables -A fail2ban- -j RETURN + iptables -I INPUT -p --dport -j fail2ban- # Option: fwend # Notes.: command executed once at the end of Fail2Ban # Values: CMD Default: # -actionstop = iptables -D INPUT -p %(protocol)s --dport %(port)s -j fail2ban-%(name)s - iptables -F fail2ban-%(name)s - iptables -X fail2ban-%(name)s +actionstop = iptables -D INPUT -p --dport -j fail2ban- + iptables -F fail2ban- + iptables -X fail2ban- # Option: fwcheck # Notes.: command executed once before each fwban command # Values: CMD Default: # -actioncheck = iptables -L INPUT | grep -q fail2ban-%(name)s +actioncheck = iptables -L INPUT | grep -q fail2ban- # Option: fwban # Notes.: command executed when banning an IP. Take care that the @@ -41,7 +32,7 @@ actioncheck = iptables -L INPUT | grep -q fail2ban-%(name)s # Values: CMD # Default: iptables -I INPUT 1 -s -j DROP # -actionban = iptables -I fail2ban-%(name)s 1 -s -j DROP +actionban = iptables -I fail2ban- 1 -s -j DROP # Option: fwunban # Notes.: command executed when unbanning an IP. Take care that the @@ -52,4 +43,22 @@ actionban = iptables -I fail2ban-%(name)s 1 -s -j DROP # Values: CMD # Default: iptables -D INPUT -s -j DROP # -actionunban = iptables -D fail2ban-%(name)s -s -j DROP +actionunban = iptables -D fail2ban- -s -j DROP + +[Init] + +# Defaut name of the chain +# +name = default + +# Option: port +# Notes.: specifies port to monitor +# Values: [ NUM | STRING ] Default: +# +port = ssh + +# Option: protocol +# Notes.: internally used by config reader for interpolations. +# Values: [ tcp | udp | icmp | all ] Default: tcp +# +protocol = tcp diff --git a/config/fail2ban.conf b/config/fail2ban.conf index 4c29363d..33323e04 100644 --- a/config/fail2ban.conf +++ b/config/fail2ban.conf @@ -1,4 +1,4 @@ -[DEFAULT] +[Definition] loglevel = 3 diff --git a/config/filter.d/apache-auth.conf b/config/filter.d/apache-auth.conf index e69de29b..642386bc 100644 --- a/config/filter.d/apache-auth.conf +++ b/config/filter.d/apache-auth.conf @@ -0,0 +1,35 @@ +[Definition] + +# Option: maxretry +# Notes.: number of failures before IP gets banned. +# Values: NUM Default: 5 +# +maxretry = 5 + +# Option: logfile +# Notes.: logfile to monitor. +# Values: FILE Default: /var/log/httpd/access_log +# +logfile = /var/log/httpd/access_log + +# Option: timeregex +# Notes.: regex to match timestamp in Apache logfile. For TAI64N format, +# use timeregex = @[0-9a-f]{24} +# Values: [Wed Jan 05 15:08:01 2005] +# Default: \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4} +# +timeregex = \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4} + +# Option: timepattern +# Notes.: format used in "timeregex" fields definition. Note that '%' must be +# escaped with '%' (see http://rgruet.free.fr/PQR2.3.html#timeModule). +# For TAI64N format, use timepattern = tai64n +# Values: TEXT Default: %%a %%b %%d %%H:%%M:%%S %%Y +# +timepattern = %%a %%b %%d %%H:%%M:%%S %%Y + +# Option: failregex +# Notes.: regex to match the password failure messages in the logfile. +# Values: TEXT Default: authentication failure|user .* not found +# +failregex = authentication failure|user .* not found diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index 7d2a007a..da301e67 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -1,12 +1,16 @@ -[DEFAULT] +[Definition] -maxretry = 22 +# Option: maxretry +# Notes.: number of failures before IP gets banned. +# Values: NUM Default: 5 +# +maxretry = 5 # Option: logpath # Notes.: logfile to monitor. # Values: FILE Default: /var/log/secure # -logpath = testcases/files/testcase01.log +logpath = /var/log/secure # Option: timeregex # Notes.: regex to match timestamp in SSH logfile. For TAI64N format, diff --git a/config/filter.d/vsftpd.conf b/config/filter.d/vsftpd.conf new file mode 100644 index 00000000..52141374 --- /dev/null +++ b/config/filter.d/vsftpd.conf @@ -0,0 +1,33 @@ +[Definition] + +# Option: maxretry +# Notes.: number of failures before IP gets banned. +# Values: NUM Default: 5 +# +maxretry = 5 + +# Option: logfile +# Notes.: logfile to monitor. +# Values: FILE Default: /var/log/vsftpd.log +# +logfile = /var/log/vsftpd.log + +# Option: timeregex +# Notes.: regex to match timestamp in VSFTPD logfile. +# Values: [Mar 7 17:53:28] +# Default: \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} +# +timeregex = \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} + +# Option: timepattern +# Notes.: format used in "timeregex" fields definition. Note that '%' must be +# escaped with '%' (see http://rgruet.free.fr/PQR2.3.html#timeModule) +# Values: TEXT Default: %%b %%d %%H:%%M:%%S +# +timepattern = %%b %%d %%H:%%M:%%S + +# Option: failregex +# Notes.: regex to match the password failures messages in the logfile. +# Values: TEXT Default: Authentication failure|Failed password|Invalid user +# +failregex = FAIL LOGIN diff --git a/config/jail.conf b/config/jail.conf index ffa314e1..63851932 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -2,18 +2,18 @@ enabled = true filter = sshd -action = dummy foo +action = hostsdeny[file=/tmp/hosts.deny] maxretry = 2 bantime = 5 -[SSH] +[ssh] enabled = false filter = sshd action = iptables bantime = 10 -[Apache-error] +[apache-error] enabled = false filter = apache-error diff --git a/server/action.py b/server/action.py index 69ce903e..08b0f71c 100644 --- a/server/action.py +++ b/server/action.py @@ -40,6 +40,7 @@ class Action: def __init__(self, name): self.name = name + self.cInfo = dict() ## Command executed in order to initialize the system. self.actionStart = '' ## Command executed when an IP address gets banned. @@ -58,6 +59,15 @@ class Action: def getName(self): return self.name + def setCInfo(self, key, value): + self.cInfo[key] = value + + def getCInfo(self, key): + return self.cInfo[key] + + def delCInfo(self, key): + del self.cInfo[key] + ## # Set the "start" command. # @@ -75,8 +85,9 @@ class Action: def getActionStart(self): return self.actionStart - def execActionStart(self, aInfo): - return self.executeCmd(self.actionStart, aInfo); + def execActionStart(self): + startCmd = Action.replaceTag(self.actionStart, self.cInfo) + return Action.executeCmd(startCmd) ## # Set the "ban" command. @@ -96,7 +107,7 @@ class Action: return self.actionBan def execActionBan(self, aInfo): - return self.executeCmd(self.actionBan, aInfo); + return self.processCmd(self.actionBan, aInfo); ## # Set the "unban" command. @@ -116,7 +127,7 @@ class Action: return self.actionUnban def execActionUnban(self, aInfo): - return self.executeCmd(self.actionUnban, aInfo); + return self.processCmd(self.actionUnban, aInfo); ## # Set the "check" command. @@ -135,9 +146,6 @@ class Action: def getActionCheck(self): return self.actionCheck - def execActionCheck(self, aInfo): - return self.executeCmd(self.actionCheck, aInfo); - ## # Set the "stop" command. # @@ -155,8 +163,9 @@ class Action: def getActionStop(self): return self.actionStop - def execActionStop(self, aInfo): - return self.executeCmd(self.actionStop, aInfo); + def execActionStop(self): + stopCmd = Action.replaceTag(self.actionStop, self.cInfo) + return Action.executeCmd(stopCmd) @staticmethod def replaceTag(query, aInfo): @@ -169,20 +178,38 @@ class Action: string = string.replace("
", '\n') return string - @staticmethod - def executeCmd(cmd, aInfo = None): + def processCmd(self, cmd, aInfo = None): """ Executes an OS command. """ if cmd == "": logSys.debug("Nothing to do") return True + checkCmd = Action.replaceTag(self.actionCheck, self.cInfo) + if not Action.executeCmd(checkCmd): + logSys.error("Invariant check failed. Trying to restore a sane" + + " environment") + stopCmd = Action.replaceTag(self.actionStop, self.cInfo) + Action.executeCmd(stopCmd) + startCmd = Action.replaceTag(self.actionStart, self.cInfo) + Action.executeCmd(startCmd) + if not Action.executeCmd(checkCmd): + logSys.fatal("Unable to restore environment") + return False + # Replace tags if not aInfo == None: realCmd = Action.replaceTag(cmd, aInfo) else: realCmd = cmd + # Replace static fields + realCmd = Action.replaceTag(realCmd, self.cInfo) + + return Action.executeCmd(realCmd) + + @staticmethod + def executeCmd(realCmd): logSys.debug(realCmd) retval = os.system(realCmd) #if not retval == 0: @@ -192,4 +219,5 @@ class Action: return True else: logSys.error("%s returned %x" % (realCmd, retval)) - return False \ No newline at end of file + return False + \ No newline at end of file diff --git a/server/actions.py b/server/actions.py index 707272d4..bed16762 100644 --- a/server/actions.py +++ b/server/actions.py @@ -103,7 +103,7 @@ class Actions(JailThread): def run(self): for action in self.actions: - action.execActionStart(None) + action.execActionStart() self.setActive(True) while self.isActive(): if not self.isIdle: @@ -116,7 +116,7 @@ class Actions(JailThread): time.sleep(self.sleepTime) self.flushBan() for action in self.actions: - action.execActionStop(None) + action.execActionStop() logSys.debug(self.jail.getName() + ": action terminated") return True diff --git a/server/server.py b/server/server.py index 31f9d97d..e868cfc6 100644 --- a/server/server.py +++ b/server/server.py @@ -181,6 +181,24 @@ class Server: else: raise ServerUnknownJail(name) + def setCInfo(self, name, action, key, value): + if self.jails.has_key(name): + self.jails[name].getAction().getAction(action).setCInfo(key, value) + else: + raise ServerUnknownJail(name) + + def getCInfo(self, name, action, key): + if self.jails.has_key(name): + return self.jails[name].getAction().getAction(action).getCInfo(key) + else: + raise ServerUnknownJail(name) + + def delCInfo(self, name, action, key): + if self.jails.has_key(name): + self.jails[name].getAction().getAction(action).delCInfo(key) + else: + raise ServerUnknownJail(name) + def setBanTime(self, name, value): if self.jails.has_key(name): self.jails[name].getAction().setBanTime(value) diff --git a/server/transmitter.py b/server/transmitter.py index c10157c9..daf65c4c 100644 --- a/server/transmitter.py +++ b/server/transmitter.py @@ -147,6 +147,17 @@ class Transmitter: elif action[1] == "delaction": self.server.delAction(name, value) return None + elif action[1] == "setcinfo": + act = action[2] + key = action[3] + value = action[4] + self.server.setCInfo(name, act, key, value) + return self.server.getCInfo(name, act, key) + elif action[1] == "delcinfo": + act = action[2] + key = action[3] + self.server.delCInfo(name, act, key) + return None elif action[1] == "actionstart": act = action[2] value = action[3]