00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 __author__ = "Cyril Jaquier"
00022 __version__ = "$Revision: 734 $"
00023 __date__ = "$Date: 2009-08-29 00:26:17 +0200 (Sat, 29 Aug 2009) $"
00024 __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
00025 __license__ = "GPL"
00026
00027 from failmanager import FailManager
00028 from ticket import FailTicket
00029 from jailthread import JailThread
00030 from mytime import MyTime
00031 from failregex import FailRegex, Regex, RegexException
00032
00033 import logging, re, time
00034
00035
00036 logSys = logging.getLogger("fail2ban.filter")
00037
00038
00039
00040
00041
00042
00043
00044
00045 class Filter(JailThread):
00046
00047
00048
00049
00050
00051
00052
00053 def __init__(self, jail):
00054 JailThread.__init__(self)
00055
00056 self.jail = jail
00057
00058 self.failManager = FailManager()
00059
00060 self.__failRegex = list()
00061
00062 self.__ignoreRegex = list()
00063
00064 self.__findTime = 6000
00065
00066 self.__ignoreIpList = []
00067 logSys.debug("Created Filter")
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077 def addFailRegex(self, value):
00078 try:
00079 regex = FailRegex(value)
00080 self.__failRegex.append(regex)
00081 except RegexException, e:
00082 logSys.error(e)
00083
00084
00085 def delFailRegex(self, index):
00086 try:
00087 del self.__failRegex[index]
00088 except IndexError:
00089 logSys.error("Cannot remove regular expression. Index %d is not "
00090 "valid" % index)
00091
00092
00093
00094
00095
00096
00097 def getFailRegex(self):
00098 failRegex = list()
00099 for regex in self.__failRegex:
00100 failRegex.append(regex.getOriginalRegex())
00101 return failRegex
00102
00103
00104
00105
00106
00107
00108
00109
00110 def addIgnoreRegex(self, value):
00111 try:
00112 regex = Regex(value)
00113 self.__ignoreRegex.append(regex)
00114 except RegexException, e:
00115 logSys.error(e)
00116
00117 def delIgnoreRegex(self, index):
00118 try:
00119 del self.__ignoreRegex[index]
00120 except IndexError:
00121 logSys.error("Cannot remove regular expression. Index %d is not "
00122 "valid" % index)
00123
00124
00125
00126
00127
00128
00129 def getIgnoreRegex(self):
00130 ignoreRegex = list()
00131 for regex in self.__ignoreRegex:
00132 ignoreRegex.append(regex.getRegex())
00133 return ignoreRegex
00134
00135
00136
00137
00138
00139
00140
00141
00142 def setFindTime(self, value):
00143 self.__findTime = value
00144 self.failManager.setMaxTime(value)
00145 logSys.info("Set findtime = %s" % value)
00146
00147
00148
00149
00150
00151
00152 def getFindTime(self):
00153 return self.__findTime
00154
00155
00156
00157
00158
00159
00160 def setMaxRetry(self, value):
00161 self.failManager.setMaxRetry(value)
00162 logSys.info("Set maxRetry = %s" % value)
00163
00164
00165
00166
00167
00168
00169 def getMaxRetry(self):
00170 return self.failManager.getMaxRetry()
00171
00172
00173
00174
00175
00176
00177
00178
00179 def run(self):
00180 raise Exception("run() is abstract")
00181
00182
00183
00184
00185
00186
00187
00188 def addBannedIP(self, ip):
00189 unixTime = time.time()
00190 self.failManager.addFailure(FailTicket(ip, unixTime))
00191 return ip
00192
00193
00194
00195
00196
00197
00198
00199
00200 def addIgnoreIP(self, ip):
00201 logSys.debug("Add " + ip + " to ignore list")
00202 self.__ignoreIpList.append(ip)
00203
00204 def delIgnoreIP(self, ip):
00205 logSys.debug("Remove " + ip + " from ignore list")
00206 self.__ignoreIpList.remove(ip)
00207
00208 def getIgnoreIP(self):
00209 return self.__ignoreIpList
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219 def inIgnoreIPList(self, ip):
00220 for i in self.__ignoreIpList:
00221
00222 if i == "":
00223 continue
00224 s = i.split('/', 1)
00225
00226 if len(s) == 1:
00227 s.insert(1, '32')
00228 s[1] = long(s[1])
00229 try:
00230 a = DNSUtils.cidr(s[0], s[1])
00231 b = DNSUtils.cidr(ip, s[1])
00232 except Exception:
00233
00234 ips = DNSUtils.dnsToIp(i)
00235 if ip in ips:
00236 return True
00237 else:
00238 continue
00239 if a == b:
00240 return True
00241 return False
00242
00243 def processLineAndAdd(self, line):
00244 try:
00245
00246 l = line.decode('utf-8')
00247 except UnicodeDecodeError:
00248 l = line
00249 for element in self.findFailure(l):
00250 ip = element[0]
00251 unixTime = element[1]
00252 if unixTime < MyTime.time() - self.getFindTime():
00253 break
00254 if self.inIgnoreIPList(ip):
00255 logSys.debug("Ignore %s" % ip)
00256 continue
00257 logSys.debug("Found %s" % ip)
00258 self.failManager.addFailure(FailTicket(ip, unixTime))
00259
00260
00261
00262
00263
00264
00265
00266
00267 def ignoreLine(self, line):
00268 for ignoreRegex in self.__ignoreRegex:
00269 ignoreRegex.process()
00270 if ignoreRegex.match(line):
00271 return True
00272 return False
00273
00274
00275
00276
00277
00278
00279
00280
00281 def findFailure(self, line):
00282 failList = list()
00283
00284 if self.ignoreLine(line):
00285
00286 return failList
00287
00288 for failRegex in self.__failRegex:
00289 failRegex.search(line)
00290 if failRegex.hasMatched():
00291
00292 try:
00293 host = failRegex.getHost()
00294 date = failRegex.getTime()
00295 if not date:
00296 logSys.warning("Unable to get a valid date: %s" % line)
00297 break
00298
00299 dateUnix = time.mktime(date)
00300 ipMatch = DNSUtils.textToIp(host)
00301 if ipMatch:
00302 for ip in ipMatch:
00303 failList.append([ip, dateUnix])
00304
00305 break
00306 except RegexException, e:
00307 logSys.error(e)
00308 return failList
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318 def status(self):
00319 ret = [("Currently failed", self.failManager.size()),
00320 ("Total failed", self.failManager.getFailTotal())]
00321 return ret
00322
00323
00324 class FileFilter(Filter):
00325
00326 def __init__(self, jail):
00327 Filter.__init__(self, jail)
00328
00329 self.__logPath = []
00330
00331
00332
00333
00334
00335
00336 def addLogPath(self, path, tail = True):
00337 container = FileContainer(path, tail)
00338 self.__logPath.append(container)
00339
00340
00341
00342
00343
00344
00345 def delLogPath(self, path):
00346 for log in self.__logPath:
00347 if log.getFileName() == path:
00348 self.__logPath.remove(log)
00349 return
00350
00351
00352
00353
00354
00355
00356 def getLogPath(self):
00357 return self.__logPath
00358
00359
00360
00361
00362
00363
00364
00365 def containsLogPath(self, path):
00366 for log in self.__logPath:
00367 if log.getFileName() == path:
00368 return True
00369 return False
00370
00371 def getFileContainer(self, path):
00372 for log in self.__logPath:
00373 if log.getFileName() == path:
00374 return log
00375 return None
00376
00377
00378
00379
00380
00381
00382
00383
00384 def getFailures(self, filename):
00385 container = self.getFileContainer(filename)
00386 if container == None:
00387 logSys.error("Unable to get failures in " + filename)
00388 return False
00389
00390 try:
00391 container.open()
00392 except Exception, e:
00393 logSys.error("Unable to open %s" % filename)
00394 logSys.exception(e)
00395 return False
00396
00397 line = container.readline()
00398 while not line == "":
00399 if not self._isActive():
00400
00401 break
00402 self.processLineAndAdd(line)
00403
00404 line = container.readline()
00405 container.close()
00406 return True
00407
00408 def status(self):
00409 ret = Filter.status(self)
00410 path = [m.getFileName() for m in self.getLogPath()]
00411 ret.append(("File list", path))
00412 return ret
00413
00414
00415
00416
00417
00418
00419
00420
00421 import md5
00422
00423 class FileContainer:
00424
00425 def __init__(self, filename, tail = True):
00426 self.__filename = filename
00427 self.__tail = tail
00428 self.__handler = None
00429
00430 handler = open(filename)
00431 try:
00432 firstLine = handler.readline()
00433
00434 self.__hash = md5.new(firstLine).digest()
00435
00436 if tail:
00437 handler.seek(0, 2)
00438 self.__pos = handler.tell()
00439 else:
00440 self.__pos = 0
00441 finally:
00442 handler.close()
00443
00444 def getFileName(self):
00445 return self.__filename
00446
00447 def open(self):
00448 self.__handler = open(self.__filename)
00449 firstLine = self.__handler.readline()
00450
00451 myHash = md5.new(firstLine).digest()
00452
00453 if not self.__hash == myHash:
00454 logSys.info("Log rotation detected for %s" % self.__filename)
00455 self.__hash = myHash
00456 self.__pos = 0
00457
00458 self.__handler.seek(self.__pos)
00459
00460 def readline(self):
00461 if self.__handler == None:
00462 return ""
00463 return self.__handler.readline()
00464
00465 def close(self):
00466 if not self.__handler == None:
00467
00468 self.__pos = self.__handler.tell()
00469
00470 self.__handler.close()
00471 self.__handler = None
00472
00473
00474
00475
00476
00477
00478
00479
00480
00481 import socket, struct
00482
00483 class DNSUtils:
00484
00485 IP_CRE = re.compile("(?:\d{1,3}\.){3}\d{1,3}")
00486
00487
00488 def dnsToIp(dns):
00489 """ Convert a DNS into an IP address using the Python socket module.
00490 Thanks to Kevin Drapel.
00491 """
00492 try:
00493 return socket.gethostbyname_ex(dns)[2]
00494 except socket.gaierror:
00495 logSys.warn("Unable to find a corresponding IP address for %s"
00496 % dns)
00497 return list()
00498 dnsToIp = staticmethod(dnsToIp)
00499
00500
00501 def searchIP(text):
00502 """ Search if an IP address if directly available and return
00503 it.
00504 """
00505 match = DNSUtils.IP_CRE.match(text)
00506 if match:
00507 return match
00508 else:
00509 return None
00510 searchIP = staticmethod(searchIP)
00511
00512
00513 def isValidIP(string):
00514 """ Return true if str is a valid IP
00515 """
00516 s = string.split('/', 1)
00517 try:
00518 socket.inet_aton(s[0])
00519 return True
00520 except socket.error:
00521 return False
00522 isValidIP = staticmethod(isValidIP)
00523
00524
00525 def textToIp(text):
00526 """ Return the IP of DNS found in a given text.
00527 """
00528 ipList = list()
00529
00530 plainIP = DNSUtils.searchIP(text)
00531 if not plainIP == None:
00532 plainIPStr = plainIP.group(0)
00533 if DNSUtils.isValidIP(plainIPStr):
00534 ipList.append(plainIPStr)
00535 if not ipList:
00536
00537 ip = DNSUtils.dnsToIp(text)
00538 for e in ip:
00539 ipList.append(e)
00540 return ipList
00541 textToIp = staticmethod(textToIp)
00542
00543
00544 def cidr(i, n):
00545 """ Convert an IP address string with a CIDR mask into a 32-bit
00546 integer.
00547 """
00548
00549 MASK = 0xFFFFFFFFL
00550 return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
00551 cidr = staticmethod(cidr)
00552
00553
00554 def addr2bin(string):
00555 """ Convert a string IPv4 address into an unsigned integer.
00556 """
00557 return struct.unpack("!L", socket.inet_aton(string))[0]
00558 addr2bin = staticmethod(addr2bin)
00559
00560
00561 def bin2addr(addr):
00562 """ Convert a numeric IPv4 address into string n.n.n.n form.
00563 """
00564 return socket.inet_ntoa(struct.pack("!L", addr))
00565 bin2addr = staticmethod(bin2addr)