diff --git a/server/filter.py b/server/filter.py index 018ecde8..3d51d3ed 100644 --- a/server/filter.py +++ b/server/filter.py @@ -28,7 +28,6 @@ from failmanager import FailManager from failmanager import FailManagerEmpty from failticket import FailTicket from jailthread import JailThread -from utils.dns import * import time, logging, os, re, sys, socket # Gets the instance of the logger. @@ -257,8 +256,8 @@ class Filter(JailThread): s.insert(1, '32') s[1] = long(s[1]) try: - a = cidr(s[0], s[1]) - b = cidr(ip, s[1]) + a = DNSUtils.cidr(s[0], s[1]) + b = DNSUtils.cidr(ip, s[1]) except Exception: return False if a == b: @@ -379,7 +378,7 @@ class Filter(JailThread): if timeMatch: date = self.getUnixTime(timeMatch.group()) try: - ipMatch = textToIp(match.group("host")) + ipMatch = DNSUtils.textToIp(match.group("host")) if ipMatch: for ip in ipMatch: failList.append([ip, date]) @@ -457,4 +456,100 @@ class Filter(JailThread): ret = [("Currently failed", self.failManager.size()), ("Total failed", self.failManager.getFailTotal())] return ret - \ No newline at end of file + + +## +# Utils class for DNS and IP handling. +# +# This class contains only static methods used to handle DNS and IP +# addresses. + +import socket, struct + +class DNSUtils: + + dnsRe = re.compile("(?:(?:\w|-)+\.){2,}\w+") + ipRe = re.compile("(?:\d{1,3}\.){3}\d{1,3}") + + @staticmethod + def dnsToIp(dns): + """ Convert a DNS into an IP address using the Python socket module. + Thanks to Kevin Drapel. + """ + try: + return socket.gethostbyname_ex(dns)[2] + except socket.gaierror: + return list() + + @staticmethod + def textToDns(text): + """ Search for possible DNS in an arbitrary text. + Thanks to Tom Pike. + """ + match = dnsRe.find(text) + if match: + return match + else: + return None + + @staticmethod + def searchIP(text): + """ Search if an IP address if directly available and return + it. + """ + match = ipRe.find(text) + if match: + return match + else: + return None + + @staticmethod + def isValidIP(str): + """ Return true if str is a valid IP + """ + s = str.split('/', 1) + try: + socket.inet_aton(s[0]) + return True + except socket.error: + return False + + @staticmethod + def textToIp(text): + """ Return the IP of DNS found in a given text. + """ + ipList = list() + # Search for plain IP + plainIP = DNSUtils.searchIP(text) + if not plainIP == None: + if isValidIP(element): + ipList.append(element) + if not ipList: + # Try to get IP from possible DNS + dnsList = DNSUtils.textToDns(text) + if not dnsList == None: + dns = DNSUtils.dnsToIp(element) + for e in dns: + ipList.append(e) + return ipList + + @staticmethod + def cidr(i, n): + """ Convert an IP address string with a CIDR mask into a 32-bit + integer. + """ + # 32-bit IPv4 address mask + MASK = 0xFFFFFFFFL + return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i) + + @staticmethod + def addr2bin(str): + """ Convert a string IPv4 address into an unsigned integer. + """ + return struct.unpack("!L", socket.inet_aton(str))[0] + + @staticmethod + def bin2addr(addr): + """ Convert a numeric IPv4 address into string n.n.n.n form. + """ + return socket.inet_ntoa(struct.pack("!L", addr)) diff --git a/utils/__init__.py b/utils/__init__.py deleted file mode 100644 index 76dba873..00000000 --- a/utils/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" \ No newline at end of file diff --git a/utils/dns.py b/utils/dns.py deleted file mode 100644 index 8d099753..00000000 --- a/utils/dns.py +++ /dev/null @@ -1,102 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import os, re, socket, struct - -def dnsToIp(dns): - """ Convert a DNS into an IP address using the Python socket module. - Thanks to Kevin Drapel. - """ - try: - return socket.gethostbyname_ex(dns)[2] - except socket.gaierror: - return list() - -def textToDns(text): - """ Search for possible DNS in an arbitrary text. - Thanks to Tom Pike. - """ - match = re.findall("(?:(?:\w|-)+\.){2,}\w+", text) - if match: - return match - else: - return [] - -def searchIP(text): - """ Search if an IP address if directly available and return - it. - """ - match = re.findall("(?:\d{1,3}\.){3}\d{1,3}", text) - if match: - return match - else: - return [] - -def isValidIP(str): - """ Return true if str is a valid IP - """ - s = str.split('/', 1) - try: - socket.inet_aton(s[0]) - return True - except socket.error: - return False - -def textToIp(text): - """ Return the IP of DNS found in a given text. - """ - ipList = list() - # Search for plain IP - plainIP = searchIP(text) - for element in plainIP: - if isValidIP(element): - ipList.append(element) - if not ipList: - # Try to get IP from possible DNS - dnsList = textToDns(text) - for element in dnsList: - dns = dnsToIp(element) - for e in dns: - ipList.append(e) - return ipList - -def cidr(i, n): - """ Convert an IP address string with a CIDR mask into a 32-bit - integer. - """ - # 32-bit IPv4 address mask - MASK = 0xFFFFFFFFL - return ~(MASK >> n) & MASK & addr2bin(i) - -def addr2bin(str): - """ Convert a string IPv4 address into an unsigned integer. - """ - return struct.unpack("!L", socket.inet_aton(str))[0] - -def bin2addr(addr): - """ Convert a numeric IPv4 address into string n.n.n.n form. - """ - return socket.inet_ntoa(struct.pack("!L", addr)) diff --git a/utils/mail.py b/utils/mail.py deleted file mode 100644 index 9a89896e..00000000 --- a/utils/mail.py +++ /dev/null @@ -1,91 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import logging, smtplib, email.Utils - -from utils.strings import replaceTag - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") - -class Mail: - """ Mailer class - """ - - def __init__(self, host, port = 25): - self.host = host - self.port = port - self.localTimeFlag = False - - def setFromAddr(self, fromAddr): - """ Set from: address - """ - self.fromAddr = fromAddr - - def setUser(self, user): - """ Set smtpuser - """ - self.user = user - - def setPassword(self, password): - """ Set smtppassword - """ - self.password = password - - def setToAddr(self, toAddr): - """ Set to: address - """ - self.toAddr = toAddr.split() - - def setLocalTimeFlag(self, localTimeFlag): - """ Set to: address - """ - self.localTimeFlag = localTimeFlag - - def sendmail(self, subject, message, aInfo): - """ Send an email using smtplib - """ - subj = replaceTag(subject, aInfo) - msg = replaceTag(message, aInfo) - - mail = ("From: %s\r\nTo: %s\r\nDate: %s\r\nSubject: %s\r\n\r\n" % - (self.fromAddr, ", ".join(self.toAddr), - email.Utils.formatdate(localtime = self.localTimeFlag), - subj)) + msg - - try: - server = smtplib.SMTP(self.host, self.port) - #server.set_debuglevel(1) - if not self.user == '': - server.login(self.user, self.password) - server.sendmail(self.fromAddr, self.toAddr, mail) - logSys.debug("Email sent to " + `self.toAddr`) - server.quit() - except Exception, e: - logSys.error("Unable to send mail to " + self.host + ":" + - `self.port` + " from " + self.fromAddr + " to " + - `self.toAddr` + ": " + `e` + ": " + `e.args`) - diff --git a/utils/pidlock.py b/utils/pidlock.py deleted file mode 100644 index 4659dfb3..00000000 --- a/utils/pidlock.py +++ /dev/null @@ -1,109 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import os, logging - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") - -class PIDLock: - """ Manages the PID lock file. - - The following class shows how to implement the singleton pattern[1] in - Python. A singleton is a class that makes sure only one instance of it - is ever created. Typically such classes are used to manage resources - that by their very nature can only exist once. - - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52558 - """ - - class __impl: - """ Implementation of the singleton interface """ - - def setPath(self, path): - """ Set PID lock file path. - """ - self.path = path - - def create(self): - """ Create PID lock. - """ - try: - fileHandler = open(self.path, mode='w') - pid = os.getpid() - fileHandler.write(`pid` + '\n') - fileHandler.close() - logSys.debug("Created PID lock (" + `pid` + ") in " + self.path) - return True - except: - logSys.error("Unable to create PID lock " + self.path) - return False - - def remove(self): - """ Remove PID lock. - """ - try: - os.remove(self.path) - logSys.debug("Removed PID lock " + self.path) - except OSError: - logSys.error("Unable to remove PID lock " + self.path) - except AttributeError: - # AttributeError if self.path wasn't specified yet - logSys.debug("PID lock not removed because not defined yet") - - def exists(self): - """ Returns the current PID if Fail2Ban is running or False - if no instance found. - """ - try: - fileHandler = open(self.path) - pid = fileHandler.readline() - fileHandler.close() - return pid - except IOError: - return False - - # storage for the instance reference - __instance = None - - def __init__(self): - """ Create singleton instance """ - # Check whether we already have an instance - if PIDLock.__instance is None: - # Create and remember instance - PIDLock.__instance = PIDLock.__impl() - - # Store instance reference as the only member in the handle - self.__dict__['_PIDLock__instance'] = PIDLock.__instance - - def __getattr__(self, attr): - """ Delegate access to implementation """ - return getattr(self.__instance, attr) - - def __setattr__(self, attr, value): - """ Delegate access to implementation """ - return setattr(self.__instance, attr, value) - \ No newline at end of file diff --git a/utils/process.py b/utils/process.py deleted file mode 100644 index cf6aafcd..00000000 --- a/utils/process.py +++ /dev/null @@ -1,108 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import os, logging, signal - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") - -class ExternalError(UserWarning): - """ Exception to warn about failed fwcheck or fwban command - """ - pass - -def createDaemon(): - """ Detach a process from the controlling terminal and run it in the - background as a daemon. - - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 - """ - - try: - # Fork a child process so the parent can exit. This will return control - # to the command line or shell. This is required so that the new process - # is guaranteed not to be a process group leader. We have this guarantee - # because the process GID of the parent is inherited by the child, but - # the child gets a new PID, making it impossible for its PID to equal its - # PGID. - pid = os.fork() - except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) - - if pid == 0: # The first child. - - # Next we call os.setsid() to become the session leader of this new - # session. The process also becomes the process group leader of the - # new process group. Since a controlling terminal is associated with a - # session, and this new session has not yet acquired a controlling - # terminal our process now has no controlling terminal. This shouldn't - # fail, since we're guaranteed that the child is not a process group - # leader. - os.setsid() - - # When the first child terminates, all processes in the second child - # are sent a SIGHUP, so it's ignored. - signal.signal(signal.SIGHUP, signal.SIG_IGN) - - try: - # Fork a second child to prevent zombies. Since the first child is - # a session leader without a controlling terminal, it's possible for - # it to acquire one by opening a terminal in the future. This second - # fork guarantees that the child is no longer a session leader, thus - # preventing the daemon from ever acquiring a controlling terminal. - pid = os.fork() # Fork a second child. - except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) - - if (pid == 0): # The second child. - # Ensure that the daemon doesn't keep any directory in use. Failure - # to do this could make a filesystem unmountable. - os.chdir("/") - else: - os._exit(0) # Exit parent (the first child) of the second child. - else: - os._exit(0) # Exit parent of the first child. - - # Close all open files. Try the system configuration variable, SC_OPEN_MAX, - # for the maximum number of open files to close. If it doesn't exist, use - # the default value (configurable). - try: - maxfd = os.sysconf("SC_OPEN_MAX") - except (AttributeError, ValueError): - maxfd = 256 # default maximum - - for fd in range(0, maxfd): - try: - os.close(fd) - except OSError: # ERROR (ignore) - pass - - # Redirect the standard file descriptors to /dev/null. - os.open("/dev/null", os.O_RDONLY) # standard input (0) - os.open("/dev/null", os.O_RDWR) # standard output (1) - os.open("/dev/null", os.O_RDWR) # standard error (2) - - return True diff --git a/utils/strings.py b/utils/strings.py deleted file mode 100644 index 6ed7ece8..00000000 --- a/utils/strings.py +++ /dev/null @@ -1,40 +0,0 @@ -# This file is part of Fail2Ban. -# -# Fail2Ban is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# Fail2Ban is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Fail2Ban; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -# Author: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import logging - -# Gets the instance of the logger. -logSys = logging.getLogger("fail2ban") - -def replaceTag(query, aInfo): - """ Replace tags in query - """ - string = query - for tag in aInfo: - string = string.replace('<'+tag+'>', `aInfo[tag]`) - # New line - string = string.replace('
', '\n') - return string