From edd7ed5d30bf903d3bde5359872cf03b94c02e6b Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Wed, 6 Jul 2005 23:10:26 +0000 Subject: [PATCH] Load fail2ban-0.4.1 into debs/fail2ban/trunk. --- Makefile | 17 + config/fail2ban.conf.default | 14 +- debian/README.Debian | 12 + debian/TODO | 3 + debian/changelog | 6 + debian/compat | 1 + debian/control | 18 ++ debian/copyright | 26 ++ debian/fail2ban.conffiles | 2 + debian/fail2ban.init | 61 ++++ debian/fail2ban.links | 1 + debian/rules | 20 ++ debian/watch | 6 + fail2ban.py | 6 +- log4py.py | 593 +++++++++++++++++++++++++++++++++++ setup.py | 2 +- 16 files changed, 778 insertions(+), 10 deletions(-) create mode 100644 Makefile create mode 100644 debian/README.Debian create mode 100644 debian/TODO create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/fail2ban.conffiles create mode 100644 debian/fail2ban.init create mode 100644 debian/fail2ban.links create mode 100755 debian/rules create mode 100644 debian/watch create mode 100644 log4py.py diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..5c2dbde8 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +# quick dirty hack to avoid reading documentation for cdbs where +# install target involved only if using autotools +DESTDIR=debian/fail2ban + +all:: + cp fail2ban.py fail2ban + gzip -c CHANGELOG > changelog.gz + +install:: all + mkdir -p $(DESTDIR)/etc/default + cp config/fail2ban.conf.default $(DESTDIR)/etc/fail2ban.conf + cp config/gentoo-confd $(DESTDIR)/etc/default/fail2ban + mkdir -p $(DESTDIR)/usr/lib/fail2ban/ + cp log4py.py $(DESTDIR)/usr/lib/fail2ban/ + +clean:: + rm -rf changelog.gz fail2ban `find -iname '*.pyc' ` diff --git a/config/fail2ban.conf.default b/config/fail2ban.conf.default index 4a9cafd1..18a30ed9 100644 --- a/config/fail2ban.conf.default +++ b/config/fail2ban.conf.default @@ -21,7 +21,7 @@ ipfw-start-rule = 100 # Notes.: start fail2ban as a daemon. Output is redirect to logfile. # Values: [true | false] Default: false # -background = false +background = true # Option: debug # Notes.: enable debug mode. More verbose output and bypass root user test. @@ -45,7 +45,7 @@ logfile = /var/log/fail2ban.log # Notes.: number of retrys before IP gets banned. # Values: NUM Default: 3 # -maxretry = 3 +maxretry = 5 # Option: bantime # Notes.: number of seconds an IP will be banned. @@ -85,9 +85,9 @@ enabled = false # Option: logfile # Notes.: logfile to monitor. -# Values: FILE Default: /var/log/httpd/access_log +# Values: FILE Default: /var/log/apache/access_log # -logfile = /var/log/httpd/access_log +logfile = /var/log/apache/access_log # Option: timeregex # Notes.: regex to match timestamp in Apache logfile. @@ -118,9 +118,9 @@ enabled = true # Option: logfile # Notes.: logfile to monitor. -# Values: FILE Default: /var/log/secure +# Values: FILE Default: /var/log/auth.log # -logfile = /var/log/secure +logfile = /var/log/auth.log # Option: timeregex # Notes.: regex to match timestamp in SSH logfile. @@ -140,4 +140,4 @@ timepattern = %%b %%d %%H:%%M:%%S # Notes.: regex to match the password failures messages in the logfile. # Values: TEXT Default: Authentication failure|Failed password|Invalid user # -failregex = Authentication failure|Failed password|Invalid user +failregex = Authentication failure|Failed password|Invalid user|Illegal user|Failed keyboard-interactive diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 00000000..f43a6f28 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,12 @@ +fail2ban for Debian +------------------- + +This package is nearly 100% identical to the upstream version. It was merely +packaged to be installed on a Debian system. + +Module log4py installed into lib/fail2ban directory because there is no package for not-developed-in-a-long-time fail2ban + +See the file TODO.Debian for more details, as well as the Debian Bug Tracking +system. + + -- Yaroslav Halchenko , Tue, 4 Jul 2005 00:00:00 -1000 diff --git a/debian/TODO b/debian/TODO new file mode 100644 index 00000000..e990b16d --- /dev/null +++ b/debian/TODO @@ -0,0 +1,3 @@ +Because this is just a quick hack to package fail2ban it might be missing some crucial parts... + +-- yoh@onerussian.com diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..f888390e --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +fail2ban (0.4.1-1) unstable; urgency=low + + * First upstream release of a Debian package + + -- Yaroslav Halchenko Mon, 04 Jul 2005 11:47:23 +0300 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..b8626c4c --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..c4cfad08 --- /dev/null +++ b/debian/control @@ -0,0 +1,18 @@ +Source: fail2ban +Section: net +Priority: optional +Maintainer: Yaroslav Halchenko +Build-Depends-Indep: debhelper (>= 4.1.67), python, python2.3-dev +Standards-Version: 3.6.1 + +Package: fail2ban +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends}, python, iptables +Description: Ban IPs that make too many password failure + Fail2Ban scans log files like /var/log/pwdfail or + /var/log/apache/error_log and bans IP that makes too many password + failures. It updates firewall rules to reject the IP + address. Currently, iptables, ipfwadm and ipfw are supported. + . + Homepage: http://www.sourceforge.net/projects/fail2ban + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..a7fed7f7 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This package was originally debianized by Yaroslav Halchenko + on Mon Jul 4 14:41:34 HST 2005 + +It was downloaded from http://www.sourceforge.net/projects/fail2ban +pointed from the project page of the upstream author +Cyril Jaquier: +http://fail2ban.sourceforge.net + +Copyright: + +This program 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. + +This program 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 this program; if not, write to the Free Software +Foundation, 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. + +See /usr/share/common-licenses/GPL for the full license. diff --git a/debian/fail2ban.conffiles b/debian/fail2ban.conffiles new file mode 100644 index 00000000..24163f38 --- /dev/null +++ b/debian/fail2ban.conffiles @@ -0,0 +1,2 @@ +/etc/fail2ban.conf +/etc/default/fail2ban diff --git a/debian/fail2ban.init b/debian/fail2ban.init new file mode 100644 index 00000000..28e09d6c --- /dev/null +++ b/debian/fail2ban.init @@ -0,0 +1,61 @@ +#! /bin/sh +# +# skeleton example file to build /etc/init.d/ scripts. +# This file should be used to construct scripts for /etc/init.d. +# +# Written by Miquel van Smoorenburg . +# Modified for Debian +# by Ian Murdock . +# +# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/fail2ban +NAME=fail2ban +DESC=fail2ban +PIDFILE=/var/run/$NAME.pid + +test -x $DAEMON || exit 0 + +# Include fail2ban defaults if available +if [ -f /etc/default/fail2ban ] ; then + . /etc/default/fail2ban +fi +DAEMON_OPTS=$FAIL2BAN_OPTS +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + [ -f $PIDFILE ] && [ ! -d /proc/`cat $PIDFILE` ] && rm -f $PIDFILE + start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ + -b --exec $DAEMON -- $DAEMON_OPTS + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid + rm -f $PIDFILE + echo "$NAME." + ;; + restart|force-reload) + # + # If the "reload" option is implemented, move the "force-reload" + # option to the "reload" entry above. If not, "force-reload" is + # just the same as "restart". + # + echo -n "Restarting $DESC: " + ( $0 stop ) + sleep 1 + $0 start + ;; + *) + N=/etc/init.d/$NAME + # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/fail2ban.links b/debian/fail2ban.links new file mode 100644 index 00000000..a28cc7a5 --- /dev/null +++ b/debian/fail2ban.links @@ -0,0 +1 @@ +/usr/bin/fail2ban /usr/sbin/fail2ban diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..39451be1 --- /dev/null +++ b/debian/rules @@ -0,0 +1,20 @@ +#!/usr/bin/make -f +# Copyrignt 2003-04 Yaroslav Halchenko + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/makefile.mk +include /usr/share/cdbs/1/class/python-distutils.mk +#include /usr/share/cdbs/1/rules/buildcore.mk +#include /usr/share/cdbs/1/class/autotools.mk + +#DEB_MAKE_INSTALL_TARGET := "install DESTDIR=debian/fail2ban" +# does on package by package bases so doesn't work :-/ +#DEB_DH_INSTALL_ARGS := --list-missing +# DEB_OPT_FLAG := +# DEB_CONFIGURE_EXTRA_FLAGS := --enable-debug +# tight versioning +#DEB_DH_MAKESHLIBS_ARGS := -V +common-install-prehook-impl:: + make install +clean:: + make clean diff --git a/debian/watch b/debian/watch new file mode 100644 index 00000000..d000cd8c --- /dev/null +++ b/debian/watch @@ -0,0 +1,6 @@ +# watch control file for uscan +# Run the "uscan" command to check for upstream updates and more. +# Site Directory Pattern Version Script +version=2 +http://voxel.dl.sourceforge.net/sourceforge/fail2ban/ \ + fail2ban-([0-9]+\.[0-9]+\.[0-9]*)\.tar\.bz2 debian uupdate diff --git a/fail2ban.py b/fail2ban.py index 56fe4976..56fb03f1 100755 --- a/fail2ban.py +++ b/fail2ban.py @@ -29,6 +29,10 @@ __license__ = "GPL" import time, sys, getopt, os, signal, string from ConfigParser import * +# Appends our own modules path +# you: moved before loading log4py so we add path to it +sys.path.append('/usr/lib/fail2ban') + # Checks if log4py is present. try: import log4py @@ -36,8 +40,6 @@ except: print "log4py is needed (see README)" sys.exit(-1) -# Appends our own modules path -sys.path.append('/usr/lib/fail2ban') from firewall.iptables import Iptables from firewall.ipfw import Ipfw diff --git a/log4py.py b/log4py.py new file mode 100644 index 00000000..19c97bf2 --- /dev/null +++ b/log4py.py @@ -0,0 +1,593 @@ +""" + +Python logging module - Version 1.3 + +Loglevels: + + LOGLEVEL_NONE, LOGLEVEL_ERROR, LOGLEVEL_NORMAL, LOGLEVEL_VERBOSE, LOGLEVEL_DEBUG + +Format-Parameters: + + %C -- The name of the current class. + %D -- Program duration since program start. + %d -- Program duration for the last step (last output). + %F -- The name of the current function. + %f -- Current filename + %L -- Log type (Error, Warning, Debug or Info) + %M -- The actual message. + %N -- The current line number. + %T -- Current time (human readable). + %t -- Current time (machine readable) + %U -- Current fully qualified module/file. + %u -- Current module/file. + %x -- NDC (nested diagnostic contexts). + +Pre-defined Formats: + + FMT_SHORT -- %M + FMT_MEDIUM -- [ %C.%F ] %D: %M + FMT_LONG -- %T %L %C [%F] %x%M + FMT_DEBUG -- %T [%D (%d)] %L %C [%F (%N)] %x%M + +""" + +# Logging levels +LOGLEVEL_NONE = 1 << 0 +LOGLEVEL_ERROR = 1 << 1 +LOGLEVEL_NORMAL = 1 << 2 +LOGLEVEL_VERBOSE = 1 << 3 +LOGLEVEL_DEBUG = 1 << 4 + +# Pre-defined format strings +FMT_SHORT = "%M" +FMT_MEDIUM = "[ %C.%F ] %D: %M" +FMT_LONG = "%T %L %C [%F] %x%M" +FMT_DEBUG = "%T [%D (%d)] %L %C [%F (%N)] %x%M" + +# Special logging targets +TARGET_MYSQL = "MySQL" +TARGET_POSTGRES = "Postgres" +TARGET_SYSLOG = "Syslog" +TARGET_SYS_STDOUT = "sys.stdout" +TARGET_SYS_STDERR = "sys.stderr" +TARGET_SYS_STDOUT_ALIAS = "stdout" +TARGET_SYS_STDERR_ALIAS = "stderr" + +SPECIAL_TARGETS = [ TARGET_MYSQL, TARGET_POSTGRES, TARGET_SYSLOG, TARGET_SYS_STDOUT, TARGET_SYS_STDERR, TARGET_SYS_STDOUT_ALIAS, TARGET_SYS_STDERR_ALIAS ] + +# Configuration files +CONFIGURATION_FILES = {} +CONFIGURATION_FILES[1] = "log4py.conf" # local directory +CONFIGURATION_FILES[2] = "$HOME/.log4py.conf" # hidden file in the home directory +CONFIGURATION_FILES[3] = "/etc/log4py.conf" # system wide file + +# Constants for the FileAppender +ROTATE_NONE = 0 +ROTATE_DAILY = 1 +ROTATE_WEEKLY = 2 +ROTATE_MONTHLY = 3 + +# The following constants are of internal interest only + +# Message constants (used for ansi colors and for logtype %L) +MSG_DEBUG = 1 << 0 +MSG_WARN = 1 << 1 +MSG_ERROR = 1 << 2 +MSG_INFO = 1 << 3 + +# Boolean constants +TRUE = "TRUE" +FALSE = "FALSE" + +# Color constants +BLACK = 30 +RED = 31 +GREEN = 32 +YELLOW = 33 +BLUE = 34 +PURPLE = 35 +AQUA = 36 +WHITE = 37 + +LOG_MSG = { MSG_DEBUG: "DEBUG", MSG_WARN: "WARNING", MSG_ERROR: "ERROR", MSG_INFO: "INFO"} +LOG_COLORS = { MSG_DEBUG: [WHITE, BLACK, FALSE], MSG_WARN: [WHITE, BLACK, FALSE], MSG_ERROR: [WHITE, BLACK, TRUE], MSG_INFO: [WHITE, BLACK, FALSE]} +LOG_LEVELS = { "DEBUG": LOGLEVEL_DEBUG, "VERBOSE": LOGLEVEL_VERBOSE, "NORMAL": LOGLEVEL_NORMAL, "NONE": LOGLEVEL_NONE, "ERROR": LOGLEVEL_ERROR } + +SECTION_DEFAULT = "Default" + +from time import time, strftime, localtime +from types import StringType, ClassType, InstanceType, FileType, TupleType +from string import zfill, atoi, lower, upper, join, replace, split, strip +from re import sub +from ConfigParser import ConfigParser, NoOptionError +from os import stat, rename + +import sys +import traceback +import os +import copy +import socket +import locale +if (os.name == "posix"): + import syslog + +try: + import MySQLdb + mysql_available = TRUE +except: + mysql_available = FALSE + +def get_homedirectory(): + if (sys.platform == "win32"): + if (os.environ.has_key("USERPROFILE")): + return os.environ["USERPROFILE"] + else: + return "C:\\" + else: + if (os.environ.has_key("HOME")): + return os.environ["HOME"] + else: + # No home directory set + return "" + +# This is the main class for the logging module + +class Logger: + + cache = {} + instance = None + configfiles = [] + hostname = socket.gethostname() + + def __init__(self, useconfigfiles = TRUE, customconfigfiles = None): + """ **(private)** Class initalization & customization. """ + if (customconfigfiles): + if (type(customconfigfiles) == StringType): + customconfigfiles = [customconfigfiles] + Logger.configfiles = customconfigfiles + + if (not Logger.instance): + self.__Logger_setdefaults() + if (useconfigfiles == TRUE): + self.__Logger_appendconfigfiles(Logger.configfiles) + # read the default options + self.__Logger_parse_options() + + self.__Logger_timeinit = time() + self.__Logger_timelaststep = self.__Logger_timeinit + + Logger.instance = self + + if (useconfigfiles == TRUE): + # read and pre-cache settings for named classids + self.__Logger_cache_options() + + def get_root(self): + """ Provides a way to change the base logger object's properties. """ + return Logger.instance + + def get_instance(self, classid = "Main", use_cache = TRUE): + """ Either get the cached logger instance or create a new one + + Note that this is safe, even if you have your target set to sys.stdout + or sys.stderr + """ + + cache = Logger.cache + + if (type(classid) == ClassType): + classid = classid.__name__ + elif (type(classid) == InstanceType): + classid = classid.__class__.__name__ + + # classid has to be lowercase, because the ConfigParser returns sections lowercase + classid = lower(classid) + + if ((cache.has_key(classid)) and (use_cache == TRUE)): + cat = Logger.cache[classid] + else: + instance = Logger.instance + + # test for targets which won't deep copy + targets = instance.__Logger_targets + deepcopyable = TRUE + for i in range(len(targets)): + if (type(targets[i]) == FileType): + deepcopyable = FALSE + if (deepcopyable == FALSE): + # swap the non-copyable target out for a moment + del instance.__Logger_targets + cat = copy.deepcopy(instance) + instance.__Logger_targets = targets + cat.__Logger_targets = targets + else: + cat = copy.deepcopy(instance) + + cat.__Logger_classname = classid + # new categories have their own private Nested Diagnostic Contexts + self.__Logger_ndc = [] + self.__Logger_classid = classid + + cat.debug("Class %s instantiated" % classid) + if (use_cache == TRUE): + cache[classid] = cat + + return cat + + # Log-target handling (add, remove, set, remove_all) + + def add_target(self, target, *args): + """ Add a target to the logger targets. """ + if (not target in self.__Logger_targets): + if (target == TARGET_MYSQL): + if (mysql_available == TRUE): + # Required parameters: dbhost, dbname, dbuser, dbpass, dbtable + try: + self.__Logger_mysql_connection = MySQLdb.connect(host=args[0], db=args[1], user=args[2], passwd=args[3]) + self.__Logger_mysql_cursor = self.__Logger_mysql_connection.cursor() + self.__Logger_mysql_tablename = args[4] + self.__Logger_targets.append(target) + except MySQLdb.OperationalError, detail: + self.error("MySQL connection failed: %s" % detail) + else: + self.error("MySQL target not added - Python-mysql not available") + else: + if (type(target) == StringType): + if (target not in SPECIAL_TARGETS): + # This is a filename + target = FileAppender(target, self.__Logger_rotation) + if ((target == TARGET_SYSLOG) and (os.name != "posix")): + self.warn("TARGET_SYSLOG is not available on non-posix platforms!") + else: + self.__Logger_targets.append(target) + + def remove_target(self, target): + """ Remove a target from the logger targets. """ + if (target in self.__Logger_targets): + if (target == TARGET_MYSQL): + self.__Logger_mysql_connection.close() + self.__Logger_targets.remove(target) + + def set_target(self, target): + """ Set a single target. """ + if (type(target) == StringType): + if (target not in SPECIAL_TARGETS): + # File target + target = FileAppender(target, self.__Logger_rotation) + self.__Logger_targets = [ target ] + + def remove_all_targets(self): + """ Remove all targets from the logger targets. """ + self.__Logger_targets=[] + + def get_targets(self): + """ Returns all defined targets. """ + return self.__Logger_targets + + # Methods to set properties + + def set_loglevel(self, loglevel): + """ Set the loglevel for the current instance. """ + self.__Logger_loglevel = loglevel + + def set_formatstring(self, formatstring): + """ Set a format string. """ + self.__Logger_formatstring = formatstring + + def set_use_ansi_codes(self, useansicodes): + """ Use ansi codes for output to the console (TRUE or FALSE). """ + self.__Logger_useansicodes = useansicodes + + def set_time_format(self, timeformat): + """ Set the time format (default: loaded from the system locale). """ + self.__Logger_timeformat = timeformat + + def set_rotation(self, rotation): + """ Set the file rotation mode to one of ROTATE_NONE, ROTATE_DAILY, ROTATE_WEEKLY, ROTATE_MONTHLY """ + self.__Logger_rotation = rotation + for i in range(len(self.__Logger_targets)): + target = self.__Logger_targets[i] + if (isinstance(target, FileAppender)): + target.set_rotation(rotation) + + # Method to get properties + + def get_loglevel(self): + """ Returns the current loglevel. """ + return self.__Logger_loglevel + + def get_formatstring(self): + """ Returns the current format string. """ + return self.__Logger_formatstring + + def get_use_ansi_codes(self): + """ Returns, wether ansi codes are being used or not. """ + return self.__Logger_useansicodes + + def get_time_format(self): + """ Returns the current time format. """ + return self.__Logger_timeformat + + def get_rotation(self): + """ Returns the current rotation setting. """ + return self.__Logger_rotation + + # Methods to push and pop trace messages for nested contexts + + def push(self, message): + """ Add a trace message. """ + self.__Logger_ndc.append(message) + + def pop(self): + """ Remove the topmost trace message. """ + ct = len(self.__Logger_ndc) + if (ct): + del(self.__Logger_ndc[ct-1]) + + def clear_ndc(self): + """ Clears all NDC messages. """ + self.__Logger_ndc = [] + + # Methods to actually print messages + + def debug(self, *messages): + """ Write a debug message. """ + if (self.__Logger_loglevel >= LOGLEVEL_DEBUG): + message = self.__Logger_collate_messages(messages) + self.__Logger_showmessage(message, MSG_DEBUG) + + def warn(self, *messages): + """ Write a warning message. """ + if (self.__Logger_loglevel >= LOGLEVEL_VERBOSE): + message = self.__Logger_collate_messages(messages) + self.__Logger_showmessage(message, MSG_WARN) + + def error(self, *messages): + """ Write a error message. """ + if (self.__Logger_loglevel >= LOGLEVEL_ERROR): + message = self.__Logger_collate_messages(messages) + self.__Logger_showmessage(message, MSG_ERROR) + + def info(self, *messages): + """ Write a info message. """ + if (self.__Logger_loglevel >= LOGLEVEL_NORMAL): + message = self.__Logger_collate_messages(messages) + self.__Logger_showmessage(message, MSG_INFO) + + # Private methods of the Logger class - you never have to use those directly + + def __Logger_collate_messages(self, messages): + """ **(private)** Create a single string from a number of messages. """ + return strip(reduce(lambda x, y: "%s%s" % (x, y), messages)) + + def __Logger_tracestack(self): + """ **(private)** Analyze traceback stack and set linenumber and functionname. """ + stack = traceback.extract_stack() + self.__Logger_module = stack[-4][0] + self.__Logger_linenumber = stack[-4][1] + self.__Logger_functionname = stack[-4][2] + self.__Logger_filename = stack[-4][0] + if (self.__Logger_functionname == "?"): + self.__Logger_functionname = "Main" + + def __Logger_setdefaults(self): + """ **(private)** Set default values for internal variables. """ + locale.setlocale(locale.LC_ALL) + self.__Logger_classid = None + self.__Logger_targets = [ TARGET_SYS_STDOUT ] # default target = sys.stdout + self.__Logger_formatstring = FMT_LONG + self.__Logger_loglevel = LOGLEVEL_NORMAL + self.__Logger_rotation = ROTATE_NONE + self.__Logger_useansicodes = FALSE + self.__Logger_functionname = "" + self.__Logger_filename = "" + self.__Logger_linenumber = -1 + try: + self.__Logger_timeformat = "%s %s" % (locale.nl_langinfo(locale.D_FMT), locale.nl_langinfo(locale.T_FMT)) + except (AttributeError): + self.__Logger_timeformat = "%d.%m.%Y %H:%M:%S" + self.__Logger_classname = None + self.__Logger_configfilename = "" + self.__Logger_module = "" + self.__Logger_ndc = [] # ndc = Nested Diagnostic Context + + def __Logger_find_config(self): + """ **(private)** Search for configuration files. """ + if (not self.__Logger_configfilename): + priorities = CONFIGURATION_FILES.keys() + priorities.sort() + configfilename = "" + for i in range(len(priorities)): + filename = CONFIGURATION_FILES[priorities[i]] + home_directory = get_homedirectory() + if (os.sep == "\\"): + home_directory = replace(home_directory, "\\", "\\\\") + filename = sub("\$HOME", home_directory, filename) + if (os.path.exists(filename)): + configfilename = filename + break + self.__Logger_configfilename = configfilename + return self.__Logger_configfilename + + def __Logger_parse_options(self, section = SECTION_DEFAULT): + """ **(private)** Parse main options from config file. """ + configfilename = self.__Logger_find_config() + + if (configfilename != ""): + parser = ConfigParser() + parser.read(configfilename) + self.__Logger_set_instance_options(parser, section, self) + return TRUE + + def __Logger_set_instance_options(self, parser, section, instance): + """ **(private)** Set the options for a given instance from the parser section """ + + for i in range(len(parser.options(section))): + option = lower(parser.options(section)[i]) + value = parser.get(section, option) + if (option == "format"): + instance.set_formatstring(value) + elif (option == "timeformat"): + instance.set_time_format(value) + elif (option == "ansi"): + instance.set_use_ansi_codes(upper(value)) + elif (option == "loglevel"): + instance.set_loglevel(LOG_LEVELS[upper(value)]) + elif (option == "target"): + splitted = split(value, ",") + instance.remove_all_targets() + for i in range(len(splitted)): + instance.add_target(strip(splitted[i])) + + def __Logger_cache_options(self): + """ **(private)** Read and cache debug levels for categories from config file. """ + configfilename = self.__Logger_find_config() + + if (configfilename != ""): + parser = ConfigParser() + parser.read(configfilename) + + for i in range(len(parser.sections())): + section = parser.sections()[i] + if (section != SECTION_DEFAULT): + instance = self.get_instance(section) + self.__Logger_set_instance_options(parser, section, instance) + return TRUE + + def __Logger_appendconfigfiles(self, filenames): + """ **(private)** Append a filename to the list of configuration files. """ + filenames.reverse() + for i in range(len(filenames)): + keys = CONFIGURATION_FILES.keys() + CONFIGURATION_FILES[min(keys) - 1] = filenames[i] + + def __Logger_get_ndc(self): + """ **(private)** Returns the NDC (nested diagnostic context) joined with single-spaces. """ + if (len(self.__Logger_ndc)): + return join(self.__Logger_ndc) + else: + return "" + + def __Logger_showmessage(self, message, messagesource): + """ **(private)** Writes a message to all targets set. """ + + if (isinstance(message, Exception)): + (exc_type, exc_value, tb) = sys.exc_info() + exception_summary = traceback.format_exception(exc_type, exc_value, tb) + message = 'Exception caught:\n' + for line in exception_summary: + message = "%s%s" % (message, line) + + currenttime = time() + self.__Logger_tracestack() + timedifference = "%.3f" % (currenttime - self.__Logger_timeinit) + timedifflaststep = "%.3f" % (currenttime - self.__Logger_timelaststep) + self.__Logger_timelaststep = currenttime + milliseconds = int(round((currenttime - long(currenttime)) * 1000)) + timeformat = sub("%S", "%S." + (zfill(milliseconds, 3)), self.__Logger_timeformat) + currentformattedtime = strftime(timeformat, localtime(currenttime)) + + line = self.__Logger_formatstring + line = sub("%C", str(self.__Logger_classname), line) + line = sub("%D", timedifference, line) + line = sub("%d", timedifflaststep, line) + line = sub("%F", self.__Logger_functionname, line) + line = sub("%f", self.__Logger_filename, line) + line = sub("%U", self.__Logger_module, line) + line = sub("%u", os.path.split(self.__Logger_module)[-1], line) + ndc = self.__Logger_get_ndc() + if (ndc != ""): + line = sub("%x", "%s - " % ndc, line) + else: + line = sub("%x", "", line) + message = replace(message, "\\", "\\\\") + if (self.__Logger_useansicodes == TRUE): + line = sub("%L", self.__Logger_ansi(LOG_MSG[messagesource], messagesource), line) + line = sub("%M", self.__Logger_ansi(message, messagesource), line) + else: + line = sub("%L", LOG_MSG[messagesource], line) + line = sub("%M", message, line) + line = sub("%N", str(self.__Logger_linenumber), line) + line = sub("%T", currentformattedtime, line) + line = sub("%t", `currenttime`, line) + + for i in range(len(self.__Logger_targets)): + target = self.__Logger_targets[i] + if (target == TARGET_MYSQL): + sqltime = strftime("'%Y-%m-%d', '%H:%M:%S'", localtime(currenttime)) + sqlStatement = "INSERT INTO %s (host, facility, level, date, time, program, msg) VALUES ('%s', '%s', '%s', %s, '%s', '%s')" % (self.__Logger_mysql_tablename, self.hostname, self.__Logger_functionname, LOG_MSG[messagesource], sqltime, str(self.__Logger_classname), sub("'", "`", message + " " + ndc)) + self.__Logger_mysql_cursor.execute(sqlStatement) + elif (target == TARGET_SYSLOG): + # We don't need time and stuff here + syslog.syslog(message) + elif (isinstance(target, FileAppender)): + target.writeline(line) + elif (target == sys.stdout) or (lower(target) == TARGET_SYS_STDOUT) or (lower(target) == TARGET_SYS_STDOUT_ALIAS): + sys.stdout.write("%s\n" % line) + elif (target == sys.stderr) or (lower(target) == TARGET_SYS_STDERR) or (lower(target) == TARGET_SYS_STDERR_ALIAS): + sys.stderr.write("%s\n" % line) + else: + target.write("%s\n" % line) + + def __Logger_ansi(self, text, messagesource): + """ **(private)** Converts plain text to ansi text. """ + bold = LOG_COLORS[messagesource][2] + fg = str(LOG_COLORS[messagesource][0]) + bg = LOG_COLORS[messagesource][1] + if (bold == TRUE): + fg = "%s;1" % fg + bg = bg + 10 + text = "\033[%d;%sm%s\033[0m" % (bg, fg, text) + return text + +class FileAppender: + + def __init__(self, filename, rotation = ROTATE_NONE): + """ **(private)** Class initalization & customization. """ + self.__FileAppender_filename = sub("\$HOME", get_homedirectory(), filename) + self.__FileAppender_filename = os.path.expanduser(self.__FileAppender_filename) + self.__FileAppender_filename = os.path.expandvars(self.__FileAppender_filename) + self.__FileAppender_rotation = rotation + + def __FileAppender_rotate(self, modification_time): + """ **(private)** Check, wether the file has to be rotated yet or not. """ + if (self.__FileAppender_rotation == ROTATE_DAILY): + strftime_mask = "%Y%j" + elif (self.__FileAppender_rotation == ROTATE_WEEKLY): + strftime_mask = "%Y%W" + elif (self.__FileAppender_rotation == ROTATE_MONTHLY): + strftime_mask = "%Y%m" + return (strftime(strftime_mask, localtime(time())) != strftime(strftime_mask, localtime(modification_time))) + + def __FileAppender_date_string(self, modification_time): + """ **(private)** Returns a new filename for the rotated file with the appropriate time included. """ + if (self.__FileAppender_rotation == ROTATE_DAILY): + return strftime("%Y-%m-%d", localtime(modification_time)) + elif (self.__FileAppender_rotation == ROTATE_WEEKLY): + return strftime("%Y-Week %W", localtime(modification_time)) + elif (self.__FileAppender_rotation == ROTATE_MONTHLY): + return strftime("%Y-Month %m", localtime(modification_time)) + + def get_rotation(self): + """ Returns the current rotation setting. """ + return self.__FileAppender_rotation + + def set_rotation(self, rotation): + """ Set the file rotation mode to one of ROTATE_NONE, ROTATE_DAILY, ROTATE_WEEKLY, ROTATE_MONTHLY """ + self.__FileAppender_rotation = rotation + + def write(self, text): + """ Write some text to the file appender. """ + if ((os.path.exists(self.__FileAppender_filename)) and (self.__FileAppender_rotation != ROTATE_NONE)): + statinfo = stat(self.__FileAppender_filename) + if (self.__FileAppender_rotate(statinfo[8])): + splitted = os.path.splitext(self.__FileAppender_filename) + target_file = "%s-%s%s" % (splitted[0], self.__FileAppender_date_string(statinfo[8]), splitted[1]) + rename(self.__FileAppender_filename, target_file) + file = open(self.__FileAppender_filename, "a") + file.write(text) + file.close() + + def writeline(self, text): + """ Write some text including newline to the file appender. """ + self.write("%s\n" % text) diff --git a/setup.py b/setup.py index 4ae41908..a202039f 100755 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( author = "Cyril Jaquier", author_email = "lostcontrol@users.sourceforge.net", url = "http://www.sourceforge.net/projects/fail2ban", - scripts = ['fail2ban.py'], + scripts = ['fail2ban'], py_modules = ['version'], packages = ['firewall', 'logreader', 'confreader', 'utils'] )