pax_global_header00006660000000000000000000000064124677627760014542gustar00rootroot0000000000000052 comment=4d1b4c9d784122b5c303cd9df328ec37941fbe16 denyhosts-2.10/000077500000000000000000000000001246776277600135045ustar00rootroot00000000000000denyhosts-2.10/.gitignore000066400000000000000000000000301246776277600154650ustar00rootroot00000000000000*.pyc build __pycache__ denyhosts-2.10/.travis.yml000066400000000000000000000000751246776277600156170ustar00rootroot00000000000000language: python python: - 2.6 - 2.7 script: nosetests denyhosts-2.10/CHANGELOG.txt000066400000000000000000000535301246776277600155420ustar00rootroot00000000000000DENYHOSTS CHANGELOG 2.10 ====================== - Updated example rule for PF in configuration file to make black listing attacking IPs more effective. - Added debugging info in case we cannot create a new PF table entry. - Fixed syntax for comparing suspecious logins. Avoids always testing true/false depending on Python version. - No longer require ETC_DIR in the configuration file. Use a default value "/etc" if ETC_DIR is not manually specified. - Make sure DenyHosts logs when running in foreground mode. When in foreground, warnings are logged to a file rather than outputted to terminal. Keeps things clean. - Add --unlock command line arguement to remove old lock files. - Updated README, version and Makefile with new version/maintainer information. - Added check for PAM failures on FreeBSD. This should block both failed user logins that are reported by PAM and also block repteated attempts at accessing the root account when root logins are disabled by OpenSSH. The latter does not really add more practical protection, but can prevent the connection \ attempts at the firewall level before the OpenSSH service is contacted. - Add systemd unit file, denyhosts.service 2.9 (November 3, 2014) ====================== - DenyHost now supports working with the PF packet filter, a popular firewall for FreeBSD, OpenBSD, TrueOS, PC-BSD and NetBSD. To enable PF support in DenyHost, comment out the IPTABLES option in the denyhosts.conf file and enable the PFCTL_PATH and PF_TABLE options. DenyHost will add misbehaving IP addresses to the PF table specified by "PF_TABLE". This table should be blocked using the pf.conf file. Please see the denyhosts.conf file for more information and example PF rules for blocking incoming traffic. Please note that even if /etc/hosts.deny is not used to block incoming connectins, the file should still exists or DenyHosts may throw an error. (This should be fixed in the next release.) 2.8 (June 12, 2014) =================== - Use standard errno instead of hardcoded errno value. Patch provided by Pino Toscano. - Make sure PLUGIN_DENY is called for each host we receive from the sync server. Patch provided by Sean M. Collins. - Made sure only new hosts in hosts.deny are reported as new, not all hosts. This prevents the PLUGIN_DENY plugin from getting old entries repeatedly. Patch provided by Chris Erdle. - We now check user defined regular expression filters, even if we already found a match with an existing filter. This allows the user to filter more services without using a plugin. Patch provided by Ben. - Added --purge-all command line flag to allow us to remove all old entries from the deny file without waiting. Patch provided by 9MediaCenterGUI on SourceForge. - Updated copyright information and some documentation. - Added manual page from Debian and fixed typo. Added additional command line options to man page. - Added --purgeip option to allow us to remove specific IP addresses from the blocked list at start time. Patch provided by Nelson Howell. Should close Debian bug 529089. - Updated FAILED_ENTRY_REGEX7 to be more flexible. - Added ability to use Linux iptables to block incoming connections. See IPTABLES option in the configuration file. - Made it possible to block specific ports, allowing remote hosts to conenct to some services while being blocked on others by the iptables firewall. See the BLOCKPORT option in the configuration file. 2.7 (May 18, 2014) ================== - Forked code from DenyHosts (denyhosts.sf.net) New project now maintained at denyhost.sf.net - Added private moduls patch from Marco Bertorello. Loads modules from /usr/share/denyhosts - Place config, lock and executable file in more standard locations. Patch provided by Marco Bertorello. - Fixed configuration (denyhosts.cfg-dist) to better support Debian and Ubuntu. Patch supplied by Marco Nenciarini. - Added warning to migrate switch. Patch provided by Marco Bertorello. - Avoid installing unwanted files (extra scripts and changelog). Patch provided by Marco Nenciarini. - Fix bug which would not recognize an attack on the root user account. Patch provided by Kyle Willmon. - Fix pattern matching bug (CVE-2007-4323). Patch provided by Nico Golde. - Added foreground mode for debugging. Patch supplied by Marco Bertorello. - Applied patch to fix plugin execution. Patched provided by Marco Bertorello. - Added patch to prevent DenyHosts from running with a double --config switch. Patch provided by Maro Bertorello. - Convert path of "env" from /bin/env to /usr/bin/env Patch provided by Kyle Willmon. - Added patch to perform missing bounds check in Purge action. Provided by Kyle Willmon. - Added patch to include SYNC_PROXY_SERVER configuration option. Provided by Kyle Willmon. - Change HOSTNAME_LOOKUP to default to "NO". Will save time. Also brings us into closer alignment with FreeBSD patches. - Added /usr/sbin/nologin to restricted_from_passwd script. Requirement from FreeBSD patch set. - Added variable "ETC_DIR" which dictates the location of configuration files. This should usually be set to /etc or /usr/local/etc - The restricted-usernames file is now loaded from the "ETC_DIR" directory, rather than from "WORK_DIR" to avoid this human-made configuration file from being over-writeen. Closes Ubuntu bug #675034 - Confirm setting timestamp over-writes old tiemstamp file. Closes Ubuntu bug #564476 - Applied advanced pattern check for authentication file which takes into account alternative port numbers. Patch provided by Helmut Grohne. - Updated license and readme files. - Updated help output from DenyHost script to include --config tip. 2.6 (Dec 7, 2006) ================== - security fix: malicious users can cause a DoS of ssh. for more info: http://nvd.nist.gov/nvd.cfm?cvename=CVE-2006-6301 - fixed bug in regex.py: 2 failed entry regexes weren't included properly in the hash - fixed bug in denyhosts.py: attribute error: self.__sync_download 2.5 (June 21, 2006) ==================== - ADMIN_EMAIL pref can now contain multiple email addresses delimited by a comma (white space is optional). eg. foo@bar.com, bar@foo.com, foobar@foo.com - fixed bug in denyfileutil: 'timestamp' is now initialized properly - daemon-control-dist: modified to work w/ non-default python versions. You must change the PYTHON_BIN and #!/bin/env/python references if appropriate. - added a debug message when loading allowed-hosts fails. - fixed bug when reporting suspicious login activity. 2.4b (April 9, 2006) =================== - fixed missing "self." in loginattempt.py 2.4 (April 9, 2006) =================== - added: PURGE_THRESHOLD setting (defaults to 0, or "none") defines the maximum times a host will be purged. Once this amount has been exceeded then this host will not be purged. - added: SYSLOG_REPORT option which, if enabled, will send the denied hosts report to syslog (in addition to -or- instead of email, depending on your other settings). Based on patch submission by Michael Still. - fix: restricted usernames were being added to wrong file 2.3 (April 4, 2006) =================== - fix: denied hosts weren't being re-added after purge unless daemon was restarted after the purge. Thanks to Steven Finnegan for recognizing the problem. - fix: daemon-control-dist should now behave correctly on FreeBSD systems 2.2 (March 13, 2006) ==================== - added environment variable substitution of preference values. Each value that contains $[SOME_NAME] will have $[SOME_NAME] substituted with the value of the environment variable SOME_NAME. eg. SMTP_SUBJECT = DenyHosts Report - $[HOSTNAME] will result in the following expansion: SMTP_SUBJECT = DenyHosts Report - foo on a host that is named "foo" - added configuaration option SMTP_DATE_FORMAT which allows you to override the DenyHosts "Date:" field when sending reports via email - fixed bug in prefs.py: AGE_RESET_RESTRICTED was not being converted to seconds - fixed bug in util.py: exceptions raised by smtp.quit() are now handled - fixed bug in denyhosts.py: missing import of "sync" line 2.1 (February 9, 2006) ======================= - added command line flag --sync which runs DenyHosts (command line/cron version) in synchronization mode. - added SYNC_DOWNLOAD_RESILIENCY setting to limit download synchronization data to attacks that have lasted longer than this value. That is, if the centralized denyhosts.net server records an attack at 2 PM and then again at 5 PM, specifying a SYNC_DOWNLOAD_RESILIENCY = 4h will not download this ip address. However, if the attacker is recorded again at 6:15 PM then the ip address will be downloaded by your DenyHosts instance. This value is used in conjunction with the SYNC_DOWNLOAD_THRESHOLD and only hosts that satisfy both values will be downloaded. This value has no effect if SYNC_DOWNLOAD_THRESHOLD = 1 and refers to the timespan between the attackers first known attack and their most recent attack. Refer to http://www.denyhosts.net/faq.html#sync_download_resiliency - added RESET_ON_SUCCESS option which, when set to "yes" will automatically reset the counter for the connecting ip address to 0 if the login was successful. The default is "no". This may be helpful in the event that a user occassionally mistypes their password. See also the AGE_RESET_* options. Refer to http://www.denyhosts.net/faq.html#reset_on_success - bug fix: if synchronization mode is disabled (default) then denied hosts will not be added to the SYNC_HOSTS staging file. - modified daemon-control-dist to use the 'ps' command (in the event that the /proc directory does not exist) to determine whether the DenyHosts process is still running. - modified daemon-control-dist to infer 'start' and 'stop' from symbolically linked programs in the event that the script is launched w/o arguments. The linked filenames must begin with either an "S" (start) or a "K" (kill). - added "restricted" user concept and functionality such that usernames defined as restricted (such as "mysql", "lpd", etc...) which are not intended for login purposes will be denied after DENY_THRESHOLD_RESTRICTED failed attempts. This option is based on ideas & suggestions from Ken Key and Dave Ingram. Refer to http://www.denyhosts.net/faq.html#restricted - added DENY_THRESHOLD_RESTRICTED (for users such as apache, mysql, etc...). Defaults to DENY_THRESHOLD_ROOT setting. - added AGE_RESET_RESTRICTED parameter - added scripts/restricted_from_passwd.py which is suitable for generating a list of restricted users based on /etc/passwd's login shells (such as /sbin/nologin). - added scripts/restricted_from_invalid.py which is suitable for generating a list of restricted users based on WORK_DIR/users-invalid contents. - if synchronization fails, a stacktrace will be printed to the log file (or console) which may be useful for isolating the problem. 2.0 (February 5, 2006) ======================= - DenyHosts has a new address: http://www.denyhosts.net - Added synchronization mode capability which allows all DenyHosts daemons the ability to seemlessly share denied host data. See this faq entry for more information: http://www.denyhosts.net/faq.html#sync - Added the configuration option USERDEF_FAILED_ENTRY_REGEX which allows the DenyHost user the ability to add custom regular expressions in order to block potential hackers. See this faq entry for more information: http://www.denyhosts.net/faq.html#userdef_regex - FAILED_ENTRY_REGEX5 now handles more login failures such as AllowGroups and AllowUsers. previously applied only to AllowGroups. - Added FAILED_ENTRY_REGEX6 to handle "Did not receive identification string from ..." messages. - Fixed issue when a purged host was re-added. http://sourceforge.net/tracker/index.php?func=detail&aid=1345437&group_id=131204&atid=720419 - fixed file permissions issue when creating some temp files - Log format message can be customized using the new DAEMON_LOG_MESSAGE_FORMAT in addition to the existing DAEMON_LOG_TIME_FORMAT option. - added 1 second sleep between stop & start in daemon-control-dist for "restart" command. - Added ShoreWall plugins (thanks to Stéphane LeDauphin for the contribution). - Fixed licensing ambiguity (DenyHosts is GPL v2). - Removed test.py~ from plugins. 1.1.4 (January 9, 2006) ======================= - Added AllowedGroups patch from Marlon Paulse. DenyHosts will now: "detect login attempts to user accounts that are not members of groups listed as 'AllowedGroups' in the sshd configuration file." - Updated denyhosts.cfg-sample such that WORK_DIR parameter points to an absolute pathname rather than a relative one. - Fixed email bug: failure to connect to smtp server is now handled properly. - Fixed plugin bug: PLUGIN_DENY is now only invoked when new hosts are denied. 1.1.3 ===== - Bug fix: duplicate entries were being added to allowed-warned-hosts file as well as sending out duplicate email messages. - If the prefs parameter DENY_THRESHOLD_INVALID isn't found but the deprecated DENY_THRESHOLD is found then the user is alerted of this. Additionally, the latter value will be used for the missing DENY_THRESHOLD_INVALID parameter such that DenyHosts will continue to run without intervention. 1.1.2 ===== - Fixed offset issue when running in daemon mode, the first pass of data discovered was processed twice. - All values that are converted to seconds in the prefs.py module when the denyhosts.cfg file is processed. Previously, these values were inefficiently converted each time that they were used. - Migrated some internal prefs.py data structures to sets rather than tuples for performance benefit. - Better handling of illegal values in denyhosts.cfg. - Fixed a bug in non-daemon mode when --purge flag was used. 1.1.1 ===== - Daemon mode now closed standard file descriptors and detaches from tty based on patch from James Abbatiello. - lock file is now created with 0644 permissions based on patch from James Abbatiello. - added optional SMTP_USERNAME and SMTP_PASSWORD parameters and support for authenticating SMTP connections (when these parameters are defined). - fixed bug when checking for required configuration parameters. 1.1.0 ===== - configuration parameter DENY_THRESHOLD has been renamed DENY_THRESHOLD_INVALID - added configuration parameters AGE_RESET_ROOT, AGE_RESET_INVALID and AGE_RESET_VALID - added the ability to automatically reset host login attempts after a given time period (age_reset) has elapsed. See the FAQ entry: http://denyhosts.sourceforge.net/faq.html#age_reset - most WORK_DIR data files will be automatically migrated to the new data format to support the above functionality. A timestamp field has been appended to each line, such that the new format is: key:# of attempts:timestamp - added chkconfig compatibility to the daemon-control-dist script. Thanks to Simon Amor for the contribution. - added optional configuration parameters PLUGIN_DENY and PLUGIN_PURGE - added plugin support. See the FAQ entry: http://denyhosts.sourceforge.net/faq.html#plugin 1.0.3 ===== - hostnames can now be specified in allowed-hosts file based on a contribution from Mathias Wagner. - hostnames specified in allowed-hosts are looked up to determine ip addresses - added optional pref: ALLOWED_HOSTS_HOSTNAME_LOOKUP - if ALLOWED_HOSTS_HOSTNAME_LOOKUP is true, then each ip address in allowed-hosts is looked up to determine it's hostname 1.0.2 ===== - fixed issue where multiple instances of the same host were added to /etc/hosts.deny (due to exceeding multiple DENY_THRESHOLD* settings simultaneously). - bz2 support is disabled if "import bz2" fails - displays more useful error message if Python version is incompatible with DenyHosts. - changed format of timestamp in email messages sent by DenyHosts based on patch submitted by Rick Holbert (now uses %z instead of %Z) 1.0.1 ===== - fixed bug relating to empty hosts-root file - added optional configuration parameter: DAEMON_LOG_TIME_FORMAT for specifying the timestamp in the DAEMON_LOG file - now uses SIGTERM to stop daemon (rather than SIHGUP) based on the suggestion of Xavier Le Vourch - daemon-control-dist has been updated to send SIGTERM on stop() 1.0.0 ===== - added new configuration option, DENY_THRESHOLD_VALID - added new configuration option, DENY_THRESHOLD_ROOT - added "hosts-valid" output file (relative to WORK_DIR) - added "hosts-root" output file (relative to WORK_DIR) - modified loginattempt.py to handle valid/invalid login hosts independently. - added 'condrestart' option to daemon-control-dist as per patch from Jason L Tibbitts III - fixed double usage() method from being displayed by daemon-control-dist 0.9.9 ===== - if upgrading DenyHosts to this version and PURGE_DENY is set then you must invoke with the --upgrade099 flag before using. eg. $ denyhosts.py --upgrade099 For further details, refer to: http://denyhosts.sourceforge.net/faq.html#upgrade099 - fixed bug: missing "import socket" in report.py - fixed bug: invalid comment format in /etc/hosts.deny for timestamping entries - /etc/hosts.deny entries are timestamped immediately before the entry now (rather than inline, in earlier versions). The timestamped lines also contain the entry data for verification. If DenyHosts determines that a purge is necessary than the verification info is compared to the actual entry line (ie. the next line). If the two entries match, the lines are purged. Otherwise a warning is output. The verification algorithm was based on a suggestion from Jim Cheetham. - sshd related regex patterns can now be configured within the DenyHosts configuration file in the event that the default (eg. DenyHosts/regex.py) patterns do not successfully parse the end user's tcp_wrappers, operating system and/or logging implementation. For details refer to: http://denyhosts.sourceforge.net/faq.html#custom_regex 0.9.8 ===== - fixed purge bug in daemon mode (using the wrong purge value to determine which records should be purged.) 0.9.7 ===== - fixed bug wrt handling of rotated files. DenyHosts now compares the inodes to determine if a file was rotated. If detected, the previous file is closed and the new file opened. - restructred daemon code block (added additional methods) 0.9.6 ===== - Command line that invoked --daemon is now recorded in the output log (useful for debugging) - The daemon-control-dist script now optionally accepts command line args to be passed to the start and restart functions. eg. daemon-control start --config=test.cfg --debug 0.9.5 ===== - If no denied hosts or suspicious logins are found, then there is no output in normal mode (only in debug mode). This limits the amount of data added to the log when in --daemon mode. See: http://sourceforge.net/tracker/index.php?func=detail&aid=1243689&group_id=131204&atid=720422 0.9.4 ===== - Fixed duplicate email report bug in --daemon mode. 0.9.3 ===== - Fixed --daemon mode bug which occurred during purging entries - Added more useful exception reporting for daemon mode 0.9.2 ===== - Daemon configuration params DAEMON_SLEEP and DAEMON_PURGE can now be given in the same format accepted by PURGE_DENY (eg. 1d, 5h, 1y, etc...). If no unit qualifier (eg. s, m, d, h, w, y) is given then 's' (seconds) is assumed. 0.9.1 ===== - Running in daemon mode now dumps preferences to log file - Added AbusiveHosts class - Added additional parameter to Purge constructor - Purged entries from HOSTS_DENY are also removed from WORK_DIR/hosts SF bug: http://sourceforge.net/tracker/index.php?func=detail&aid=1243093&group_id=131204&atid=720419 0.9.0 ===== - Added daemon mode - Added several optional parameters to the configuration file for daemon mode use 0.8.2 ===== - Maintenance release - Moved most classes to new modules - Now uses the python logging package for some output 0.8.1 ===== - Revised LockFile class to do perform exclusive locking -- deprecated the --unlock flag since it is no longer necessary 0.8.0 ===== - Added LOCK_FILE configuration parameter and support such that only one instance of DenyHosts can be running at any given time - Added PURGE_DENY configuration parameter and support for purging entries from the HOSTS_DENY file that have exceeded the PURGE_DENY value -- Added the ability to migrate HOSTS_DENY file such that all entries are suitable for purging. -- Added --purge, --migrate and --unlock command line arguments 0.7.3 ===== - Prefs change (reqd fields can be blank) 0.7.2 ===== - Added support for using an alternate deny file with FreeBSD based on feed back from Alesha Oparin 0.7.1 ===== - fixed blank BLOCK_SERVICE bug 0.7.0 ===== - Added support for the metalog logger based on a patch contributed by Mike Kelly. - Added support for FreeBSD based on input from Francesca Smith - Added support for catching hostname entries (in addition to IP addresses) based on a patch from Christian Smyth - Added support for using an alternative hosts deny file where only the offending ip address is listed. This feature is based on a patch contributed by John Meinel Jr. - Added support for processing gzip'ed logfiles based on feature request by Brian McGraw. 0.6.0 ===== - Modified email message date/time format to be RFC-2822 compliant. Incorporated patch contributed by Rick Holbert. - Added reverse dns lookups and the HOSTNAME_LOOKUP for enabling/disabling them. Incorporated feature request of Ron Joffe. - Added additional config option (SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS) for reporting (or not) suspicious login activity. This feature is based on feedback from Ron Joffe. - Updated regex for matching non-existent user break-ins. Previously, "invalid" was matched against. Now, both "invalid" and "illegal" should match. This fix is based on email from Scott Hunziker. denyhosts-2.10/DenyHosts/000077500000000000000000000000001246776277600154245ustar00rootroot00000000000000denyhosts-2.10/DenyHosts/__init__.py000066400000000000000000000000151246776277600175310ustar00rootroot00000000000000__all__ = [] denyhosts-2.10/DenyHosts/allowedhosts.py000066400000000000000000000077011246776277600205130ustar00rootroot00000000000000import logging import os from socket import getfqdn, gethostbyname from constants import ALLOWED_HOSTS, ALLOWED_WARNED_HOSTS from regex import ALLOWED_REGEX from util import is_true logger = logging.getLogger("AllowedHosts") debug, warn = logger.debug, logger.warn class AllowedHosts(object): def __init__(self, prefs): debug("initializing AllowedHosts") work_dir = prefs.get("WORK_DIR") self.hostname_lookup = is_true(prefs.get("ALLOWED_HOSTS_HOSTNAME_LOOKUP")) self.allowed_path = os.path.join(work_dir, ALLOWED_HOSTS) self.warned_path = os.path.join(work_dir, ALLOWED_WARNED_HOSTS) self.allowed_hosts = {} self.warned_hosts = {} self.new_warned_hosts = [] self.load_hosts() self.load_warned_hosts() debug("done initializing AllowedHosts") def __contains__(self, ip_addr): if self.allowed_hosts.has_key(ip_addr): return 1 else: return 0 def dump(self): print "Dumping AllowedHosts" print self.allowed_hosts.keys() def load_hosts(self): try: fp = open(self.allowed_path, "r") except Exception, e: debug("Could not open %s - %s", self.allowed_path, str(e)) return for line in fp: line = line.strip() if not line or line[0] == '#': continue m = ALLOWED_REGEX.match(line) debug("line: %s - regex match? %s", line, m is not None) if m: # line contains an ip address first3 = m.group('first_3bits') fourth = m.group('fourth') wildcard = m.group('ip_wildcard') ip_range = m.group('ip_range') if fourth: self.allowed_hosts["%s%s" % (first3, fourth)] = 1 self.add_hostname("%s%s" % (first3, fourth)) elif wildcard: for i in range(256): self.allowed_hosts["%s%s" % (first3, i)] = 1 self.add_hostname("%s%s" % (first3, i)) else: start, end = ip_range.split("-") for i in range(int(start), int(end)): self.allowed_hosts["%s%d" % (first3, i)] = 1 self.add_hostname("%s%s" % (first3, i)) else: # assume that line contains hostname self.allowed_hosts[line] = 1 try: # lookup ip address of host ip = gethostbyname(line) self.allowed_hosts[ip] = 1 except: pass fp.close() debug("allowed_hosts: %s", self.allowed_hosts.keys()) def add_hostname(self, ip_addr): if not self.hostname_lookup: return else: hostname = getfqdn(ip_addr) if hostname != ip_addr: self.allowed_hosts[hostname] = 1 def add_warned_host(self, host): #debug("warned_hosts: %s", self.warned_hosts.keys()) if host not in self.warned_hosts: debug("%s not in warned hosts" % host) self.new_warned_hosts.append(host) self.warned_hosts[host] = None def get_new_warned_hosts(self): return self.new_warned_hosts def load_warned_hosts(self): try: fp = open(self.warned_path, "r") for line in fp: self.warned_hosts[line.strip()] = None fp.close() except IOError: warn("Couldn't load warned hosts from %s" % self.warned_path) def save_warned_hosts(self): if not self.new_warned_hosts: return try: fp = open(self.warned_path, "a") for host in self.new_warned_hosts: fp.write("%s\n" % host) fp.close() except Exception, e: print e def clear_warned_hosts(self): self.new_warned_hosts = [] denyhosts-2.10/DenyHosts/constants.py000066400000000000000000000033141246776277600200130ustar00rootroot00000000000000import sys ################################################################################# # These files will be created relative to prefs WORK_DIR # ################################################################################# SECURE_LOG_OFFSET = "offset" DENIED_TIMESTAMPS = "denied-timestamps" ABUSIVE_HOSTS_INVALID = "hosts" ABUSIVE_HOSTS_VALID = "hosts-valid" ABUSIVE_HOSTS_ROOT = "hosts-root" ABUSIVE_HOSTS_RESTRICTED = "hosts-restricted" ABUSED_USERS_INVALID = "users-invalid" ABUSED_USERS_VALID = "users-valid" ABUSED_USERS_AND_HOSTS = "users-hosts" SUSPICIOUS_LOGINS = "suspicious-logins" # successful logins AFTER invalid # attempts from same host ALLOWED_HOSTS = "allowed-hosts" ALLOWED_WARNED_HOSTS = "allowed-warned-hosts" RESTRICTED_USERNAMES = "restricted-usernames" SYNC_TIMESTAMP = "sync-timestamp" SYNC_HOSTS = "sync-hosts" SYNC_HOSTS_TMP = "sync-hosts.tmp" SYNC_RECEIVED_HOSTS = "sync-received" PURGE_HISTORY = "purge-history" ################################################################################# # Miscellaneous constants # ################################################################################# CONFIG_FILE = "/etc/denyhosts.conf" DENY_DELIMITER = "# DenyHosts:" ENTRY_DELIMITER = " | " TIME_SPEC_LOOKUP = { 's': 1, # s 'm': 60, # minute 'h': 3600, # hour 'd': 86400, # day 'w': 604800, # week 'y': 31536000, # year } SYNC_MIN_INTERVAL = 300 # 5 minutes plat = sys.platform if plat.startswith("freebsd"): # this has no effect if BLOCK_SERVICE is empty BSD_STYLE = " : deny" else: BSD_STYLE = "" denyhosts-2.10/DenyHosts/counter.py000066400000000000000000000037401246776277600174610ustar00rootroot00000000000000import logging import time debug = logging.getLogger("counter").debug # TODO define __eq__ for this class class CounterRecord(object): def __init__(self, count=0, date=None): self.__count = count if not date: self.__date = time.asctime() else: self.__date = date def __str__(self): return "%d:%s" % (self.__count, self.__date) def __repr__(self): return "CountRecord <%d - %s>" % (self.__count, self.__date) def __add__(self, increment): # AAAAH! # Expressions like `a + 4` are usually assumed to not have any side effects, # but this is not the case with CounterRecord objects. With `c = CounterRecord()`, # simply evaluating `c + 1` will increment c.__count by 1. This is horrifying. self.__count += increment self.__date = time.asctime() return self def get_count(self): return self.__count def get_date(self): return self.__date def reset_count(self): self.__count = 0 def age_count(self, age): cutoff = long(time.time()) - age epoch = time.mktime(time.strptime(self.__date)) #debug("cutoff : %d", cutoff) #debug("epoch : %d", epoch) if cutoff > epoch: self.__count = 0 # TODO replace this with collections.defaultdict class Counter(dict): """ Behaves like a dictionary, except that if the key isn't found, 0 is returned rather than an exception. This is suitable for situations like: c = Counter() c['x'] += 1 """ def __init__(self): dict.__init__(self) def __getitem__(self, k): try: return dict.__getitem__(self, k) except KeyError: count_rec = CounterRecord(0) #debug("%s - %s", k, count_rec) self.__setitem__(k, count_rec) #debug("dict: %s", dict.values(self)) #debug("count_rec: %s", count_rec) return count_rec denyhosts-2.10/DenyHosts/daemon.py000066400000000000000000000101351246776277600172410ustar00rootroot00000000000000"""Disk And Execution MONitor (Daemon) Default daemon behaviors (they can be modified): 1.) Ignore SIGHUP signals. 2.) Default current working directory to the "/" directory. 3.) Set the current file creation mode mask to 0. 4.) Close all open files (0 to [SC_OPEN_MAX or 256]). 5.) Redirect standard I/O streams to "/dev/null". Failed fork() calls will return a tuple: (errno, strerror). This behavior can be modified to meet your program's needs. Resources: Advanced Programming in the Unix Environment: W. Richard Stevens Unix Network Programming (Volume 1): W. Richard Stevens http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 """ __author__ = "Chad J. Schroeder" import os import signal import sys def createDaemon(): """Detach a process from the controlling terminal and run it in the background as a daemon. """ 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("/") # Give the child complete control over permissions. os.umask(0) else: os._exit(0) # Exit parent (the first child) of the second child. else: os._exit(0) # Exit parent of the first child. std_fds = 3 # 0,1,2 for fd in range(0, std_fds): 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 0 if __name__ == "__main__": # Self-test. retCode = createDaemon() # If executed with superuser privilages, there should be a new file in the # "/" directory. It should contain the function's return code, the daemon's # PID, PPID, and PGRP. Its PID should not equal its PGRP, and its PPID # should equal 1. If it's executed without superuser privilages, the file # won't be created and no errors will be reported. open("createDaemon.log", "w").write("rc: %s; pid: %d; ppid: %d; pgrp: %d" % \ (retCode, os.getpid(), os.getppid(), os.getpgrp())) sys.exit(0) denyhosts-2.10/DenyHosts/deny_hosts.py000066400000000000000000000502401246776277600201560ustar00rootroot00000000000000import logging import gzip import os import signal from stat import ST_SIZE, ST_INO import time try: import bz2 HAS_BZ2 = True except ImportError: HAS_BZ2 = False from allowedhosts import AllowedHosts from constants import * from daemon import createDaemon from denyfileutil import Purge from filetracker import FileTracker from loginattempt import LoginAttempt import plugin from regex import * from report import Report from restricted import Restricted from sync import Sync from util import die, is_true, parse_host, send_email from version import VERSION debug = logging.getLogger("denyhosts").debug info = logging.getLogger("denyhosts").info error = logging.getLogger("denyhosts").error class DenyHosts(object): def __init__(self, logfile, prefs, lock_file, ignore_offset=0, first_time=0, noemail=0, daemon=0, foreground=0): self.__denied_hosts = {} self.__prefs = prefs self.__lock_file = lock_file self.__first_time = first_time self.__noemail = noemail self.__report = Report(prefs.get("HOSTNAME_LOOKUP"), is_true(prefs['SYSLOG_REPORT'])) self.__daemon = daemon self.__foreground = foreground self.__sync_server = prefs.get('SYNC_SERVER') self.__sync_upload = is_true(prefs.get("SYNC_UPLOAD")) self.__sync_download = is_true(prefs.get("SYNC_DOWNLOAD")) self.__iptables = prefs.get("IPTABLES") self.__blockport = prefs.get("BLOCKPORT") self.__pfctl = prefs.get("PFCTL_PATH") self.__pftable = prefs.get("PF_TABLE") r = Restricted(prefs) self.__restricted = r.get_restricted() info("restricted: %s", self.__restricted) self.init_regex() try: self.file_tracker = FileTracker(self.__prefs.get('WORK_DIR'), logfile) except Exception, e: self.__lock_file.remove() die("Can't read: %s" % logfile, e) self.__allowed_hosts = AllowedHosts(self.__prefs) if ignore_offset: last_offset = 0 else: last_offset = self.file_tracker.get_offset() if last_offset is not None: self.get_denied_hosts() info("Processing log file (%s) from offset (%ld)", logfile, last_offset) offset = self.process_log(logfile, last_offset) if offset != last_offset: self.file_tracker.save_offset(offset) last_offset = offset elif not daemon: info("Log file size has not changed. Nothing to do.") if daemon and not foreground: info("launching DenyHosts daemon (version %s)..." % VERSION) #logging.getLogger().setLevel(logging.WARN) # remove lock file since createDaemon will # create a new pid. A new lock # will be created when runDaemon is invoked self.__lock_file.remove() retCode = createDaemon() if retCode == 0: self.runDaemon(logfile, last_offset) else: die("Error creating daemon: %s (%d)" % (retCode[1], retCode[0])) elif foreground: info("launching DenyHost (version %s)..." % VERSION) self.__lock_file.remove() self.runDaemon(logfile, last_offset) def killDaemon(self, signum, frame): debug("Received SIGTERM") info("DenyHosts daemon is shutting down") # signal handler # self.__lock_file.remove() # lock will be freed on SIGTERM by denyhosts.py # exception handler (SystemExit) sys.exit(0) def toggleDebug(self, signum, frame): level = logging.getLogger().getEffectiveLevel() if level == logging.INFO: level = logging.DEBUG name = "DEBUG" else: level = logging.INFO name = "INFO" info("setting debug level to: %s", name) logging.getLogger().setLevel(level) def runDaemon(self, logfile, last_offset): #signal.signal(signal.SIGHUP, self.killDaemon) signal.signal(signal.SIGTERM, self.killDaemon) signal.signal(signal.SIGUSR1, self.toggleDebug) info("DenyHost daemon is now running, pid: %s", os.getpid()) info("send daemon process a TERM signal to terminate cleanly") info(" eg. kill -TERM %s", os.getpid()) self.__lock_file.create() info("monitoring log: %s", logfile) daemon_sleep = self.__prefs.get('DAEMON_SLEEP') purge_time = self.__prefs.get('PURGE_DENY') sync_time = self.__prefs.get('SYNC_INTERVAL') info("sync_time: %s", str(sync_time)) if purge_time: daemon_purge = self.__prefs.get('DAEMON_PURGE') daemon_purge = max(daemon_sleep, daemon_purge) purge_sleep_ratio = daemon_purge / daemon_sleep self.purge_counter = 0 info("daemon_purge: %ld", daemon_purge) info("daemon_sleep: %ld", daemon_sleep) info("purge_sleep_ratio: %ld", purge_sleep_ratio) else: purge_sleep_ratio = None info("purging of %s is disabled", self.__prefs.get('HOSTS_DENY')) if sync_time and self.__sync_server: if sync_time < SYNC_MIN_INTERVAL: info("SYNC_INTERVAL (%d) should be atleast %d", sync_time, SYNC_MIN_INTERVAL) sync_time = SYNC_MIN_INTERVAL sync_time = max(daemon_sleep, sync_time) info("sync_time: : %ld", sync_time) sync_sleep_ratio = sync_time / daemon_sleep self.sync_counter = 0 info("sync_sleep_ratio: %ld", sync_sleep_ratio) else: sync_sleep_ratio = None info("denyhost synchronization disabled") self.daemonLoop(logfile, last_offset, daemon_sleep, purge_time, purge_sleep_ratio, sync_sleep_ratio) def daemonLoop(self, logfile, last_offset, daemon_sleep, purge_time, purge_sleep_ratio, sync_sleep_ratio): fp = open(logfile, "r") inode = os.fstat(fp.fileno())[ST_INO] while 1: try: curr_inode = os.stat(logfile)[ST_INO] except OSError: info("%s has been deleted", logfile) self.sleepAndPurge(daemon_sleep, purge_time, purge_sleep_ratio) continue if curr_inode != inode: info("%s has been rotated", logfile) inode = curr_inode try: fp.close() except IOError: pass fp = open(logfile, "r") # this ultimately forces offset (if not 0) to be < last_offset last_offset = sys.maxint offset = os.fstat(fp.fileno())[ST_SIZE] if last_offset is None: last_offset = offset if offset > last_offset: # new data added to logfile debug("%s has additional data", logfile) self.get_denied_hosts() last_offset = self.process_log(logfile, last_offset) self.file_tracker.save_offset(last_offset) elif offset == 0: # log file rotated, nothing to do yet... # since there is no first_line debug("%s is empty. File was rotated", logfile) elif offset < last_offset: # file was rotated or replaced and now has data debug("%s most likely rotated and now has data", logfile) last_offset = 0 self.file_tracker.update_first_line() continue self.sleepAndPurge(daemon_sleep, purge_time, purge_sleep_ratio, sync_sleep_ratio) def sleepAndPurge(self, sleep_time, purge_time, purge_sleep_ratio = None, sync_sleep_ratio = None): time.sleep(sleep_time) if purge_time: self.purge_counter += 1 if self.purge_counter == purge_sleep_ratio: try: purge = Purge(self.__prefs, purge_time) except Exception, e: logging.getLogger().exception(e) raise self.purge_counter = 0 if sync_sleep_ratio: #debug("sync count: %d", self.sync_counter) self.sync_counter += 1 if self.sync_counter == sync_sleep_ratio: try: sync = Sync(self.__prefs) if self.__sync_upload: debug("sync upload") timestamp = sync.send_new_hosts() if self.__sync_download: debug("sync download") new_hosts = sync.receive_new_hosts() if new_hosts: info("received new hosts: %s", str(new_hosts)) self.get_denied_hosts() self.update_hosts_deny(new_hosts) sync.xmlrpc_disconnect() except Exception, e: logging.getLogger().exception(e) raise self.sync_counter = 0 def get_denied_hosts(self): self.__denied_hosts = {} for line in open(self.__prefs.get('HOSTS_DENY'), "r"): if line[0] not in ('#', '\n'): idx = line.find('#') if idx != 1: line = line[:idx] try: host = parse_host(line) self.__denied_hosts[host] = 0 if host in self.__allowed_hosts: self.__allowed_hosts.add_warned_host(host) except Exception: pass new_warned_hosts = self.__allowed_hosts.get_new_warned_hosts() if new_warned_hosts: self.__allowed_hosts.save_warned_hosts() text = """WARNING: The following hosts appear in %s but should be allowed based on your %s file""" % (self.__prefs.get("HOSTS_DENY"), os.path.join(self.__prefs.get("WORK_DIR"), ALLOWED_HOSTS)) self.__report.add_section(text, new_warned_hosts) self.__allowed_hosts.clear_warned_hosts() def update_hosts_deny(self, deny_hosts): if not deny_hosts: return None, None #info("keys: %s", str( self.__denied_hosts.keys())) new_hosts = [host for host in deny_hosts if not self.__denied_hosts.has_key(host) and host not in self.__allowed_hosts] debug("new hosts: %s", str(new_hosts)) try: fp = open(self.__prefs.get('HOSTS_DENY'), "a") status = 1 except Exception, e: print e print "These hosts should be manually added to", print self.__prefs.get('HOSTS_DENY') fp = sys.stdout status = 0 write_timestamp = self.__prefs.get('PURGE_DENY') is not None for host in new_hosts: block_service = self.__prefs.get('BLOCK_SERVICE') if block_service: block_service = "%s: " % block_service output = "%s%s%s" % (block_service, host, BSD_STYLE) else: output = "%s" % host if write_timestamp: fp.write("%s %s%s%s\n" % (DENY_DELIMITER, time.asctime(), ENTRY_DELIMITER, output)) fp.write("%s\n" % output) plugin_deny = self.__prefs.get('PLUGIN_DENY') if plugin_deny: plugin.execute(plugin_deny, new_hosts) if self.__iptables: debug("Trying to create iptables rules") try: for host in new_hosts: my_host = str(host) if self.__blockport: new_rule = self.__iptables + " -I INPUT -p tcp --dport " + self.__blockport + " -s " + my_host + " -j DROP" else: new_rule = self.__iptables + " -I INPUT -s " + my_host + " -j DROP" debug("Running iptabes rule: %s", new_rule) info("Creating new firewall rule %s", new_rule) os.system(new_rule); except Exception, e: print e print "Unable to write new firewall rule." if self.__pfctl and self.__pftable: debug("Trying to update PF table.") try: for host in new_hosts: my_host = str(host) new_rule = self.__pfctl + " -t " + self.__pftable + " -T add " + my_host debug("Running PF update rule: %s", new_rule) info("Creating new PF rule %s", new_rule) os.system(new_rule); except Exception, e: print e print "Unable to write new PF rule." debug("Unable to create PF rule. %s", e) if fp != sys.stdout: fp.close() return new_hosts, status def is_valid(self, rx_match): invalid = 0 try: if rx_match.group("invalid"): invalid = 1 except Exception: invalid = 1 return invalid def process_log(self, logfile, offset): try: if logfile.endswith(".gz"): fp = gzip.open(logfile) elif logfile.endswith(".bz2"): if HAS_BZ2: fp = bz2.BZ2File(logfile, "r") else: raise Exception, "Can not open bzip2 file (missing bz2 module)" else: fp = open(logfile, "r") except Exception, e: print "Could not open log file: %s" % logfile print e return -1 try: fp.seek(offset) except IOError: pass suspicious_always = is_true(self.__prefs.get('SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS')) login_attempt = LoginAttempt(self.__prefs, self.__allowed_hosts, suspicious_always, self.__first_time, 1, # fetch all self.__restricted) for line in fp: success = invalid = 0 m = None sshd_m = self.__sshd_format_regex.match(line) if sshd_m: message = sshd_m.group('message') # did this line match any of the fixed failed regexes? for i in FAILED_ENTRY_REGEX_RANGE: rx = self.__failed_entry_regex_map.get(i) if rx is None: continue m = rx.search(message) if m: invalid = self.is_valid(m) break else: # didn't match any of the failed regex'es, was it succesful? m = self.__successful_entry_regex.match(message) if m: success = 1 # otherwise, did the line match one of the userdef regexes? if not m: for rx in self.__prefs.get('USERDEF_FAILED_ENTRY_REGEX'): m = rx.search(line) if m: #info("matched: %s" % rx.pattern) invalid = self.is_valid(m) break if not m: # line isn't important continue try: user = m.group("user") except Exception: user = "" try: host = m.group("host") except Exception: error("regex pattern ( %s ) is missing 'host' group" % m.re.pattern) continue debug ("user: %s - host: %s - success: %d - invalid: %d", user, host, success, invalid) login_attempt.add(user, host, success, invalid) offset = fp.tell() fp.close() login_attempt.save_all_stats() deny_hosts = login_attempt.get_deny_hosts() #print deny_hosts new_denied_hosts, status = self.update_hosts_deny(deny_hosts) if new_denied_hosts: if not status: msg = "WARNING: Could not add the following hosts to %s" % self.__prefs.get('HOSTS_DENY') else: msg = "Added the following hosts to %s" % self.__prefs.get('HOSTS_DENY') self.__report.add_section(msg, new_denied_hosts) if self.__sync_server: self.sync_add_hosts(new_denied_hosts) plugin_deny = self.__prefs.get('PLUGIN_DENY') if plugin_deny: plugin.execute(plugin_deny, new_denied_hosts) new_suspicious_logins = login_attempt.get_new_suspicious_logins() if new_suspicious_logins: msg = "Observed the following suspicious login activity" self.__report.add_section(msg, new_suspicious_logins.keys()) if new_denied_hosts: info("new denied hosts: %s", str(new_denied_hosts)) else: debug("no new denied hosts") if new_suspicious_logins: info("new suspicious logins: %s", str(new_suspicious_logins.keys())) else: debug("no new suspicious logins") if not self.__report.empty(): if not self.__noemail: # send the report via email if configured send_email(self.__prefs, self.__report.get_report()) elif not self.__daemon: # otherwise, if not in daemon mode, log the report to the console info(self.__report.get_report()) self.__report.clear() return offset def sync_add_hosts(self, hosts): try: filename = os.path.join(self.__prefs.get("WORK_DIR"), SYNC_HOSTS) fp = open(filename, "a") for host in hosts: fp.write("%s\n" % host) fp.close() os.chmod(filename, 0644) except Exception, e: error(str(e)) def get_regex(self, name, default): val = self.__prefs.get(name) if not val: return default else: return re.compile(val) def init_regex(self): self.__sshd_format_regex = self.get_regex('SSHD_FORMAT_REGEX', SSHD_FORMAT_REGEX) self.__successful_entry_regex = self.get_regex('SUCCESSFUL_ENTRY_REGEX', SUCCESSFUL_ENTRY_REGEX) self.__failed_entry_regex_map = {} for i in FAILED_ENTRY_REGEX_RANGE: if i == 1: extra = "" else: extra = "%i" % i self.__failed_entry_regex_map[i] = self.get_regex('FAILED_ENTRY_REGEX%s' % extra, FAILED_ENTRY_REGEX_MAP[i]) ## self.__failed_entry_regex = self.get_regex('FAILED_ENTRY_REGEX', FAILED_ENTRY_REGEX) ## self.__failed_entry_regex2 = self.get_regex('FAILED_ENTRY_REGEX2', FAILED_ENTRY_REGEX2) ## self.__failed_entry_regex3 = self.get_regex('FAILED_ENTRY_REGEX3', FAILED_ENTRY_REGEX3) ## self.__failed_entry_regex4 = self.get_regex('FAILED_ENTRY_REGEX4', FAILED_ENTRY_REGEX4) ## self.__failed_entry_regex5 = self.get_regex('FAILED_ENTRY_REGEX5', FAILED_ENTRY_REGEX5) ## self.__failed_entry_regex6 = self.get_regex('FAILED_ENTRY_REGEX6', FAILED_ENTRY_REGEX6) ## self.__failed_entry_regex6 = self.get_regex('FAILED_ENTRY_REGEX7', FAILED_ENTRY_REGEX7) ## self.__failed_entry_regex6 = self.get_regex('FAILED_ENTRY_REGEX8', FAILED_ENTRY_REGEX8) ## self.__failed_entry_regex6 = self.get_regex('FAILED_ENTRY_REGEX9', FAILED_ENTRY_REGEX9) ## self.__failed_entry_regex6 = self.get_regex('FAILED_ENTRY_REGEX10', FAILED_ENTRY_REGEX10) # vim: set sw=4 et : denyhosts-2.10/DenyHosts/denyfileutil.py000066400000000000000000000212441246776277600204760ustar00rootroot00000000000000import os import shutil import time import logging from constants import DENY_DELIMITER, ENTRY_DELIMITER from loginattempt import AbusiveHosts from util import parse_host import plugin from purgecounter import PurgeCounter debug = logging.getLogger("denyfileutil").debug info = logging.getLogger("denyfileutil").info warn = logging.getLogger("denyfileutil").warn class DenyFileUtilBase(object): def __init__(self, deny_file, extra_file_id=""): self.deny_file = deny_file self.backup_file = "%s.%s.bak" % (deny_file, extra_file_id) self.temp_file = "%s.%s.tmp" % (deny_file, extra_file_id) def backup(self): try: shutil.copy(self.deny_file, self.backup_file) except Exception, e: warn(str(e)) def replace(self): # overwrites deny_file with contents of temp_file try: os.rename(self.temp_file, self.deny_file) except Exception, e: print e def remove_temp(self): try: os.unlink(self.temp_file) except OSError: pass def create_temp(self, data_list): raise Exception, "Not Imlemented" def get_data(self): data = [] try: fp = open(self.backup_file, "r") data = fp.readlines() fp.close() except IOError: pass return data ################################################################################# class Migrate(DenyFileUtilBase): def __init__(self, deny_file): print "" print "**** WARNING ****" print "migrate switch will migrate ALL your entries in your HOSTS_DENY file" print "and this can be potentially dangerous, if you have some entry that " print "you won't purge" print "" print "If you don't understand, please type 'No' and" print "read /usr/share/doc/denyhosts/README.Debian" print "for more info" print "" response = raw_input("Are you sure that you want do this? (Yes/No)") if response == "Yes": DenyFileUtilBase.__init__(self, deny_file, "migrate") self.backup() self.create_temp(self.get_data()) self.replace() else: print "nothing done" def create_temp(self, data): try: fp = open(self.temp_file, "w") os.chmod(self.temp_file, 0644) for line in data: if line.find("#") != -1: fp.write(line) continue line = line.strip() if not line: fp.write("\n") continue fp.write("%s %s%s%s\n" % (DENY_DELIMITER, time.asctime(), ENTRY_DELIMITER, line)) fp.write("%s\n" % line) fp.close() except Exception, e: raise e ################################################################################# class UpgradeTo099(DenyFileUtilBase): def __init__(self, deny_file): DenyFileUtilBase.__init__(self, deny_file, "0.9.9") self.backup() self.create_temp(self.get_data()) self.replace() def create_temp(self, data): try: fp = open(self.temp_file, "w") for line in data: if line.find("#") == 0: fp.write(line) continue line = line.strip() if not line: fp.write("\n") continue delimiter_idx = line.find(DENY_DELIMITER) if delimiter_idx == -1: fp.write("%s\n" % line) continue entry = line[:delimiter_idx].strip() fp.write("%s%s%s\n" % (line[delimiter_idx:], ENTRY_DELIMITER, entry)) fp.write("%s\n" % entry) fp.close() except Exception, e: raise e ################################################################################# class Purge(DenyFileUtilBase): def __init__(self, prefs, cutoff): deny_file = prefs.get('HOSTS_DENY') DenyFileUtilBase.__init__(self, deny_file, "purge") work_dir = prefs.get('WORK_DIR') self.purge_threshold = prefs['PURGE_THRESHOLD'] self.purge_counter = PurgeCounter(prefs) self.cutoff = long(time.time()) - cutoff debug("relative cutoff: %ld (seconds)", cutoff) debug("absolute cutoff: %ld (epoch)", self.cutoff) info("purging entries older than: %s", time.asctime(time.localtime(self.cutoff))) self.backup() purged_hosts = self.create_temp(self.get_data()) num_purged = len(purged_hosts) if num_purged > 0: self.replace() abusive_hosts = AbusiveHosts(prefs) abusive_hosts.purge_hosts(purged_hosts) abusive_hosts.save_abusive_hosts() self.purge_counter.increment(purged_hosts) else: self.remove_temp() info("num entries purged: %d", num_purged) plugin_purge = prefs.get('PLUGIN_PURGE') if plugin_purge: plugin.execute(plugin_purge, purged_hosts) def create_temp(self, data): purged_hosts = [] banned = self.purge_counter.get_banned_for_life() try: fp = open(self.temp_file, "w") os.chmod(self.temp_file, 0644) offset = 0 num_lines = len(data) while offset < num_lines: line = data[offset] offset += 1 if not line.startswith(DENY_DELIMITER): fp.write(line) continue else: if offset == num_lines: warn("DenyHosts comment line at end of file") fp.write(line) continue timestamp = None try: rest = line.lstrip(DENY_DELIMITER) timestamp, host_verify = rest.split(ENTRY_DELIMITER) tm = time.strptime(timestamp) except Exception, e: warn("Parse error -- Ignorning timestamp: %s for: %s", timestamp, line) warn("exception: %s", str(e)) # ignoring bad time string fp.write(line) continue epoch = long(time.mktime(tm)) #print entry, epoch, self.cutoff if self.cutoff > epoch: # this entry should be purged entry = data[offset] if host_verify != entry: warn("%s purge verification failed: %s vs. %s", self.deny_file, host_verify.rstrip(), entry.rstrip()) fp.write(line) continue host = parse_host(entry) if host and host not in banned: # purge purged_hosts.append(host) # increment offset past purged line offset += 1 continue else: fp.write(line) continue fp.close() except Exception, e: raise e return purged_hosts ################################################################################# class PurgeIP(DenyFileUtilBase): def __init__(self, prefs, purgeip_list): deny_file = prefs.get('HOSTS_DENY') DenyFileUtilBase.__init__(self, deny_file, "purgeip") work_dir = prefs.get('WORK_DIR') self.purge_counter = PurgeCounter(prefs) info("purging listed IP addresses.",) self.backup() purged_hosts = purgeip_list num_purged = len(purged_hosts) if num_purged > 0: self.replace() abusive_hosts = AbusiveHosts(prefs) abusive_hosts.purge_hosts(purged_hosts) abusive_hosts.save_abusive_hosts() self.purge_counter.increment(purged_hosts) else: self.remove_temp() info("num entries purged: %d", num_purged) plugin_purge = prefs.get('PLUGIN_PURGE') if plugin_purge: plugin.execute(plugin_purge, purged_hosts) denyhosts-2.10/DenyHosts/filetracker.py000066400000000000000000000045261246776277600203000ustar00rootroot00000000000000import os import logging from constants import SECURE_LOG_OFFSET debug = logging.getLogger("filetracker").debug class FileTracker(object): def __init__(self, work_dir, logfile): self.work_dir = work_dir self.logfile = logfile (self.__first_line, self.__offset) = self.__get_current_offset() def __get_last_offset(self): path = os.path.join(self.work_dir, SECURE_LOG_OFFSET) first_line = "" offset = 0L try: fp = open(path, "r") first_line = fp.readline()[:-1] offset = long(fp.readline()) except IOError: pass debug("__get_last_offset():") debug(" first_line: %s", first_line) debug(" offset: %ld", offset) return first_line, offset def __get_current_offset(self): first_line = "" offset = 0L try: fp = open(self.logfile, "r") first_line = fp.readline()[:-1] fp.seek(0, 2) offset = fp.tell() except IOError, e: raise e debug("__get_current_offset():") debug(" first_line: %s", first_line) debug(" offset: %ld", offset) return first_line, offset def update_first_line(self): first_line = "" try: fp = open(self.logfile, "r") first_line = fp.readline()[:-1] except IOError, e: raise e self.__first_line = first_line def get_offset(self): last_line, last_offset = self.__get_last_offset() if last_line != self.__first_line: # log file was rotated, start from beginning offset = 0L elif self.__offset > last_offset: # new lines exist in log file offset = last_offset else: # no new entries in log file offset = None debug("get_offset():") debug(" offset: %s", str(offset)) return offset def save_offset(self, offset): path = os.path.join(self.work_dir, SECURE_LOG_OFFSET) try: fp = open(path, "w") fp.write("%s\n" % self.__first_line) fp.write("%ld\n" % offset) fp.close() except IOError: print "Could not save logfile offset to: %s" % path denyhosts-2.10/DenyHosts/lockfile.py000066400000000000000000000025431246776277600175720ustar00rootroot00000000000000import os from util import die class LockFile(object): def __init__(self, lockpath): self.lockpath = lockpath self.fd = None def exists(self): return os.access(self.lockpath, os.F_OK) def get_pid(self): pid = "" try: fp = open(self.lockpath, "r") pid = fp.read().strip() fp.close() except IOError: pass return pid def create(self): try: self.fd = os.open(self.lockpath, os.O_CREAT | # create file os.O_TRUNC | # truncate it, if it exists os.O_WRONLY | # write-only os.O_EXCL, # exclusive access 0644) # file mode except Exception, e: pid = self.get_pid() die("DenyHosts could not obtain lock (pid: %s)" % pid, e) os.write(self.fd, "%s\n" % os.getpid()) os.fsync(self.fd) def remove(self, die_=True): try: if self.fd: os.close(self.fd) except IOError: pass self.fd = None try: os.unlink(self.lockpath) except Exception, e: if die_: die("Error deleting DenyHosts lock file: %s" % self.lockpath, e) denyhosts-2.10/DenyHosts/loginattempt.py000066400000000000000000000270141246776277600205110ustar00rootroot00000000000000import os import logging import errno from util import is_true from counter import Counter, CounterRecord from constants import * debug = logging.getLogger("loginattempt").debug info = logging.getLogger("loginattempt").info class LoginAttempt(object): def __init__(self, prefs, allowed_hosts, suspicious_always=1, first_time=0, fetch_all=1, restricted=None): if restricted is None: restricted = set() self.__restricted = restricted self.__work_dir = prefs.get('WORK_DIR') self.__deny_threshold_invalid = prefs.get('DENY_THRESHOLD_INVALID') self.__deny_threshold_valid = prefs.get('DENY_THRESHOLD_VALID') self.__deny_threshold_root = prefs.get('DENY_THRESHOLD_ROOT') self.__deny_threshold_restricted = prefs.get('DENY_THRESHOLD_RESTRICTED') self.__age_reset_invalid = prefs.get('AGE_RESET_INVALID') self.__age_reset_valid = prefs.get('AGE_RESET_VALID') self.__age_reset_root = prefs.get('AGE_RESET_ROOT') self.__age_reset_restricted = prefs.get('AGE_RESET_RESTRICTED') self.__reset_on_success = is_true(prefs.get('RESET_ON_SUCCESS')) self.__first_time = first_time self.__suspicious_always = suspicious_always self.__allowed_hosts = allowed_hosts if fetch_all: self.__suspicious_logins = self.get_suspicious_logins() self.__valid_users = self.get_abused_users_valid() self.__invalid_users = self.get_abused_users_invalid() self.__valid_users_and_hosts = self.get_abused_users_and_hosts() self.__abusive_hosts_valid = self.get_abusive_hosts_valid() self.__abusive_hosts_invalid = self.get_abusive_hosts_invalid() self.__abusive_hosts_root = self.get_abusive_hosts_root() self.__abusive_hosts_restricted = self.get_abusive_hosts_restricted() self.__new_suspicious_logins = Counter() def get_new_suspicious_logins(self): return self.__new_suspicious_logins def add(self, user, host, success, invalid): user_host_key = "%s - %s" % (user, host) if host: if self.__age_reset_invalid: self.__abusive_hosts_invalid[host].age_count(self.__age_reset_invalid) if self.__age_reset_valid: self.__abusive_hosts_valid[host].age_count(self.__age_reset_valid) if self.__age_reset_restricted: self.__abusive_hosts_restricted[host].age_count(self.__age_reset_restricted) if self.__age_reset_root: self.__abusive_hosts_root[host].age_count(self.__age_reset_root) if success and self.__reset_on_success: info("resetting count for: %s", host) self.__abusive_hosts_valid[host].reset_count() # ??? maybe: self.__abusive_hosts_invalid[host].reset_count() if success and self.__abusive_hosts_invalid[host].get_count() > self.__deny_threshold_invalid: num_failures = self.__valid_users_and_hosts.get(user_host_key, 0) self.__suspicious_logins[user_host_key] += 1 if self.__suspicious_always or host not in self.__allowed_hosts: self.__new_suspicious_logins[user_host_key] += 1 elif not success: if user in self.__restricted: self.increment_count(host, self.__abusive_hosts_restricted, self.__age_reset_restricted) if invalid: # username is invalid self.increment_count(host, self.__abusive_hosts_invalid, self.__age_reset_invalid) self.__invalid_users[user] += 1 else: # username is valid self.increment_count(user, self.__valid_users) self.increment_count(user_host_key, self.__valid_users_and_hosts) if user == 'root': self.increment_count(host, self.__abusive_hosts_root, self.__age_reset_root) elif user in self.__restricted: self.increment_count(host, self.__abusive_hosts_restricted, self.__age_reset_restricted) else: self.increment_count(host, self.__abusive_hosts_valid, self.__age_reset_valid) def increment_count(self, key, count_inst, age_reset=None): #if not count_inst.has_key(key) or count_inst.has_key(key) and count_inst[key] is None: # count_inst[key] = CounterRecord(0) #debug(count_inst) if age_reset: count_inst[key].age_count(age_reset) #debug(count_inst) count_inst[key] += 1 #debug(count_inst) def get_abusive_hosts_invalid(self): return self.__get_stats(ABUSIVE_HOSTS_INVALID) def get_abusive_hosts_root(self): return self.__get_stats(ABUSIVE_HOSTS_ROOT) def get_abusive_hosts_restricted(self): return self.__get_stats(ABUSIVE_HOSTS_RESTRICTED) def get_abusive_hosts_valid(self): return self.__get_stats(ABUSIVE_HOSTS_VALID) def get_abused_users_invalid(self): return self.__get_stats(ABUSED_USERS_INVALID) def get_abused_users_valid(self): return self.__get_stats(ABUSED_USERS_VALID) def get_abused_users_and_hosts(self): return self.__get_stats(ABUSED_USERS_AND_HOSTS) def get_suspicious_logins(self): return self.__get_stats(SUSPICIOUS_LOGINS) def __get_stats(self, fname): path = os.path.join(self.__work_dir, fname) stats = Counter() try: for line in open(path, "r"): try: line = line.strip() parts = line.split(":") name = parts[0] count = parts[1] try: date = ':'.join(parts[2:]) except Exception: date = None stats[name] = CounterRecord(int(count), date) #debug("stats[%s] = %s", name, stats[name]) except Exception, e: ##debug(e) pass except IOError, e: if e.errno == errno.ENOENT: debug("%s does not exist", fname) else: print e except Exception, e: if not self.__first_time: print e return stats def save_all_stats(self): self.save_abusive_hosts_valid() self.save_abusive_hosts_invalid() self.save_abusive_hosts_root() self.save_abusive_hosts_restricted() self.save_abused_users_valid() self.save_abused_users_invalid() self.save_abused_users_and_hosts() self.save_suspicious_logins() def save_abusive_hosts_invalid(self, abusive_hosts=None): if abusive_hosts is None: abusive_hosts = self.__abusive_hosts_invalid self.__save_stats(ABUSIVE_HOSTS_INVALID, abusive_hosts) def save_abusive_hosts_root(self, abusive_hosts=None): if abusive_hosts is None: abusive_hosts = self.__abusive_hosts_root self.__save_stats(ABUSIVE_HOSTS_ROOT, abusive_hosts) def save_abusive_hosts_restricted(self, abusive_hosts=None): if abusive_hosts is None: abusive_hosts = self.__abusive_hosts_restricted self.__save_stats(ABUSIVE_HOSTS_RESTRICTED, abusive_hosts) def save_abusive_hosts_valid(self, abusive_hosts=None): if abusive_hosts is None: abusive_hosts = self.__abusive_hosts_valid self.__save_stats(ABUSIVE_HOSTS_VALID, abusive_hosts) def save_abused_users_invalid(self): self.__save_stats(ABUSED_USERS_INVALID, self.__invalid_users) def save_abused_users_valid(self): self.__save_stats(ABUSED_USERS_VALID, self.__valid_users) def save_abused_users_and_hosts(self): self.__save_stats(ABUSED_USERS_AND_HOSTS, self.__valid_users_and_hosts) def save_suspicious_logins(self): self.__save_stats(SUSPICIOUS_LOGINS, self.__suspicious_logins) def get_deny_hosts(self): invalid_hosts = [host for host,count_rec in self.__abusive_hosts_invalid.items() if count_rec.get_count() > self.__deny_threshold_invalid] root_hosts = [host for host,count_rec in self.__abusive_hosts_root.items() if count_rec.get_count() > self.__deny_threshold_root] restricted_hosts = [host for host,count_rec in self.__abusive_hosts_restricted.items() if count_rec.get_count() > self.__deny_threshold_restricted] valid_hosts = [host for host,count_rec in self.__abusive_hosts_valid.items() if count_rec.get_count() > self.__deny_threshold_valid] deny_set = set(invalid_hosts + valid_hosts + root_hosts + restricted_hosts) return list(deny_set) def __save_stats(self, fname, stats): path = os.path.join(self.__work_dir, fname) if stats is None: #debug("%s: is none", fname) return try: fp = open(path, "w") except Exception, e: print e return if not stats: # if stats dict is empty-- no data to process fp.close() return keys = stats.keys() keys.sort() for key in keys: #debug("") #debug("key: %s - stats[key]: %s", key, stats[key]) #debug("stats: %s", stats) #debug("") fp.write("%s:%s\n" % (key, stats[key])) fp.close() class AbusiveHosts(LoginAttempt): def __init__(self, prefs): LoginAttempt.__init__(self, prefs, None, fetch_all = 0) self.__abusive_hosts_invalid = self.get_abusive_hosts_invalid() self.__abusive_hosts_root = self.get_abusive_hosts_root() self.__abusive_hosts_restricted = self.get_abusive_hosts_restricted() self.__abusive_hosts_valid = self.get_abusive_hosts_valid() def save_abusive_hosts(self): LoginAttempt.save_abusive_hosts_invalid(self, self.__abusive_hosts_invalid) LoginAttempt.save_abusive_hosts_root(self, self.__abusive_hosts_root) LoginAttempt.save_abusive_hosts_restricted(self, self.__abusive_hosts_restricted) LoginAttempt.save_abusive_hosts_valid(self, self.__abusive_hosts_valid) def purge_host(self, host): try: self.__abusive_hosts_invalid[host] = None self.__abusive_hosts_root[host] = None self.__abusive_hosts_restricted[host] = None self.__abusive_hosts_valid[host] = None del self.__abusive_hosts_invalid[host] del self.__abusive_hosts_root[host] del self.__abusive_hosts_restricted[host] del self.__abusive_hosts_valid[host] except Exception: pass def purge_hosts(self, hosts): info("purging_hosts: %s", str(hosts)) for host in hosts: self.purge_host(host) denyhosts-2.10/DenyHosts/plugin.py000066400000000000000000000007111246776277600172730ustar00rootroot00000000000000import logging import os error = logging.getLogger("plugin").error info = logging.getLogger("plugin").info debug = logging.getLogger("plugin").debug def execute(executable, hosts): for host in hosts: debug("invoking plugin: %s %s", executable, host) try: res = os.system("%s %s" % (executable, host)) if res: info("plugin returned %d", res) except Exception, e: error("plugin error: %s", e) denyhosts-2.10/DenyHosts/prefs.py000066400000000000000000000223111246776277600171140ustar00rootroot00000000000000import logging import os import re from regex import PREFS_REGEX from util import die, calculate_seconds debug = logging.getLogger("prefs").debug info = logging.getLogger("prefs").info ENVIRON_REGEX = re.compile(r"""\$\[(?P[A-Z_]*)\]""") class Prefs(dict): def __getitem__(self, k): return self.get(k) def __init__(self, path=None, **kwargs): super(Prefs, self).__init__(**kwargs) # default values for some of the configurable items self.__data = {'ADMIN_EMAIL': None, 'SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS': 'yes', 'HOSTNAME_LOOKUP': 'yes', 'SYSLOG_REPORT': 'no', 'DAEMON_LOG': '/var/log/denyhosts', 'DAEMON_SLEEP': '30s', 'DAEMON_PURGE': '1h', 'DAEMON_LOG_TIME_FORMAT': None, 'DAEMON_LOG_MESSAGE_FORMAT': '%(asctime)s - %(name)-12s: %(levelname)-8s %(message)s', 'AGE_RESET_INVALID': None, 'AGE_RESET_VALID': None, 'AGE_RESET_ROOT': None, 'AGE_RESET_RESTRICTED': None, 'RESET_ON_SUCCESS': 'no', 'PLUGIN_DENY': None, 'PLUGIN_PURGE': None, 'IPTABLES': None, 'BLOCKPORT': None, 'PFCTL_PATH': None, 'PF_TABLE': None, 'SMTP_USERNAME': None, 'SMTP_PASSWORD': None, 'SMTP_DATE_FORMAT': "%a, %d %b %Y %H:%M:%S %z", 'SSHD_FORMAT_REGEX': None, 'FAILED_ENTRY_REGEX': None, 'FAILED_ENTRY_REGEX2': None, 'FAILED_ENTRY_REGEX3': None, 'FAILED_ENTRY_REGEX4': None, 'FAILED_ENTRY_REGEX5': None, 'FAILED_ENTRY_REGEX6': None, 'FAILED_ENTRY_REGEX7': None, 'FAILED_ENTRY_REGEX8': None, # 'FAILED_ENTRY_REGEX9': None, # 'FAILED_ENTRY_REGEX10': None, 'USERDEF_FAILED_ENTRY_REGEX': [], 'SUCCESSFUL_ENTRY_REGEX': None, 'SYNC_INTERVAL': '1h', 'SYNC_SERVER': None, 'SYNC_UPLOAD': "yes", 'SYNC_DOWNLOAD': "yes", 'SYNC_DOWNLOAD_THRESHOLD': 3, 'SYNC_DOWNLOAD_RESILIENCY': '5h', 'PURGE_THRESHOLD': 0, 'ALLOWED_HOSTS_HOSTNAME_LOOKUP': 'no', 'ETC_DIR': '/etc'} # reqd[0]: required field name # reqd[1]: is value required? (False = value can be blank) self.reqd = (('DENY_THRESHOLD_INVALID', True), ('DENY_THRESHOLD_VALID', True), ('DENY_THRESHOLD_ROOT', True), ('DENY_THRESHOLD_RESTRICTED', True), ('SECURE_LOG', True), ('LOCK_FILE', True), ('BLOCK_SERVICE', False), ('PURGE_DENY', False), ('HOSTS_DENY', True), ('WORK_DIR', True), ('ETC_DIR', False), ('IPTABLES', False), ('BLOCKPORT', False), ('PFCTL_PATH', False), ('PF_TABLE', False)) # the paths for these keys will be converted to # absolute pathnames (in the event they are relative) # since the --daemon mode requires absolute pathnames self.make_abs = ('WORK_DIR', 'ETC_DIR', 'LOCK_FILE', 'SECURE_LOG', 'HOSTS_DENY', 'DAEMON_LOG', 'IPTABLES', 'PFCTL_PATH') # these settings are converted to numeric values self.to_int = set(('DENY_THRESHOLD', 'DENY_THRESHOLD_INVALID', 'DENY_THRESHOLD_VALID', 'DENY_THRESHOLD_ROOT', 'DENY_THRESHOLD_RESTRICTED', 'SYNC_DOWNLOAD_THRESHOLD', 'PURGE_THRESHOLD')) # these settings are converted from timespec format # to number of seconds (ie. "1m" -> 60) self.to_seconds = set(('PURGE_DENY', 'DAEMON_PURGE', 'DAEMON_SLEEP', 'AGE_RESET_VALID', 'AGE_RESET_INVALID', 'AGE_RESET_RESTRICTED', 'SYNC_INTERVAL', 'SYNC_DOWNLOAD_RESILIENCY', 'AGE_RESET_ROOT')) self.process_defaults() if path: self.load_settings(path) def process_defaults(self): for name in self.to_seconds: try: self.__data[name] = calculate_seconds(self.__data[name]) except Exception: pass def load_settings(self, path): try: fp = open(path, "r") except Exception, e : die("Error reading file: %s" % path, e) for line in fp: line = line.strip() if not line or line[0] in ('\n', '#'): continue try: m = PREFS_REGEX.search(line) if m: name = m.group('name').upper() value = self.environ_sub(m.group('value')) #print name, value if not value: value = None if name in self.to_int: value = int(value) if name in self.to_seconds and value: value = calculate_seconds(value) if name == 'USERDEF_FAILED_ENTRY_REGEX': self.__data['USERDEF_FAILED_ENTRY_REGEX'].append(re.compile(value)) else: self.__data[name] = value except Exception, e: fp.close() die("Error processing configuration parameter %s: %s" % (name, e)) fp.close() self.check_required(path) self.make_absolute() def make_absolute(self): for key in self.make_abs: val = self.__data[key] if val: self.__data[key] = os.path.abspath(val) def check_required(self, path): ok = 1 for name_reqd, val_reqd in self.reqd: if not self.__data.has_key(name_reqd): print "Missing configuration parameter: %s" % name_reqd if name_reqd == 'DENY_THRESHOLD_INVALID': print "\nNote: The configuration parameter DENY_THRESHOLD has been renamed" print " DENY_THRESHOLD_INVALID. Please update your DenyHosts configuration" print " file to reflect this change." if self.__data.has_key('DENY_THRESHOLD'): print "\n*** Using deprecated DENY_THRESHOLD value for DENY_THRESHOLD_INVALID ***" self.__data['DENY_THRESHOLD_INVALID'] = self.__data['DENY_THRESHOLD'] else: ok = 0 elif name_reqd == 'DENY_THRESHOLD_RESTRICTED': print "\nNote: DENY_THRESHOLD_RESTRICTED has not been defined. Setting this" print "value to DENY_THRESHOLD_ROOT" self.__data['DENY_THRESHOLD_RESTRICTED'] = self.__data['DENY_THRESHOLD_ROOT'] else: ok = 0 elif val_reqd and not self.__data[name_reqd]: print "Missing configuration value for: %s" % name_reqd ok = 0 if not ok: die("You must correct these problems found in: %s" % path) def environ_sub(self, value): while True: environ_match = ENVIRON_REGEX.search(value) if not environ_match: return value name = environ_match.group("environ") env = os.environ.get(name) if not env: die("Could not find environment variable: %s" % name) value = ENVIRON_REGEX.sub(env, value) def get(self, name): return self.__data[name] def dump(self): print "Preferences:" keys = self.__data.keys() for key in keys: if key == 'USERDEF_FAILED_ENTRY_REGEX': for rx in self.__data[key]: print " %s: [%s]" % (key, rx.pattern) else: print " %s: [%s]" % (key, self.__data[key]) def dump_to_logger(self): keys = self.__data.keys() keys.sort() info("DenyHosts configuration settings:") for key in keys: if key == 'USERDEF_FAILED_ENTRY_REGEX': for rx in self.__data[key]: info(" %s: [%s]" % (key, rx.pattern)) else: info(" %s: [%s]", key, self.__data[key]) denyhosts-2.10/DenyHosts/purgecounter.py000066400000000000000000000035261246776277600205260ustar00rootroot00000000000000import logging import os import constants from counter import Counter, CounterRecord error = logging.getLogger("purgecounter").error info = logging.getLogger("purgecounter").info class PurgeCounter(object): def __init__(self, prefs): self.filename = os.path.join(prefs['WORK_DIR'], constants.PURGE_HISTORY) self.purge_threshold = prefs['PURGE_THRESHOLD'] def get_banned_for_life(self): banned = set() if self.purge_threshold == 0: return banned try: fp = open(self.filename, "r") except IOError: return banned for line in fp: try: host, count, timestamp = line.strip().split(':', 2) except Exception: continue if int(count) > self.purge_threshold: banned.add(host) fp.close() return banned def get_data(self): counter = Counter() try: fp = open(self.filename, "r") except IOError: return counter for line in fp: try: host, count, timestamp = line.strip().split(':', 2) except Exception: continue counter[host] = CounterRecord(int(count), timestamp) fp.close() return counter def write_data(self, data): try: fp = open(self.filename, "w") keys = data.keys() keys.sort() for key in keys: fp.write("%s:%s\n" % (key, data[key])) fp.close() except Exception, e: error("error saving %s: %s", self.filename, str(e)) def increment(self, purged_hosts): data = self.get_data() for host in purged_hosts: data[host] += 1 self.write_data(data) denyhosts-2.10/DenyHosts/python_version.py000066400000000000000000000020331246776277600210620ustar00rootroot00000000000000import sys from textwrap import dedent MINIMUM_VERSION = (2, 4) def check_version(): if sys.version_info < MINIMUM_VERSION: min_version_str = '.'.join(str(x) for x in MINIMUM_VERSION) print "Python >= %s required. You are using:\n%s" % (min_version_str, sys.version) print dedent(""" ###################################################################### Visit http://www.python.org and download a more recent version of Python. You should install this version in addition to your current version (rather than upgrading your current version) because your system might depend on the current version. After installing the newer version, for instance version 3.2, simply invoke DenyHosts explicitly with the new version of python, eg: $ python3.2 %s ###################################################################### """) % ' '.join(sys.argv) sys.exit(1) check_version() denyhosts-2.10/DenyHosts/regex.py000066400000000000000000000051231246776277600171110ustar00rootroot00000000000000import re ################################################################################# # REGULAR EXPRESSIONS ARE COOL. Check out Kodos (http://kodos.sourceforge.net) # ################################################################################# #DATE_FORMAT_REGEX = re.compile(r"""(?P[A-z]{3,3})\s*(?P\d+)""") SSHD_FORMAT_REGEX = re.compile(r""".* (sshd.*:|\[sshd\]) (?P.*)""") #SSHD_FORMAT_REGEX = re.compile(r""".* sshd.*: (?P.*)""") FAILED_ENTRY_REGEX = re.compile(r"""Failed (?P\S*) for (?Pinvalid user |illegal user )?(?P.*) from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})( port \d+)?( ssh2)?$""") FAILED_ENTRY_REGEX2 = re.compile(r"""(?P(Illegal|Invalid)) user (?P.*) from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$""") FAILED_ENTRY_REGEX3 = re.compile(r"""Authentication failure for (?P.*) .*from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})""") FAILED_ENTRY_REGEX4 = re.compile(r"""Authentication failure for (?P.*) .*from (?P.*)""") FAILED_ENTRY_REGEX5 = re.compile(r"""User (?P.*) .*from (?P.*) not allowed because none of user's groups are listed in AllowGroups$""") FAILED_ENTRY_REGEX6 = re.compile(r"""Did not receive identification string .*from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})""") FAILED_ENTRY_REGEX7 = re.compile(r"""User (?P.*) .*from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) not allowed because not listed in AllowUsers""") FAILED_ENTRY_REGEX8 = re.compile(r"""authentication error for (?P.*) .*from (?P.*)""") # these are reserved for future versions FAILED_ENTRY_REGEX9 = None FAILED_ENTRY_REGEX10 = None FAILED_ENTRY_REGEX_NUM = 9 # this should match the highest num failed_entry_regex + 1 FAILED_ENTRY_REGEX_RANGE = range(1, FAILED_ENTRY_REGEX_NUM) FAILED_ENTRY_REGEX_MAP = {} # create a hash of the failed entry regex'es indexed from 1 .. FAILED_ENTRY_REGEX_NUM for i in FAILED_ENTRY_REGEX_RANGE: if i == 1: extra = "" else: extra = "%i" % i rx = eval("FAILED_ENTRY_REGEX%s" % extra) FAILED_ENTRY_REGEX_MAP[i] = rx SUCCESSFUL_ENTRY_REGEX = re.compile(r"""Accepted (?P\S+) for (?P.*) from (::ffff:)?(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})( port \d+)?( ssh2)?$""") TIME_SPEC_REGEX = re.compile(r"""(?P\d*)\s*(?P[smhdwy])?""") ALLOWED_REGEX = re.compile(r"""(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.)((?P\d{1,3})|(?P\*)|\[(?P\d{1,3}\-\d{1,3})\])""") PREFS_REGEX = re.compile(r"""(?P.*?)\s*[:=]\s*(?P.*)""") denyhosts-2.10/DenyHosts/report.py000066400000000000000000000035741246776277600173220ustar00rootroot00000000000000import logging import re import socket from types import ListType, TupleType from util import is_true try: import syslog HAS_SYSLOG = True except ImportError: HAS_SYSLOG = False debug = logging.getLogger("report").debug warn = logging.getLogger("report").warn IP_ADDR_REGEX = re.compile(r"""(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})""") class Report: def __init__(self, hostname_lookup, use_syslog=False): self.report = "" if use_syslog and not HAS_SYSLOG: warn("syslog is unavailable on this platform") self.use_syslog = use_syslog and HAS_SYSLOG if self.use_syslog: syslog.openlog("denyhosts") self.hostname_lookup = is_true(hostname_lookup) def empty(self): if self.report: return 0 else: return 1 def clear(self): self.report = "" def get_report(self): return self.report def add_section(self, message, iterable): self.report += "%s:\n\n" % message for i in iterable: if type(i) in (TupleType, ListType): extra = ": %d\n" % i[1] i = i[0] else: extra = "" if self.hostname_lookup: hostname = self.get_hostname(i) debug("get_host: %s", hostname) else: hostname = i self.report += "%s%s\n" % (hostname, extra) if self.use_syslog: syslog.syslog("%s - %s%s" %(message, hostname, extra)) self.report += "\n" + "-" * 70 + "\n" def get_hostname(self, text): m = IP_ADDR_REGEX.search(text) if m: start = m.start() ip = m.group('ip') text = text[:start] else: return text hostname = socket.getfqdn(ip) if hostname == ip: hostname = "unknown" return "%s (%s)" % (ip, hostname) denyhosts-2.10/DenyHosts/restricted.py000066400000000000000000000011421246776277600201440ustar00rootroot00000000000000import os from constants import RESTRICTED_USERNAMES class Restricted: def __init__(self, prefs): self.filename = os.path.join(prefs['ETC_DIR'], RESTRICTED_USERNAMES) self.__data = set() self.load_restricted() def load_restricted(self): try: fp = open(self.filename, "r") for line in fp: line = line.strip() if not line: continue if line[0] == '#': continue self.__data.add(line) except IOError: pass def get_restricted(self): return self.__data denyhosts-2.10/DenyHosts/sync.py000066400000000000000000000077431246776277600167650ustar00rootroot00000000000000import logging import os import time from xmlrpclib import ServerProxy from constants import SYNC_TIMESTAMP, SYNC_HOSTS, SYNC_HOSTS_TMP, SYNC_RECEIVED_HOSTS logger = logging.getLogger("sync") debug, info, error, exception = logger.debug, logger.info, logger.error, logger.exception def get_plural(items): if len(items) != 1: return "s" else: return "" class Sync(object): def __init__(self, prefs): self.__prefs = prefs self.__work_dir = prefs.get('WORK_DIR') self.__connected = False self.__hosts_added = [] def xmlrpc_connect(self): try: self.__server = ServerProxy(self.__prefs.get('SYNC_SERVER')) self.__connected = True except Exception, e: error(str(e)) self.__connected = False return self.__connected def xmlrpc_disconnect(self): if self.__connected: try: #self.__server.close() self.__server = None except Exception: pass self.__connected = False def get_sync_timestamp(self): try: fp = open(os.path.join(self.__work_dir, SYNC_TIMESTAMP)) timestamp = fp.readline() timestamp = long(timestamp.strip()) return timestamp except Exception, e: error(str(e)) return 0l def set_sync_timestamp(self, timestamp): try: fp = open(os.path.join(self.__work_dir, SYNC_TIMESTAMP), "w") fp.write(timestamp) except Exception, e: error(e) def send_new_hosts(self): debug("send_new_hosts()") self.__hosts_added = [] try: src_file = os.path.join(self.__work_dir, SYNC_HOSTS) dest_file = os.path.join(self.__work_dir, SYNC_HOSTS_TMP) os.rename(src_file, dest_file) except OSError: return False hosts = [] fp = open(dest_file, "r") for line in fp.readlines(): hosts.append(line.strip()) fp.close() try: self.__send_new_hosts(hosts) info("sent %d new host%s", len(hosts), get_plural(hosts)) self.__hosts_added = hosts except Exception: os.rename(dest_file, src_file) return False try: os.unlink(dest_file) except OSError: pass return True def __send_new_hosts(self, hosts): if not self.__connected and not self.xmlrpc_connect(): error("Could not initiate xmlrpc connection") return try: self.__server.add_hosts(hosts) except Exception, e: exception(e) def receive_new_hosts(self): debug("receive_new_hosts()") if not self.__connected and not self.xmlrpc_connect(): error("Could not initiate xmlrpc connection") return timestamp = self.get_sync_timestamp() try: data = self.__server.get_new_hosts(timestamp, self.__prefs.get("SYNC_DOWNLOAD_THRESHOLD"), self.__hosts_added, self.__prefs.get("SYNC_DOWNLOAD_RESILIENCY")) timestamp = data['timestamp'] self.set_sync_timestamp(timestamp) hosts = data['hosts'] info("received %d new host%s", len(hosts), get_plural(hosts)) self.__save_received_hosts(hosts, timestamp) return hosts except Exception, e: exception(e) return None def __save_received_hosts(self, hosts, timestamp): try: fp = open(os.path.join(self.__work_dir, SYNC_RECEIVED_HOSTS), "a") except IOError, e: error(e) return timestr = time.ctime(float(timestamp)) for host in hosts: fp.write("%s:%s\n" % (host, timestr)) fp.close() denyhosts-2.10/DenyHosts/util.py000066400000000000000000000121511246776277600167530ustar00rootroot00000000000000import logging import logging.handlers from smtplib import SMTP from smtplib import SMTPResponseException from smtplib import SMTPHeloError import sys from textwrap import dedent import time from types import IntType from constants import BSD_STYLE, TIME_SPEC_LOOKUP from regex import TIME_SPEC_REGEX debug = logging.getLogger("util").debug def setup_logging(prefs, enable_debug, verbose, daemon): daemon_log = prefs.get('DAEMON_LOG') if daemon_log: # define a Handler which writes INFO messages or higher to the sys.stderr # fh = logging.FileHandler(daemon_log, 'a') fh = logging.handlers.RotatingFileHandler(daemon_log, 'a', 1024*1024, 7) fh.setLevel(logging.DEBUG) formatter = logging.Formatter(prefs.get('DAEMON_LOG_MESSAGE_FORMAT'), prefs.get('DAEMON_LOG_TIME_FORMAT')) fh.setFormatter(formatter) # add the handler to the root logger logging.getLogger().addHandler(fh) if enable_debug: # if --debug was enabled provide gory activity details logging.getLogger().setLevel(logging.DEBUG) #prefs.dump_to_logger() else: # in daemon mode we always log some activity logging.getLogger().setLevel(logging.INFO) info = logging.getLogger("denyhosts").info info("DenyHosts launched with the following args:") info(" %s", ' '.join(sys.argv)) prefs.dump_to_logger() def die(msg, ex=None): print msg if ex: print ex sys.exit(1) def is_true(s): return s.lower() in ('1', 't', 'true', 'y', 'yes') def is_false(s): return not is_true(s) def calculate_seconds(timestr, zero_ok=False): # return the number of seconds in a given timestr such as 1d (1 day), # 13w (13 weeks), 5s (5seconds), etc... if type(timestr) is IntType: return timestr m = TIME_SPEC_REGEX.search(timestr) if not m: raise Exception("Invalid time specification: string format error: %s", timestr) units = int(m.group('units')) period = m.group('period') or 's' # seconds is the default if units == 0 and not zero_ok: raise Exception("Invalid time specification: units = 0") seconds = units * TIME_SPEC_LOOKUP[period] #info("converted %s to %ld seconds: ", timestr, seconds) return seconds def parse_host(line): # parses a line from /etc/hosts.deny # returns the ip address # the deny file can be in the form: # 1) ip_address # 2) sshd: ip_address # 3) ip_address : deny # 4) sshd: ip_address : deny # convert form 3 & 4 to 1 & 2 try: line = line.strip(BSD_STYLE) vals = line.split(":") # we're only concerned about the ip_address if len(vals) == 1: form = vals[0] else: form = vals[1] host = form.strip() except Exception: host = "" return host def send_email(prefs, report_str): recipients = prefs['ADMIN_EMAIL'].split(',') msg = dedent(""" From: %s To: %s Subject: %s Date: %s """).lstrip() % ( prefs.get('SMTP_FROM'), prefs.get('ADMIN_EMAIL'), prefs.get('SMTP_SUBJECT'), time.strftime(prefs.get('SMTP_DATE_FORMAT') ) ) msg += report_str try: smtp = SMTP() if logging.getLogger().isEnabledFor(logging.DEBUG): smtp.set_debuglevel(1) smtp.connect(prefs.get('SMTP_HOST'), prefs.get('SMTP_PORT')) # If the server supports ESMTP and TLS, then convert the message exchange to TLS via the # STARTTLS command. if smtp.ehlo()[0] == 250: if smtp.has_extn('starttls'): (code, resp) = smtp.starttls() if code != 220: raise SMTPResponseException(code, resp) (code, resp) = smtp.ehlo() if code != 250: raise SMTPResponseException(code, resp) else: # The server does not support esmtp. # The Python library SMTP class handles executing HELO/EHLO commands inside # login/sendmail methods when neither helo()/ehlo() methods have been # previously called. Because we have already called ehlo() above, we must # manually fallback to calling helo() here. (code, resp) = smtp.helo() if not (200 <= code <= 299): raise SMTPHeloError(code, resp) username = prefs.get('SMTP_USERNAME') password = prefs.get('SMTP_PASSWORD') if username and password: smtp.login(username, password) smtp.sendmail(prefs.get('SMTP_FROM'), recipients, msg) debug("sent email to: %s" % prefs.get("ADMIN_EMAIL")) except Exception, e: print "Error sending email" print e print "Email message follows:" print msg try: smtp.quit() except Exception: pass def normalize_whitespace(string): return ' '.join(string.split()) denyhosts-2.10/DenyHosts/version.py000066400000000000000000000000211246776277600174540ustar00rootroot00000000000000VERSION = "2.10" denyhosts-2.10/LICENSE.txt000066400000000000000000000430411246776277600153310ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 version 2 of the License. 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. denyhosts-2.10/MANIFEST.in000066400000000000000000000005411246776277600152420ustar00rootroot00000000000000#recursive-include help *.html #recursive-include translations * #include plugins #include scripts recursive-include plugins * recursive-include scripts * include MANIFEST.in include LICENSE.txt include README.txt include CHANGELOG.txt include DenyHosts/*.py include denyhosts.py include denyhosts.cfg-dist include setup.py include daemon-control-dist denyhosts-2.10/Makefile000066400000000000000000000002241246776277600151420ustar00rootroot00000000000000VERSION?=2.10 clean: rm -rf build rm -rf DenyHosts/*.pyc tarball: clean cd .. && tar czf denyhosts-$(VERSION).tar.gz denyhosts --exclude=.git denyhosts-2.10/PKG-INFO000066400000000000000000000010601246776277600145760ustar00rootroot00000000000000Metadata-Version: 1.0 Name: DenyHost Version: 2.9 Summary: DenyHost is a utility to help sys admins thwart ssh hackers Home-page: http://denyhost.sourceforge.net Author: Jesse Smith Author-email: jessefrgsmith@yahoo.ca License: GPL v2 Description: DenyHost is a python program that automatically blocks ssh attacks by adding entries to /etc/hosts.deny. DenyHosts will also inform Linux administrators about offending hosts, attacked users and suspicious logins. Originally authored by Phil Schwartz. Platform: UNKNOWN denyhosts-2.10/README.md000066400000000000000000000066041246776277600147710ustar00rootroot00000000000000DenyHosts ========= DenyHosts is a utility developed by Phil Schwartz and maintained by a number of developers which aims to thwart sshd (ssh server) brute force attacks. Please refer to https://github.com/denyhosts/denyhosts for more information. Installation ============ Source Distribution ------------------- If you downloaded the source distribution file (DenyHosts-#.#-tar.gz) then: $ tar zxvf DenyHosts-2.10.tar.gz (Where #.#.# is the version) $ cd denyhosts as root: # python setup.py install This will install the DenyHosts modules into python's site-packages directory. Binary Distribution (rpm, deb, etc) ----------------------------------- It is assumed that you are familiar with installing a binary package on your particular operating system. If you are unsure how to do this, you may wish to install from source instead. All Distributions ----------------- DenyHosts requires that a configuration file be created before it can function. The sample configuration file denyhosts.conf contains most of the possible settings and should be copied and then edited as such: # cp denyhosts.conf /etc # /etc/denyhosts.conf (where is your preferred text editor such as emacs, vi, etc) The sample configuration file contains informational comments that should help you quickly configure DenyHosts. After you have edited your configuration file, save it. Next, if you intend to run DenyHosts in daemon mode (recommended) copy the sample daemon-control.dist script as such: # cp daemon-control-dist daemon-control Edit the daemon-control file. You should only need to edit this section near the top: ############################################### #### Edit these to suit your configuration #### ############################################### DENYHOSTS_BIN = "/usr/bin/denyhosts.py" DENYHOSTS_LOCK = "/var/lock/subsys/denyhosts" DENYHOSTS_CFG = "/etc/denyhosts.conf" These defaults should be reasonable for many systems. You should customize these settings to match your particular system. Once you have edited the configuration and daemon control files make sure that the daemon control script it executable (by root). # chown root daemon-control # chmod 700 daemon-control Starting DenyHosts Manually =========================== Assuming you have configured DenyHosts to run as a daemon, you can use the daemon-control script to control it: # daemon-control start You should refer to the daemon log (typically /var/log/denyhosts) to ensure that DenyHosts is running successfully. If you notice any problems you may wish to consult the FAQ at http://www.denyhosts.net/faq.html If you wish to run DenyHosts from cron rather than as a daemon, please refer to the FAQ. Starting DenyHosts Automatically ================================ Method 1 (preferred) -------------------- Create a symbolic link from /etc/init.d such as: # cd /etc/init.d # ln -s /usr/share/denyhosts/daemon-control denyhosts If you have chkconfig installed you can then use it to ensure that DenyHosts runs at boot time: # chkconfig --add denyhosts If you do not have chkconfig (or similar) installed you can either manually create the symlinks in /etc/rc2.d, /etc/rc3.d, /etc/rc5.d but that is beyond the scope of this document. Method 2 -------- Add an entry into the /etc/rc.local file: /usr/share/denyhosts/daemon-control start denyhosts-2.10/TODO000066400000000000000000000011371246776277600141760ustar00rootroot00000000000000Debian bug 690524 When sshd passwordauthenication is turned off (key access only) then DenyHost does not detect the denied log entry. This means we are not blocking the remote host when multiple attempts are made to access the local service. We should try to detect bad connections to key-only systems. In cases where DenyHosts cannot write to disk, it should not crash. Likewise, if /etc/hosts.deny does not exist, that should not make DenyHost panic or crash. Add new option to write list of banned IP addresses to a location such as /etc/blacklist Consider support for IPv6 Create new sync server. denyhosts-2.10/daemon-control-dist000077500000000000000000000100601246776277600173110ustar00rootroot00000000000000#!/usr/bin/env python # denyhosts Bring up/down the DenyHosts daemon # # chkconfig: 2345 98 02 # description: Activates/Deactivates the # DenyHosts daemon to block ssh attempts # ############################################### ############################################### #### Edit these to suit your configuration #### ############################################### DENYHOSTS_BIN = "/usr/sbin/denyhosts" DENYHOSTS_LOCK = "/run/denyhosts.pid" DENYHOSTS_CFG = "/etc/denyhosts.conf" PYTHON_BIN = "/usr/bin/env python" ############################################### #### Do not edit below #### ############################################### DENYHOSTS_BIN = "%s %s" % (PYTHON_BIN, DENYHOSTS_BIN) import os, sys, signal, time # make sure 'ps' command is accessible (which should be # in either /usr/bin or /bin. Modify the PATH so # popen can find it env = os.environ.get('PATH', "") os.environ['PATH'] = "/usr/bin:/bin:%s" % env STATE_NOT_RUNNING = -1 STATE_LOCK_EXISTS = -2 def usage(): print "Usage: %s {start [args...] | stop | restart [args...] | status | debug | condrestart [args...] }" % sys.argv[0] print print "For a list of valid 'args' refer to:" print "$ denyhosts.py --help" print sys.exit(0) def getpid(): try: fp = open(DENYHOSTS_LOCK, "r") pid = int(fp.readline().rstrip()) fp.close() except Exception, e: return STATE_NOT_RUNNING if not sys.platform.startswith('freebsd') and os.access("/proc", os.F_OK): # proc filesystem exists, look for pid if os.access(os.path.join("/proc", str(pid)), os.F_OK): return pid else: return STATE_LOCK_EXISTS else: # proc filesystem doesn't exist (or it doesn't contain PIDs), use 'ps' p = os.popen("ps -p %d" % pid, "r") p.readline() # get the header line pid_running = p.readline() # pid_running will be '' if no process is found if pid_running: return pid else: return STATE_LOCK_EXISTS def start(*args): cmd = "%s --daemon " % DENYHOSTS_BIN if args: cmd += ' '.join(args) print "starting DenyHosts: ", cmd os.system(cmd) def stop(): pid = getpid() if pid >= 0: os.kill(pid, signal.SIGTERM) print "sent DenyHosts SIGTERM" else: print "DenyHosts is not running" def debug(): pid = getpid() if pid >= 0: os.kill(pid, signal.SIGUSR1) print "sent DenyHosts SIGUSR1" else: print "DenyHosts is not running" def status(): pid = getpid() if pid == STATE_LOCK_EXISTS: print "%s exists but DenyHosts is not running" % DENYHOSTS_LOCK elif pid == STATE_NOT_RUNNING: print "Denyhosts is not running" else: print "DenyHosts is running with pid = %d" % pid def condrestart(*args): pid = getpid() if pid >= 0: restart(*args) def restart(*args): stop() time.sleep(1) start(*args) if __name__ == '__main__': cases = {'start': start, 'stop': stop, 'debug': debug, 'status': status, 'condrestart': condrestart, 'restart': restart} try: args = sys.argv[2:] except Exception: args = [] try: # arg 1 should contain one of the cases above option = sys.argv[1] except Exception: # try to infer context (from an /etc/init.d/ script, perhaps) procname = os.path.basename(sys.argv[0]) infer_dict = {'K': 'stop', 'S': 'start'} option = infer_dict.get(procname[0]) if not option: usage() try: if option in ('start', 'restart', 'condrestart'): anystartswith = lambda prefix, xs: any(map(lambda x: x.startswith(prefix), xs)) if not anystartswith('--config', args) and '-c' not in args: args.append("--config=%s" % DENYHOSTS_CFG) cmd = cases[option] apply(cmd, args) except Exception: usage() denyhosts-2.10/denyhosts.8000066400000000000000000000042071246776277600156200ustar00rootroot00000000000000.TH DENYHOSTS "8" "February 2015" "DenyHosts version: 2.10" "User Commands" .SH NAME DenyHosts \- version: 2.10 .SH DESCRIPTION .B DenyHosts is a python program that automatically blocks ssh attacks by adding entries to /etc/hosts.deny. .B DenyHosts will also inform system administrators about offending hosts, attacked users and suspicious logins. Usage: /usr/sbin/denyhosts [\-f logfile | \fB\-\-file\fR=\fIlogfile]\fR [ \fB\-c\fR configfile | \fB\-\-config\fR=\fIconfigfile]\fR [\-i | \fB\-\-ignore]\fR [\-n | \fB\-\-noemail]\fR [\-\-purge] [\-\-migrate] [\-\-daemon] [\-\-sync] [\-\-version] .TP \fB\-\-file\fR: The name of log file to parse .HP \fB\-\-config\fR: The configuration file to use, typically /etc/denyhosts.conf .HP \fB\-\-ignore\fR: Ignore last processed offset (start processing from beginning) .HP \fB\-\-noemail\fR: Do not send an email report .HP \fB\-\-unlock\fR: if lockfile exists, remove it and run as normal .HP \fB\-\-migrate\fR: migrate your HOSTS_DENY file so that it is suitable for \fB\-\-purge\fR .HP \fB\-\-purge\fR: expire entries older than your PURGE_DENY setting .HP \fB\-\-purge\-all\fR: expire all entries immediately .HP \fB\-\-purgeip\fR: remove specified IP address from blocked list .HP \fB\-\-daemon\fR: run DenyHosts in daemon mode .HP \fB\-\-foreground\fR: run DenyHosts in foreground mode .HP \fB\-\-sync\fR: run DenyHosts synchronization mode .HP \fB\-\-version\fR: Prints the version of DenyHosts and exits .PP Note: multiple \fB\-\-file\fR args can be processed. If multiple files are provided, \fB\-\-ignore\fR is implied .PP Note: in a Debian system, the default running mode is \fBdaemon mode\fR and the configuration file is \fB/etc/denyhosts.conf\fR .SS "When run in --daemon mode the following flags are ignored:" .HP \fB\-\-file\fR, \fB\-\-purge\fR, \fB\-\-migrate\fR, \fB\-\-sync\fR, \fB\-\-verbose\fR .SH "SEE ALSO" Please refer to http://denyhost.sourceforge.net/faq.html for full documentation. .SH AUTHOR .B DenyHosts was written by Phil Schwartz .PP This manual page was written by Marco Bertorello for the Debian project (but may be used by others). denyhosts-2.10/denyhosts.conf000066400000000000000000000560561246776277600164070ustar00rootroot00000000000000 ############ THESE SETTINGS ARE REQUIRED ############ ######################################################################## # # SECURE_LOG: the log file that contains sshd logging info # if you are not sure, grep "sshd:" /var/log/* # # The file to process can be overridden with the --file command line # argument # # Redhat or Fedora Core: #SECURE_LOG = /var/log/secure # # Mandrake, FreeBSD or OpenBSD: #SECURE_LOG = /var/log/auth.log # # SuSE or Gentoo: #SECURE_LOG = /var/log/messages # # Mac OS X (v10.4 or greater - # also refer to: http://www.denyhost.net/faq.html#macos #SECURE_LOG = /private/var/log/asl.log # # Mac OS X (v10.3 or earlier): #SECURE_LOG=/private/var/log/system.log # # Debian and Ubuntu SECURE_LOG = /var/log/auth.log ######################################################################## ######################################################################## # # HOSTS_DENY: the file which contains restricted host access information # # Most operating systems: HOSTS_DENY = /etc/hosts.deny # # Some BSD (FreeBSD) Unixes: #HOSTS_DENY = /etc/hosts.allow # # Another possibility (also see the next option): #HOSTS_DENY = /etc/hosts.evil ####################################################################### ######################################################################## # # PURGE_DENY: removed HOSTS_DENY entries that are older than this time # when DenyHosts is invoked with the --purge flag # # format is: i[dhwmy] # Where 'i' is an integer (eg. 7) # 'm' = minutes # 'h' = hours # 'd' = days # 'w' = weeks # 'y' = years # # never purge: PURGE_DENY = # # purge entries older than 1 week #PURGE_DENY = 1w # # purge entries older than 5 days #PURGE_DENY = 5d ####################################################################### ####################################################################### # # PURGE_THRESHOLD: defines the maximum times a host will be purged. # Once this value has been exceeded then this host will not be purged. # Setting this parameter to 0 (the default) disables this feature. # # default: a denied host can be purged/re-added indefinitely #PURGE_THRESHOLD = 0 # # a denied host will be purged at most 2 times. #PURGE_THRESHOLD = 2 # ####################################################################### ####################################################################### # # BLOCK_SERVICE: the service name that should be blocked in HOSTS_DENY # # man 5 hosts_access for details # # eg. sshd: 127.0.0.1 # will block sshd logins from 127.0.0.1 # # To block all services for the offending host: #BLOCK_SERVICE = ALL # To block only sshd: BLOCK_SERVICE = sshd # To only record the offending host and nothing else (if using # an auxilary file to list the hosts). Refer to: # http://denyhost.sourceforge.net/faq.html#aux #BLOCK_SERVICE = # ####################################################################### ####################################################################### # # DENY_THRESHOLD_INVALID: block each host after the number of failed login # attempts has exceeded this value. This value applies to invalid # user login attempts (eg. non-existent user accounts) # DENY_THRESHOLD_INVALID = 5 # ####################################################################### ####################################################################### # # DENY_THRESHOLD_VALID: block each host after the number of failed # login attempts has exceeded this value. This value applies to valid # user login attempts (eg. user accounts that exist in /etc/passwd) except # for the "root" user # DENY_THRESHOLD_VALID = 10 # ####################################################################### ####################################################################### # # DENY_THRESHOLD_ROOT: block each host after the number of failed # login attempts has exceeded this value. This value applies to # "root" user login attempts only. # DENY_THRESHOLD_ROOT = 1 # ####################################################################### ####################################################################### # # DENY_THRESHOLD_RESTRICTED: block each host after the number of failed # login attempts has exceeded this value. This value applies to # usernames that appear in the WORK_DIR/restricted-usernames file only. # DENY_THRESHOLD_RESTRICTED = 1 # ####################################################################### ####################################################################### # # WORK_DIR: the path that DenyHosts will use for writing data to # (it will be created if it does not already exist). # # Note: it is recommended that you use an absolute pathname # for this value (eg. /home/foo/denyhost/data) # WORK_DIR = /var/lib/denyhosts # ####################################################################### ####################################################################### # # ETC_DIR: the path that DenyHosts will use for reading data when # we need configuration information. # # Note: it is recommended that you use an absolute pathname # for this value (eg. /etc or /usr/local/etc) # ETC_DIR = /etc # ####################################################################### ####################################################################### # # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS # # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES|NO # If set to YES, if a suspicious login attempt results from an allowed-host # then it is considered suspicious. If this is NO, then suspicious logins # from allowed-hosts will not be reported. All suspicious logins from # ip addresses that are not in allowed-hosts will always be reported. # SUSPICIOUS_LOGIN_REPORT_ALLOWED_HOSTS=YES ###################################################################### ###################################################################### # # HOSTNAME_LOOKUP # # HOSTNAME_LOOKUP=YES|NO # If set to YES, for each IP address that is reported by Denyhosts, # the corresponding hostname will be looked up and reported as well # (if available). # HOSTNAME_LOOKUP=NO # ###################################################################### ###################################################################### # # LOCK_FILE # # LOCK_FILE=/path/denyhosts # If this file exists when DenyHosts is run, then DenyHosts will exit # immediately. Otherwise, this file will be created upon invocation # and deleted upon exit. This ensures that only one instance is # running at a time. # # Redhat/Fedora: #LOCK_FILE = /var/lock/subsys/denyhosts # # Debian or Gentoo LOCK_FILE = /var/run/denyhosts.pid # # Misc #LOCK_FILE = /tmp/denyhosts.lock # ###################################################################### ############ THESE SETTINGS ARE OPTIONAL ############ ####################################################################### # # IPTABLES: if you would like DenyHost to block incoming connections # using the Linux firewall IPTABLES, then set the following variable # to the path of the iptables executable. Typically this is # /sbin/iptables # If this option is not set or commented out then the iptables # firewall is not used. IPTABLES = /sbin/iptables # # Warning: If you are running IPTABLES, please make sure to comment # out the PFCTL_PATH and the PF_TABLE variables below. PF and # IPTABLES should not be running together on the same operating system. # # By default DenyHost will ask IPTables to block incoming connections # from an aggressive host on ALL ports. While this is usually a good # idea, it may prevent some botted machines from being able to access # services their legitmate users want, like a web server. To only # block specific ports, enable the following option. # BLOCKPORT = 22 # ####################################################################### ####################################################################### # # On FreeBSD/OpenBSD/TrueOS/PC-BSD/NetBSD we may want to block incoming # traffic using the PF firewall instead of the hosts.deny file # (aka tcp_wrapper). # The admin can set up a PF table that is persistent # and DenyHost can add new addresses to be blocked to that table. # The TrueOS operating system enables this by default, blocking # all addresses in the "blacklist" table. # # To have DenyHost update the blocking PF table in real time, uncomment # these next two options. Make sure the table name specificed # is one created in the pf.conf file of your operating system. # The PFCTL_PATH variable must point to the pfctl extectuable on your OS. # PFCTL_PATH = /sbin/pfctl # PF_TABLE = blacklist # Note, a good rule to have in your pf.conf file to enable the # blacklist table is: # # table persist file "/etc/blacklist" # block in quick from to any # # Warning: If you are using PF, please make sure to disable the # IPTABLES rule above as these two packet filters should not be # run together on the same operating system. # Note: Even if you decide to run DenyHost with PF filtering # only and no hosts.deny support, please still create an empty # file called /etc/hosts.deny for backward compatibility. # Also, please make sure PF is enabled prior to launching # DenyHosts. To do this run "pfctl -e". ####################################################################### ####################################################################### # # ADMIN_EMAIL: if you would like to receive emails regarding newly # restricted hosts and suspicious logins, set this address to # match your email address. If you do not want to receive these reports # leave this field blank (or run with the --noemail option) # # Multiple email addresses can be delimited by a comma, eg: # ADMIN_EMAIL = foo@bar.com, bar@foo.com, etc@foobar.com # ADMIN_EMAIL = root@localhost # ####################################################################### ####################################################################### # # SMTP_HOST and SMTP_PORT: if DenyHosts is configured to email # reports (see ADMIN_EMAIL) then these settings specify the # email server address (SMTP_HOST) and the server port (SMTP_PORT) # # SMTP_HOST = localhost SMTP_PORT = 25 # ####################################################################### ####################################################################### # # SMTP_USERNAME and SMTP_PASSWORD: set these parameters if your # smtp email server requires authentication # #SMTP_USERNAME=foo #SMTP_PASSWORD=bar # ###################################################################### ####################################################################### # # SMTP_FROM: you can specify the "From:" address in messages sent # from DenyHosts when it reports thwarted abuse attempts # SMTP_FROM = DenyHosts # ####################################################################### ####################################################################### # # SMTP_SUBJECT: you can specify the "Subject:" of messages sent # by DenyHosts when it reports thwarted abuse attempts SMTP_SUBJECT = DenyHosts Report # ###################################################################### ###################################################################### # # SMTP_DATE_FORMAT: specifies the format used for the "Date:" header # when sending email messages. # # for possible values for this parameter refer to: man strftime # # the default: # #SMTP_DATE_FORMAT = %a, %d %b %Y %H:%M:%S %z # ###################################################################### ###################################################################### # # SYSLOG_REPORT # # SYSLOG_REPORT=YES|NO # If set to yes, when denied hosts are recorded the report data # will be sent to syslog (syslog must be present on your system). # The default is: NO # #SYSLOG_REPORT=NO # #SYSLOG_REPORT=YES # ###################################################################### ###################################################################### # # ALLOWED_HOSTS_HOSTNAME_LOOKUP # # ALLOWED_HOSTS_HOSTNAME_LOOKUP=YES|NO # If set to YES, for each entry in the WORK_DIR/allowed-hosts file, # the hostname will be looked up. If your versions of tcp_wrappers # and sshd sometimes log hostnames in addition to ip addresses # then you may wish to specify this option. # ALLOWED_HOSTS_HOSTNAME_LOOKUP=NO # ###################################################################### ###################################################################### # # AGE_RESET_VALID: Specifies the period of time between failed login # attempts that, when exceeded will result in the failed count for # this host to be reset to 0. This value applies to login attempts # to all valid users (those within /etc/passwd) with the # exception of root. If not defined, this count will never # be reset. # # See the comments in the PURGE_DENY section (above) # for details on specifying this value or for complete details # refer to: http://denyhost.sourceforge.net/faq.html#timespec # AGE_RESET_VALID=5d # ###################################################################### ###################################################################### # # AGE_RESET_ROOT: Specifies the period of time between failed login # attempts that, when exceeded will result in the failed count for # this host to be reset to 0. This value applies to all login # attempts to the "root" user account. If not defined, # this count will never be reset. # # See the comments in the PURGE_DENY section (above) # for details on specifying this value or for complete details # refer to: http://denyhost.sourceforge.net/faq.html#timespec # AGE_RESET_ROOT=25d # ###################################################################### ###################################################################### # # AGE_RESET_RESTRICTED: Specifies the period of time between failed login # attempts that, when exceeded will result in the failed count for # this host to be reset to 0. This value applies to all login # attempts to entries found in the WORK_DIR/restricted-usernames file. # If not defined, the count will never be reset. # # See the comments in the PURGE_DENY section (above) # for details on specifying this value or for complete details # refer to: http://denyhost.sourceforge.net/faq.html#timespec # AGE_RESET_RESTRICTED=25d # ###################################################################### ###################################################################### # # AGE_RESET_INVALID: Specifies the period of time between failed login # attempts that, when exceeded will result in the failed count for # this host to be reset to 0. This value applies to login attempts # made to any invalid username (those that do not appear # in /etc/passwd). If not defined, count will never be reset. # # See the comments in the PURGE_DENY section (above) # for details on specifying this value or for complete details # refer to: http://denyhost.sourceforge.net/faq.html#timespec # AGE_RESET_INVALID=10d # ###################################################################### ###################################################################### # # RESET_ON_SUCCESS: If this parameter is set to "yes" then the # failed count for the respective ip address will be reset to 0 # if the login is successful. # # The default is RESET_ON_SUCCESS = no # #RESET_ON_SUCCESS = yes # ##################################################################### ###################################################################### # # PLUGIN_DENY: If set, this value should point to an executable # program that will be invoked when a host is added to the # HOSTS_DENY file. This executable will be passed the host # that will be added as its only argument. # #PLUGIN_DENY=/usr/bin/true # ###################################################################### ###################################################################### # # PLUGIN_PURGE: If set, this value should point to an executable # program that will be invoked when a host is removed from the # HOSTS_DENY file. This executable will be passed the host # that is to be purged as it's only argument. # #PLUGIN_PURGE=/usr/bin/true # ###################################################################### ###################################################################### # # USERDEF_FAILED_ENTRY_REGEX: if set, this value should contain # a regular expression that can be used to identify additional # hackers for your particular ssh configuration. This functionality # extends the built-in regular expressions that DenyHosts uses. # This parameter can be specified multiple times. # See this faq entry for more details: # http://denyhost.sf.net/faq.html#userdef_regex # #USERDEF_FAILED_ENTRY_REGEX= # # ###################################################################### ######### THESE SETTINGS ARE SPECIFIC TO DAEMON MODE ########## ####################################################################### # # DAEMON_LOG: when DenyHosts is run in daemon mode (--daemon flag) # this is the logfile that DenyHosts uses to report its status. # To disable logging, leave blank. (default is: /var/log/denyhosts) # DAEMON_LOG = /var/log/denyhosts # # disable logging: #DAEMON_LOG = # ###################################################################### ####################################################################### # # DAEMON_LOG_TIME_FORMAT: when DenyHosts is run in daemon mode # (--daemon flag) this specifies the timestamp format of # the DAEMON_LOG messages (default is the ISO8061 format: # ie. 2005-07-22 10:38:01,745) # # for possible values for this parameter refer to: man strftime # # Jan 1 13:05:59 #DAEMON_LOG_TIME_FORMAT = %b %d %H:%M:%S # # Jan 1 01:05:59 #DAEMON_LOG_TIME_FORMAT = %b %d %I:%M:%S # ###################################################################### ####################################################################### # # DAEMON_LOG_MESSAGE_FORMAT: when DenyHosts is run in daemon mode # (--daemon flag) this specifies the message format of each logged # entry. By default the following format is used: # # %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s # # Where the "%(asctime)s" portion is expanded to the format # defined by DAEMON_LOG_TIME_FORMAT # # This string is passed to python's logging.Formatter contstuctor. # For details on the possible format types please refer to: # http://docs.python.org/lib/node357.html # # This is the default: #DAEMON_LOG_MESSAGE_FORMAT = %(asctime)s - %(name)-12s: %(levelname)-8s %(message)s # # ###################################################################### ####################################################################### # # DAEMON_SLEEP: when DenyHosts is run in daemon mode (--daemon flag) # this is the amount of time DenyHosts will sleep between polling # the SECURE_LOG. See the comments in the PURGE_DENY section (above) # for details on specifying this value or for complete details # refer to: http://denyhost.sourceforge.net/faq.html#timespec # # DAEMON_SLEEP = 30s # ####################################################################### ####################################################################### # # DAEMON_PURGE: How often should DenyHosts, when run in daemon mode, # run the purge mechanism to expire old entries in HOSTS_DENY # This has no effect if PURGE_DENY is blank. # DAEMON_PURGE = 1h # ####################################################################### ######### THESE SETTINGS ARE SPECIFIC TO ########## ######### DAEMON SYNCHRONIZATION ########## ####################################################################### # # Synchronization mode allows the DenyHosts daemon the ability # to periodically send and receive denied host data such that # DenyHosts daemons worldwide can automatically inform one # another regarding banned hosts. This mode is disabled by # default, you must uncomment SYNC_SERVER to enable this mode. # # for more information, please refer to: # http:/denyhost.sourceforge.net/faq.html # ####################################################################### ####################################################################### # # SYNC_SERVER: The central server that communicates with DenyHost # daemons. # # To disable synchronization (the default), do nothing. # # To enable synchronization, you must uncomment the following line: #SYNC_SERVER = http://xmlrpc.denyhosts.net:9911 # ####################################################################### ####################################################################### # # SYNC_INTERVAL: the interval of time to perform synchronizations if # SYNC_SERVER has been uncommented. The default is 1 hour. # #SYNC_INTERVAL = 1h # ####################################################################### ####################################################################### # # SYNC_UPLOAD: allow your DenyHosts daemon to transmit hosts that have # been denied? This option only applies if SYNC_SERVER has # been uncommented. # The default is SYNC_UPLOAD = yes # SYNC_UPLOAD = no #SYNC_UPLOAD = yes # ####################################################################### ####################################################################### # # SYNC_DOWNLOAD: allow your DenyHosts daemon to receive hosts that have # been denied by others? This option only applies if SYNC_SERVER has # been uncommented. # The default is SYNC_DOWNLOAD = yes # SYNC_DOWNLOAD = no #SYNC_DOWNLOAD = yes # # # ####################################################################### ####################################################################### # # SYNC_DOWNLOAD_THRESHOLD: If SYNC_DOWNLOAD is enabled this parameter # filters the returned hosts to those that have been blocked this many # times by others. That is, if set to 1, then if a single DenyHosts # server has denied an ip address then you will receive the denied host. # # See also SYNC_DOWNLOAD_RESILIENCY # #SYNC_DOWNLOAD_THRESHOLD = 10 # # The default is SYNC_DOWNLOAD_THRESHOLD = 3 # #SYNC_DOWNLOAD_THRESHOLD = 3 # ####################################################################### ####################################################################### # # SYNC_DOWNLOAD_RESILIENCY: If SYNC_DOWNLOAD is enabled then the # value specified for this option limits the downloaded data # to this resiliency period or greater. # # Resiliency is defined as the timespan between a hackers first known # attack and its most recent attack. Example: # # If the centralized denyhosts.net server records an attack at 2 PM # and then again at 5 PM, specifying a SYNC_DOWNLOAD_RESILIENCY = 4h # will not download this ip address. # # However, if the attacker is recorded again at 6:15 PM then the # ip address will be downloaded by your DenyHosts instance. # # This value is used in conjunction with the SYNC_DOWNLOAD_THRESHOLD # and only hosts that satisfy both values will be downloaded. # This value has no effect if SYNC_DOWNLOAD_THRESHOLD = 1 # # The default is SYNC_DOWNLOAD_RESILIENCY = 5h (5 hours) # # Only obtain hackers that have been at it for 2 days or more: #SYNC_DOWNLOAD_RESILIENCY = 2d # # Only obtain hackers that have been at it for 5 hours or more: #SYNC_DOWNLOAD_RESILIENCY = 5h # ####################################################################### denyhosts-2.10/denyhosts.py000077500000000000000000000205141246776277600161030ustar00rootroot00000000000000#!/usr/bin/env python import os import platform import sys sys.path.insert(0, '/usr/share/denyhosts') import DenyHosts.python_version import getopt from getopt import GetoptError import traceback from DenyHosts.util import die, setup_logging, is_true from DenyHosts.lockfile import LockFile from DenyHosts.prefs import Prefs from DenyHosts.version import VERSION from DenyHosts.deny_hosts import DenyHosts from DenyHosts.denyfileutil import Purge, PurgeIP, Migrate, UpgradeTo099 from DenyHosts.constants import * from DenyHosts.sync import Sync ################################################################################# def usage(): print "Usage:" print "%s [-f logfile | --file=logfile] [ -c configfile | --config=configfile] [-i | --ignore] [-n | --noemail] [--purge] [--purge-all] [--purgeip=ip] [--migrate] [--daemon] [--sync] [--version]" % sys.argv[0] print print print " --config: The pathname of the configuration file" print " --file: The name of log file to parse" print " --ignore: Ignore last processed offset (start processing from beginning)" print " --noemail: Do not send an email report" print " --unlock: if lockfile exists, remove it and run as normal" print " --migrate: migrate your HOSTS_DENY file so that it is suitable for --purge" print " --purge: expire entries older than your PURGE_DENY setting" print " --purge-all: expire all entries" print " --purgeip: expire designated IP entry immediately" print " --daemon: run DenyHosts in daemon mode" print " --foreground: run DenyHosts in foreground mode" print " --sync: run DenyHosts synchronization mode" print " --version: Prints the version of DenyHosts and exits" print print "Note: multiple --file args can be processed. ", print "If multiple files are provided, --ignore is implied" print print "Note: multiple --purgeip arguments can be processed. " print print "When run in --daemon mode the following flags are ignored:" print " --file, --purge, --purge-all, --purgeip, --migrate, --sync, --verbose" ################################################################################# ################################################################################# if __name__ == '__main__': logfiles = [] purgeip_list = [] config_file = CONFIG_FILE ignore_offset = 0 noemail = 0 verbose = 0 migrate = 0 purge = 0 purge_all = 0 sync_mode = 0 daemon = 0 foreground = 0 enable_debug = 0 purgeip = 0 upgrade099 = 0 unlock = 0 args = sys.argv[1:] try: (opts, getopts) = getopt.getopt(args, 'f:c:dinuvps?hV', ["file=", "ignore", "verbose", "debug", "help", "noemail", "config=", "version", "migrate", "purge", "purge-all", "purgeip", "daemon", "foreground", "unlock", "sync", "upgrade099"]) except GetoptError: print "\nInvalid command line option detected." usage() sys.exit(1) for opt, arg in opts: if opt in ('-h', '-?', '--help'): usage() sys.exit(0) if opt in ('-f', '--file'): logfiles.append(arg) if opt in ('-i', '--ignore'): ignore_offset = 1 if opt in ('-n', '--noemail'): noemail = 1 if opt in ('-v', '--verbose'): verbose = 1 if opt in ('-d', '--debug'): enable_debug = 1 if opt in ('-c', '--config'): config_file = arg if opt in ('-m', '--migrate'): migrate = 1 if opt in ('-p', '--purge'): purge = 1 if opt in ('-s', '--sync'): sync_mode = 1 if opt in ('-s', '--unlock'): unlock = 1 if opt == '--daemon': daemon = 1 if opt == '--foreground': foreground = 1 if opt == '--purge-all': purge_all = 1 if opt == '--purgeip': purgeip_list.append(arg) purgeip = 1 if opt == '--upgrade099': upgrade099 = 1 if opt == '--version': print "DenyHosts version:", VERSION sys.exit(0) # This is generally expected to be in the environment, but there's no # non-hackish way to get systemd to set it, so just hack it in here. os.environ['HOSTNAME'] = platform.node() prefs = Prefs(config_file) first_time = 0 try: os.makedirs(prefs.get('WORK_DIR')) first_time = 1 except Exception, e: if e[0] != 17: print e sys.exit(1) setup_logging(prefs, enable_debug, verbose, daemon) if not logfiles or daemon: logfiles = [prefs.get('SECURE_LOG')] elif len(logfiles) > 1: ignore_offset = 1 if not prefs.get('ADMIN_EMAIL'): noemail = 1 lock_file = LockFile(prefs.get('LOCK_FILE')) if unlock: if os.path.isfile( prefs.get('LOCK_FILE') ): lock_file.remove() lock_file.create() if upgrade099 and not (daemon or foreground): if not prefs.get('PURGE_DENY'): lock_file.remove() die("You have supplied the --upgrade099 flag, however you have not set PURGE_DENY in your configuration file") else: u = UpgradeTo099(prefs.get("HOSTS_DENY")) if migrate and not (daemon or foreground): if not prefs.get('PURGE_DENY'): lock_file.remove() die("You have supplied the --migrate flag however you have not set PURGE_DENY in your configuration file.") else: m = Migrate(prefs.get("HOSTS_DENY")) # clear out specific IP addresses if purgeip and not daemon: if len(purgeip_list) < 1: lock_file.remove() die("You have provided the --purgeip flag however you have not listed any IP addresses to purge.") else: try: p = PurgeIP(prefs, purgeip_list) except Exception, e: lock_file.remove() die(str(e)) # Try to purge old records without any delay if purge_all and not daemon: purge_time = 1 try: p = Purge(prefs, purge_time) except Exception, e: lock_file.remove() die(str(e)) if purge and not (daemon or foreground): purge_time = prefs.get('PURGE_DENY') if not purge_time: lock_file.remove() die("You have provided the --purge flag however you have not set PURGE_DENY in your configuration file.") else: try: p = Purge(prefs, purge_time) except Exception, e: lock_file.remove() die(str(e)) try: for f in logfiles: dh = DenyHosts(f, prefs, lock_file, ignore_offset, first_time, noemail, daemon, foreground) except KeyboardInterrupt: pass except SystemExit, e: pass except Exception, e: traceback.print_exc(file=sys.stdout) print "\nDenyHosts exited abnormally" if sync_mode and not (daemon or foreground): if not prefs.get('SYNC_SERVER'): lock_file.remove() die("You have provided the --sync flag however your configuration file is missing a value for SYNC_SERVER.") sync_upload = is_true(prefs.get("SYNC_UPLOAD")) sync_download = is_true(prefs.get("SYNC_DOWNLOAD")) if not sync_upload and not sync_download: lock_file.remove() die("You have provided the --sync flag however your configuration file has SYNC_UPLOAD and SYNC_DOWNLOAD set to false.") try: sync = Sync(prefs) if sync_upload: timestamp = sync.send_new_hosts() if sync_download: new_hosts = sync.receive_new_hosts() if new_hosts: # MMR: What is 'info' here? info("received new hosts: %s", str(new_hosts)) sync.get_denied_hosts() sync.update_hosts_deny(new_hosts) sync.xmlrpc_disconnect() except Exception, e: lock_file.remove() die("Error synchronizing data", e) # remove lock file on exit lock_file.remove() denyhosts-2.10/denyhosts.service000066400000000000000000000004111246776277600171020ustar00rootroot00000000000000[Unit] Description=SSH log watcher Before=sshd.service [Service] Type=forking ExecStartPre=/bin/rm -f /var/run/denyhosts.pid ExecStart=/usr/bin/denyhosts.py --daemon --config=/etc/denyhosts.conf PIDFile=/var/run/denyhosts.pid [Install] WantedBy=multi-user.target denyhosts-2.10/plugins/000077500000000000000000000000001246776277600151655ustar00rootroot00000000000000denyhosts-2.10/plugins/README.contrib000066400000000000000000000016021246776277600175030ustar00rootroot00000000000000Plugin contributions are always welcome. For inclusion in an upcoming release please email the following to the DenyHosts mailing list: denyhosts-user@lists.sourceforge.net Please consider the following conventions when submitting a plugin module: 1. Each plugin should accept a single command line arg (the ip address) 2. Each plugin should be named appropriately (that is, the filename should describe what the plugin does). 3. You should include your name and contact information at the top (in the form of comments) of the module. In this way, if somebody has a problem with your plugin, they can contact you rather than me! Also, feel free to include a url where a user can find an updated version of your plugin. 4. You should also document any dependencies that your plugin has. 5. Email the plugin to denyhosts-user@lists.sourceforge.net as an attachment. denyhosts-2.10/plugins/shorewall_allow.sh000066400000000000000000000001241246776277600207140ustar00rootroot00000000000000#!/bin/sh # # Stéphane LeDauphin # /sbin/shorewall allow $1 denyhosts-2.10/plugins/shorewall_deny.sh000066400000000000000000000001251246776277600205360ustar00rootroot00000000000000#!/bin/sh # # Stéphane LeDauphin # /sbin/shorewall reject $1 denyhosts-2.10/plugins/test_deny.py000077500000000000000000000001671246776277600175440ustar00rootroot00000000000000#!/usr/bin/python import sys print "%s invoked with the following args: %s" % (sys.argv[0], sys.argv[1:]) sys.exit(0) denyhosts-2.10/scripts/000077500000000000000000000000001246776277600151735ustar00rootroot00000000000000denyhosts-2.10/scripts/restricted_from_invalid.py000077500000000000000000000016371246776277600224600ustar00rootroot00000000000000#!/usr/bin/env python import os, sys def usage(): print "%s WORK_DIR [num_results]" % sys.argv[0] sys.exit(1) try: work_dir = sys.argv[1] except IndexError: print "you must specify your DenyHosts WORK_DIR" usage() try: num = int(sys.argv[2]) except IndexError: num = 10 fname = os.path.join(work_dir, "users-invalid") try: fp = open(fname, "r") except IOError: print fname, "does not exist" sys.exit(1) d = {} for line in fp: try: foo = line.split(":") username = foo[0] attempts = int(foo[1]) # timestamp = foo[2].strip() except Exception: continue l = d.get(attempts, []) l.append(username) d[attempts] = l fp.close() keys = d.keys() keys.sort() keys.reverse() i = 0 for key in keys: l = d.get(key) for username in l: i += 1 print username if i >= num: break if i >= num: break denyhosts-2.10/scripts/restricted_from_passwd.py000077500000000000000000000016371246776277600223330ustar00rootroot00000000000000#!/usr/bin/env python # ############################################################################ # this script will read the /etc/passwd file and extract usernames # that have one of the RESTRICTED_SHELLS. The ouput of this # script is a list of these restricted usernames suitable for # use in WORK_DIR/restricted-usernames # # such as: python restricted_from_passwd > $WORK_DIR/restricted-usernames # where $WORK_DIR is your DenyHosts WORK_DIR parameter # ############################################################################ RESTRICTED_SHELLS = ("/sbin/nologin", "/usr/sbin/nologin", "/sbin/shutdown", "/sbin/halt") from pwd import getpwall passwd = getpwall() usernames = [] for pw_tuple in passwd: if pw_tuple[6] in RESTRICTED_SHELLS: usernames.append(pw_tuple[0]) usernames.sort() for username in usernames: print username denyhosts-2.10/setup.py000066400000000000000000000025021246776277600152150ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2005-2006 (C) Phil Schwartz # Copyright 2014 (C) Jesse Smith from glob import glob from os.path import join as ospj from distutils.core import setup from DenyHosts.util import normalize_whitespace from DenyHosts.version import VERSION etcpath = "/etc" manpath = "/usr/share/man/man8" libpath = "/usr/share/denyhosts" scriptspath = ospj("scripts", libpath) pluginspath = ospj("plugins", libpath) setup( name="DenyHosts", version=VERSION, description="DenyHost is a utility to help sys admins thwart ssh hackers", author="Jesse Smith", author_email="jessefrgsmith@yahoo.ca", url="http://denyhost.sourceforge.net", scripts=['denyhosts.py', 'daemon-control-dist'], package_dir={'DenyHosts': 'DenyHosts'}, packages=["DenyHosts"], data_files=[ (etcpath, glob("denyhosts.conf")), (manpath, glob("denyhosts.8")), ], license="GPL v2", long_description=normalize_whitespace( """ DenyHosts is a python program that automatically blocks ssh attacks by adding entries to /etc/hosts.deny. DenyHosts will also inform administrators about offending hosts, attacked users and suspicious logins. Originally written by Phil Schwartz. """ ), ) denyhosts-2.10/tests/000077500000000000000000000000001246776277600146465ustar00rootroot00000000000000denyhosts-2.10/tests/__init__.py000066400000000000000000000000001246776277600167450ustar00rootroot00000000000000denyhosts-2.10/tests/data/000077500000000000000000000000001246776277600155575ustar00rootroot00000000000000denyhosts-2.10/tests/data/allowed-hosts000066400000000000000000000000701246776277600202640ustar00rootroot00000000000000127.0.0.1 172.16.0.1 192.168.1.* 1.1.1.[20-40] hostname denyhosts-2.10/tests/data/deny_hosts/000077500000000000000000000000001246776277600177365ustar00rootroot00000000000000denyhosts-2.10/tests/data/deny_hosts/.gitignore000066400000000000000000000000111246776277600217160ustar00rootroot00000000000000lockfile denyhosts-2.10/tests/data/deny_hosts/etc/000077500000000000000000000000001246776277600205115ustar00rootroot00000000000000denyhosts-2.10/tests/data/deny_hosts/etc/.gitignore000066400000000000000000000000001246776277600224670ustar00rootroot00000000000000denyhosts-2.10/tests/data/deny_hosts/work/000077500000000000000000000000001246776277600207205ustar00rootroot00000000000000denyhosts-2.10/tests/data/deny_hosts/work/logfile000066400000000000000000000000001246776277600222520ustar00rootroot00000000000000denyhosts-2.10/tests/data/purgecounter/000077500000000000000000000000001246776277600203015ustar00rootroot00000000000000denyhosts-2.10/tests/data/purgecounter/dynamic/000077500000000000000000000000001246776277600217255ustar00rootroot00000000000000denyhosts-2.10/tests/data/purgecounter/dynamic/.gitignore000066400000000000000000000000161246776277600237120ustar00rootroot00000000000000purge-history denyhosts-2.10/tests/data/purgecounter/static/000077500000000000000000000000001246776277600215705ustar00rootroot00000000000000denyhosts-2.10/tests/data/purgecounter/static/purge-history000066400000000000000000000000401246776277600243260ustar00rootroot00000000000000host:2:Tue Dec 16 11:24:15 2014 denyhosts-2.10/tests/data/restricted/000077500000000000000000000000001246776277600177275ustar00rootroot00000000000000denyhosts-2.10/tests/data/restricted/restricted-usernames000066400000000000000000000000351246776277600240200ustar00rootroot00000000000000# Comment line host1 host2 denyhosts-2.10/tests/data/sync/000077500000000000000000000000001246776277600165335ustar00rootroot00000000000000denyhosts-2.10/tests/data/sync/dynamic/000077500000000000000000000000001246776277600201575ustar00rootroot00000000000000denyhosts-2.10/tests/data/sync/dynamic/.gitignore000066400000000000000000000000171246776277600221450ustar00rootroot00000000000000sync-timestamp denyhosts-2.10/tests/data/sync/static/000077500000000000000000000000001246776277600200225ustar00rootroot00000000000000denyhosts-2.10/tests/data/sync/static/sync-timestamp000066400000000000000000000000121246776277600227130ustar00rootroot00000000000000427850432 denyhosts-2.10/tests/test_allowedhosts.py000066400000000000000000000070371246776277600207760ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import abspath, dirname, join as ospj from tempfile import mkdtemp import unittest from DenyHosts.allowedhosts import AllowedHosts from DenyHosts.constants import ALLOWED_WARNED_HOSTS from DenyHosts.prefs import Prefs # TODO use assertIn/assertNotIn when dropping support for 2.6 class AllowedHostsBase(unittest.TestCase): def setUp(self): data_dir = ospj(dirname(abspath(__file__)), 'data') self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = data_dir self.prefs._Prefs__data['ALLOWED_HOSTS_HOSTNAME_LOOKUP'] = 'false' self.allowed_hosts = AllowedHosts(self.prefs) class AllowedHostsBasicTest(AllowedHostsBase): def test_positives(self): self.assertTrue('127.0.0.1' in self.allowed_hosts) self.assertTrue('172.16.0.1' in self.allowed_hosts) def test_negatives(self): self.assertFalse('10.0.0.1' in self.allowed_hosts) self.assertFalse('127.0.0.2' in self.allowed_hosts) self.assertFalse('0.0.0.0' in self.allowed_hosts) self.assertFalse('bogus' in self.allowed_hosts) class AllowedHostsWildcardTest(AllowedHostsBase): def test_positives(self): self.assertTrue('192.168.1.1' in self.allowed_hosts) self.assertTrue('192.168.1.255' in self.allowed_hosts) def test_negatives(self): self.assertFalse('192.168.0.1' in self.allowed_hosts) self.assertFalse('255.255.255.255' in self.allowed_hosts) class AllowedHostsRangeTest(AllowedHostsBase): def test_positives(self): self.assertTrue('1.1.1.20' in self.allowed_hosts) self.assertTrue('1.1.1.39' in self.allowed_hosts) def test_negatives(self): self.assertFalse('1.1.1.10' in self.allowed_hosts) self.assertFalse('1.1.1.50' in self.allowed_hosts) class AllowedHostsHostnameTest(AllowedHostsBase): def test_positives(self): self.assertTrue('hostname' in self.allowed_hosts) def test_negatives(self): self.assertFalse('another_hostname' in self.allowed_hosts) class AllowedHostsWarnedHostsTest(unittest.TestCase): """ Not a subclass of AllowedHostsBase since testing the warned hosts functionality is kind of distinct from tracking a set of allowed hosts """ def setUp(self): self.work_dir = mkdtemp() self.warned_hosts_filename = ospj(self.work_dir, ALLOWED_WARNED_HOSTS) self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = self.work_dir self.prefs._Prefs__data['ALLOWED_HOSTS_HOSTNAME_LOOKUP'] = 'false' self.allowed_hosts = AllowedHosts(self.prefs) def test_no_new_warned_host(self): self.assertFalse(self.allowed_hosts.get_new_warned_hosts()) def test_no_warned_host(self): self.allowed_hosts.load_warned_hosts() self.assertFalse(self.allowed_hosts.get_new_warned_hosts()) def test_load_warned_hosts(self): hosts = ['host1', 'host2'] with open(self.warned_hosts_filename, 'w') as f: for host in hosts: print(host, file=f) self.allowed_hosts.load_warned_hosts() self.assertEqual(set(hosts), set(self.allowed_hosts.warned_hosts)) def test_save_warned_hosts(self): hosts = ['host1', 'host2'] test_string = ''.join(host + '\n' for host in hosts) for host in hosts: self.allowed_hosts.add_warned_host(host) self.allowed_hosts.save_warned_hosts() with open(self.warned_hosts_filename) as f: self.assertEqual(test_string, f.read()) denyhosts-2.10/tests/test_counter.py000066400000000000000000000076711246776277600177510ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from datetime import datetime, timedelta import time import unittest from DenyHosts.counter import Counter, CounterRecord class CounterRecordTest(unittest.TestCase): def test_init(self): c = CounterRecord() self.assertEqual(c.get_count(), 0) # Counter.__date is initialized with time.asctime(), so there isn't # much to test beyond the type self.assertTrue(isinstance(c.get_date(), str)) def test_init_provided_date(self): """ CounterRecord.__date is intended to be a string (for some reason; a datetime object would be more appropriate), but any object can be used. Verify that what we pass to the constructor is accessible. """ date = object() c = CounterRecord(date=date) self.assertTrue(c.get_date() is date) def test_init_provided_count(self): """ CounterRecord.__count is intended to be numeric, but any object can be used. Verify that what we pass to the constructor is accessible. """ count = object() c = CounterRecord(count=count) self.assertTrue(c.get_count() is count) def test_str(self): """ CounterRecord.__str__ is actually used in PurgeCounter.write_data, so it's worth testing """ count = 1 date = object() c = CounterRecord(count=count, date=date) string = '%d:%s' % (count, date) self.assertEqual(str(c), string) def test_add(self): """ CounterRecord.__add__ is *horrible* design, but that's how it's been for a very long time. I want test coverage for the current behavior before making any changes. """ c = CounterRecord() orig_date = c.get_date() self.assertEqual(c.get_count(), 0) increment = 4 # ! c + increment self.assertEqual(c.get_count(), increment) # Original attempt: self.assertNotEqual(c.get_date(), orig_date) # time.asctime only provides seconds in that string representation of the # date, though, so just verify that the two strings are different objects # since they'll usually be equal self.assertFalse(c.get_date() is orig_date) def test_reset_count(self): c = CounterRecord() c + 1 orig_date = c.get_date() c.reset_count() self.assertEqual(c.get_count(), 0) self.assertTrue(c.get_date() is orig_date) def test_age_count_newer(self): """ Initialize a CounterRecord to one hour ago, then call age_count with 2 hours to verify that the count won't reset. ("Reset if the stored date is older than 2 hours ago") """ one_hour_ago = datetime.now() - timedelta(hours=1) date_str = time.asctime(one_hour_ago.timetuple()) count = object() c = CounterRecord(count=count, date=date_str) c.age_count(2 * 60 * 60) self.assertEqual(c.get_count(), count) def test_age_count_older(self): """ Initialize a CounterRecord to one hour ago, then reset the count by passing 0 to age_count (i.e. "reset if the stored date is older than now") """ one_hour_ago = datetime.now() - timedelta(hours=1) date_str = time.asctime(one_hour_ago.timetuple()) count = object() c = CounterRecord(count=count, date=date_str) c.age_count(0) self.assertEqual(c.get_count(), 0) class CounterTest(unittest.TestCase): def test_init(self): c = Counter() self.assertEqual(len(c), 0) def test_missing_key(self): c = Counter() key = 'key' value = c[key] self.assertEqual(value.get_count(), 0) self.assertTrue(key in c) def test_existing_key(self): key = 'key' value = object() c = Counter() c[key] = value self.assertTrue(c[key] is value) denyhosts-2.10/tests/test_cves.py000066400000000000000000000057561246776277600172340ustar00rootroot00000000000000from __future__ import print_function, unicode_literals import unittest from DenyHosts.regex import FAILED_ENTRY_REGEX_MAP, SSHD_FORMAT_REGEX # From https://bugs.gentoo.org/show_bug.cgi?id=157163 CVE_2006_6301_LINE = ' sshd[23964]: Invalid user foo from 123.123.123.123 from 127.0.0.1' CVE_2006_6301_USER = 'foo from 123.123.123.123' CVE_2006_6301_HOST = '127.0.0.1' # From https://bugzilla.redhat.com/show_bug.cgi?id=237449 CVE_2007_5715_LINE = ' sshd[29961]: User root from 122.36.2.10 not allowed because not listed in AllowUsers' CVE_2007_5715_USER = 'root' CVE_2007_5715_HOST = '122.36.2.10' # From Helmut Grohne's disclosure of this CVE CVE_2013_6890_LINES = [ ' sshd[123]: Invalid user Invalid user root from 123.123.123.123 from 21.21.21.21', ' sshd[123]: input_userauth_request: invalid user Invalid user root from 123.123.123.123 [preauth]', ' sshd[123]: Connection closed by 21.21.21.21 [preauth]', ] CVE_2013_6890_USER = 'Invalid user root from 123.123.123.123' CVE_2013_6890_HOST = '21.21.21.21' # TODO separate matching behavior into a common function # that's also used by the DenyHosts daemon class, instead of # doing it ourselves in every test case here class CVEsTestCase(unittest.TestCase): def test_cve_2006_6301(self): user = None host = None sshd_m = SSHD_FORMAT_REGEX.match(CVE_2006_6301_LINE) if sshd_m: message = sshd_m.group('message') for rx in FAILED_ENTRY_REGEX_MAP.values(): m = rx.search(message) if m: user = m.group('user') host = m.group('host') self.assertEqual(user, CVE_2006_6301_USER) self.assertEqual(host, CVE_2006_6301_HOST) def test_cve_2007_4323(self): pass def test_cve_2007_5715(self): user = None host = None sshd_m = SSHD_FORMAT_REGEX.match(CVE_2007_5715_LINE) if sshd_m: message = sshd_m.group('message') for rx in FAILED_ENTRY_REGEX_MAP.values(): m = rx.search(message) if m: user = m.group('user') host = m.group('host') self.assertEqual(user, CVE_2007_5715_USER) self.assertEqual(host, CVE_2007_5715_HOST) def test_cve_2013_6890(self): user = None host = None # There's no harm in iterating over all three lines even though # the first contains what we want. The second and third lines # don't match any of the 'failed entry' regexes. for line in CVE_2013_6890_LINES: sshd_m = SSHD_FORMAT_REGEX.match(line) if sshd_m: message = sshd_m.group('message') for rx in FAILED_ENTRY_REGEX_MAP.values(): m = rx.search(message) if m: user = m.group('user') host = m.group('host') self.assertEqual(user, CVE_2013_6890_USER) self.assertEqual(host, CVE_2013_6890_HOST) denyhosts-2.10/tests/test_deny_hosts.py000066400000000000000000000016341246776277600204420ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import dirname, join as ospj import unittest from DenyHosts.deny_hosts import DenyHosts from DenyHosts.lockfile import LockFile from DenyHosts.prefs import Prefs class DenyHostsBasicTest(unittest.TestCase): def setUp(self): self.directory = ospj(dirname(__file__), 'data/deny_hosts') self.work_dir = ospj(self.directory, 'work') self.logfile = ospj(self.work_dir, 'logfile') self.prefs = Prefs() self.lock_file = LockFile(ospj(self.directory, 'lockfile')) self.lock_file.remove(die_=False) self.lock_file.create() self.prefs._Prefs__data['ETC_DIR'] = ospj(self.directory, 'etc') self.prefs._Prefs__data['WORK_DIR'] = self.work_dir def test_init(self): DenyHosts(self.logfile, self.prefs, self.lock_file) def tearDown(self): self.lock_file.remove() denyhosts-2.10/tests/test_loginattempt.py000066400000000000000000000054251246776277600207740ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import join as ospj from tempfile import mkdtemp import time import unittest from DenyHosts.counter import CounterRecord from DenyHosts.prefs import Prefs from DenyHosts.loginattempt import LoginAttempt from DenyHosts.constants import ( ABUSIVE_HOSTS_INVALID, ABUSIVE_HOSTS_ROOT, ABUSIVE_HOSTS_RESTRICTED, ABUSIVE_HOSTS_VALID, ABUSED_USERS_INVALID, ABUSED_USERS_VALID, ABUSED_USERS_AND_HOSTS, SUSPICIOUS_LOGINS, ) # List of 2-tuples: # [0] filename in which data is stored # [1] LoginAttempt attribute name that reads from this file LOGIN_ATTEMPT_ATTRIBUTE_FILENAMES = [ (ABUSIVE_HOSTS_INVALID, 'get_abusive_hosts_invalid'), (ABUSIVE_HOSTS_ROOT, 'get_abusive_hosts_root'), (ABUSIVE_HOSTS_RESTRICTED, 'get_abusive_hosts_restricted'), (ABUSIVE_HOSTS_VALID, 'get_abusive_hosts_valid'), (ABUSED_USERS_INVALID, 'get_abused_users_invalid'), (ABUSED_USERS_VALID, 'get_abused_users_valid'), (ABUSED_USERS_AND_HOSTS, 'get_abused_users_and_hosts'), (SUSPICIOUS_LOGINS, 'get_suspicious_logins'), ] class LoginAttemptTestBase(unittest.TestCase): def setUp(self): self.allowed_hosts = ['host1', 'host2'] self.prefs = Prefs() self.work_dir = mkdtemp() self.prefs._Prefs__data['WORK_DIR'] = self.work_dir keys = [ 'DENY_THRESHOLD_INVALID', 'DENY_THRESHOLD_VALID', 'DENY_THRESHOLD_ROOT', 'DENY_THRESHOLD_RESTRICTED', ] for key in keys: self.prefs._Prefs__data[key] = 0 class BasicLoginAttemptTest(LoginAttemptTestBase): def test_no_data_file(self): login_attempt = LoginAttempt(self.prefs, set(self.allowed_hosts)) for filename, method_name in LOGIN_ATTEMPT_ATTRIBUTE_FILENAMES: self.assertFalse(getattr(login_attempt, method_name)()) class LoginAttemptDataFileTest(LoginAttemptTestBase): def test_data_files(self): login_attempt = LoginAttempt(self.prefs, set(self.allowed_hosts)) host = 'host' count = 1 asctime = time.asctime() test_counter_record = CounterRecord(count=count, date=asctime) for filename, method_name in LOGIN_ATTEMPT_ATTRIBUTE_FILENAMES: path = ospj(self.work_dir, filename) with open(path, 'w') as f: print('%s:%d:%s' % (host, count, asctime), file=f) data = getattr(login_attempt, method_name)() self.assertTrue(host in data) # TODO fix this after defining CounterRecord.__eq__ real_counter_record = data[host] self.assertEqual(test_counter_record.get_count(), real_counter_record.get_count()) self.assertEqual(test_counter_record.get_date(), real_counter_record.get_date()) denyhosts-2.10/tests/test_purgecounter.py000066400000000000000000000063321246776277600210050ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import dirname, join as ospj from tempfile import mkdtemp import unittest from DenyHosts.constants import PURGE_HISTORY from DenyHosts.counter import Counter from DenyHosts.prefs import Prefs from DenyHosts.purgecounter import PurgeCounter class EmptyPurgeCounterTest(unittest.TestCase): """ Tests creating a PurgeCounter object with no data file. This should: 1) not throw an exception, and 2) result in empty sets of hosts. We ensure that there's no data file by making an empty temporary directory for this test, and assinging that path to the appropriate Prefs key. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = mkdtemp() def test_no_data_file(self): purgecounter = PurgeCounter(self.prefs) self.assertFalse(purgecounter.get_banned_for_life()) self.assertFalse(purgecounter.get_data()) class PurgeCounterTestThreshold0(unittest.TestCase): """ Tests creating a Restricted object with a data file. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = ospj(dirname(__file__), 'data/purgecounter/static') def test_data_file(self): purgecounter = PurgeCounter(self.prefs) self.assertFalse(purgecounter.get_banned_for_life()) self.assertEqual(len(purgecounter.get_data()), 1) class PurgeCounterTestThreshold1(unittest.TestCase): """ Tests creating a Restricted object with a data file. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = ospj(dirname(__file__), 'data/purgecounter/static') self.prefs._Prefs__data['PURGE_THRESHOLD'] = 1 def test_data_file(self): purgecounter = PurgeCounter(self.prefs) self.assertEqual(len(purgecounter.get_banned_for_life()), 1) self.assertEqual(len(purgecounter.get_data()), 1) class PurgeCounterTestWriteData(unittest.TestCase): """ Tests creating a Restricted object with a data file. """ def setUp(self): self.prefs = Prefs() work_dir = ospj(dirname(__file__), 'data/purgecounter/dynamic') self.filename = ospj(work_dir, PURGE_HISTORY) self.prefs._Prefs__data['WORK_DIR'] = work_dir self.counter = Counter() host = 'host' count = 1 self.counter[host] += count self.test_string = '%s:%s\n' % (host, self.counter[host]) def test_data_file(self): purgecounter = PurgeCounter(self.prefs) purgecounter.write_data(self.counter) with open(self.filename) as f: self.assertEqual(f.read(), self.test_string) class PurgeCounterTestIncrement(unittest.TestCase): def setUp(self): self.prefs = Prefs() work_dir = mkdtemp() self.filename = ospj(work_dir, PURGE_HISTORY) self.prefs._Prefs__data['WORK_DIR'] = work_dir self.hosts = set(['host1', 'host2']) def test_increment(self): purge_counter = PurgeCounter(self.prefs) self.assertFalse(purge_counter.get_data()) purge_counter.increment(self.hosts) data = purge_counter.get_data() self.assertEqual(set(data), self.hosts) denyhosts-2.10/tests/test_report.py000066400000000000000000000012401246776277600175670ustar00rootroot00000000000000from __future__ import print_function, unicode_literals import unittest from DenyHosts.report import Report class ReportTest(unittest.TestCase): def test_empty(self): report = Report(hostname_lookup='false') self.assertTrue(report.empty()) self.assertEqual(report.get_report(), '') def test_add_section(self): report = Report(hostname_lookup='false') report.add_section('message', ['line1', ('line2', 1)]) expected_report = ('message:\n\nline1\nline2: 1\n\n\n------------------' '----------------------------------------------------\n') self.assertEqual(report.get_report(), expected_report) denyhosts-2.10/tests/test_restricted.py000066400000000000000000000024111246776277600204250ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import dirname, join as ospj from tempfile import mkdtemp import unittest from DenyHosts.prefs import Prefs from DenyHosts.restricted import Restricted class EmptyRestrictedTest(unittest.TestCase): """ Tests creating a Restricted object with no data file. This should: 1) not throw an exception, and 2) result in an empty set of restricted hosts. We ensure that there's no data file by making an empty temporary directory for this test, and assinging that path to the appropriate Prefs key. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['ETC_DIR'] = mkdtemp() def test_no_data_file(self): restricted = Restricted(self.prefs) self.assertFalse(restricted.get_restricted()) class RestrictedTest(unittest.TestCase): """ Tests creating a Restricted object with a data file. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['ETC_DIR'] = ospj(dirname(__file__), 'data/restricted') self.test_hosts = set(['host1', 'host2']) def test_no_data_file(self): restricted = Restricted(self.prefs) self.assertEqual(restricted.get_restricted(), self.test_hosts) denyhosts-2.10/tests/test_sync.py000066400000000000000000000156521246776277600172440ustar00rootroot00000000000000from __future__ import print_function, unicode_literals from os.path import dirname, join as ospj from random import randint import unittest from SimpleXMLRPCServer import SimpleXMLRPCServer from tempfile import mkdtemp from threading import Lock, Thread, local as thread_local import xmlrpclib from DenyHosts.constants import SYNC_HOSTS, SYNC_RECEIVED_HOSTS, SYNC_TIMESTAMP from DenyHosts.prefs import Prefs from DenyHosts.sync import Sync LOCAL_SYNC_SERVER_ADDRESS = ('127.0.0.1', 9911) LOCAL_SYNC_SERVER_URL = 'http://%s:%d' % LOCAL_SYNC_SERVER_ADDRESS class MockSyncServer(object): def __init__(self): self.hosts = [] def get_hosts(self): """ Test method to directly return stored hosts """ return self.hosts def get_new_hosts(self, timestamp, threshold, hosts_added, download_resiliency): """ Matches the API inferred from the Sync class """ return { 'hosts': self.hosts, 'timestamp': '0', } def add_hosts(self, hosts): self.hosts.extend(hosts) class SyncServerTest(unittest.TestCase): """ Base class of all Sync test classes that use a mock sync server. """ def sync_server(self): self.thread_local.alive = True server = None try: server = SimpleXMLRPCServer(LOCAL_SYNC_SERVER_ADDRESS, allow_none=True, logRequests=False) server.register_function(self._exit, 'exit') sync_server = MockSyncServer() server.register_instance(sync_server) finally: self.lock.release() if server is not None: while True: if not self.thread_local.alive: break server.handle_request() def _exit(self): self.thread_local.alive = False def setUp(self): # Poor-man's version of a threading.Barrier (which was added in 3.2). Make a # new threading.Lock and immediately acquire it. Spawn the sync_server thread, # then acquire the lock again. The second acquire() call will block until the # sync server thread is initialized -- it calls release() before entering the # serve_forever loop. This isn't totally free of race conditions -- a test # method could conceivably be run between sync_server's calls to # self.lock.release() and server.serve_forever. We just need our test runs # to behave as deterministically as possible, so hopefully this is good # enough for now. # TODO: use a real threading.Barrier when we drop support for Python 2 self.lock = Lock() self.lock.acquire() self.thread_local = thread_local() self.server_thread = Thread(target=self.sync_server) self.server_thread.start() self.lock.acquire() self.remote_sync_server = xmlrpclib.ServerProxy(LOCAL_SYNC_SERVER_URL, allow_none=True) self.lock.release() def tearDown(self): self.remote_sync_server.exit() self.server_thread.join() class MockSyncServerTest(SyncServerTest): """ Test the mock sync server itself. """ def test_add_hosts(self): hosts = ['host1', 'host2'] self.remote_sync_server.add_hosts(['host1', 'host2']) data = self.remote_sync_server.get_new_hosts(None, None, None, None) self.assertEqual(data['hosts'], hosts) self.assertEqual(self.remote_sync_server.get_hosts(), hosts) def test_add_no_hosts(self): self.remote_sync_server.add_hosts([]) data = self.remote_sync_server.get_new_hosts(None, None, None, None) self.assertFalse(data['hosts']) self.assertFalse(self.remote_sync_server.get_hosts()) class SyncTestStaticTimestamp(unittest.TestCase): """ Tests that we can read the sync timestamp from the filesystem. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = ospj(dirname(__file__), 'data/sync/static') self.sync = Sync(self.prefs) def test_get_sync_timestamp(self): timestamp = 427850432 self.assertEqual(self.sync.get_sync_timestamp(), timestamp) class SyncTestDynamicTimestamp(unittest.TestCase): """ Tests that we can set the timestamp on the filesystem. Separated into a different test class to avoid clobbering the static test data for SyncTestStaticTimestamp. """ def setUp(self): self.prefs = Prefs() self.prefs._Prefs__data['WORK_DIR'] = ospj(dirname(__file__), 'data/sync/dynamic') self.sync = Sync(self.prefs) self.value = randint(0, 1e9) def test_set_sync_timestamp(self): self.sync.set_sync_timestamp(str(self.value)) path = ospj(self.prefs._Prefs__data['WORK_DIR'], SYNC_TIMESTAMP) with open(path) as f: saved_timestamp = int(f.read().strip()) self.assertEqual(self.value, saved_timestamp) class SyncTestBasic(SyncServerTest): def setUp(self): super(SyncTestBasic, self).setUp() self.prefs = Prefs() self.prefs._Prefs__data['SYNC_SERVER'] = LOCAL_SYNC_SERVER_URL self.prefs._Prefs__data['WORK_DIR'] = ospj(dirname(__file__), 'data/sync/static') def test_connect_disconnect(self): sync = Sync(self.prefs) self.assertTrue(sync.xmlrpc_connect()) self.assertFalse(sync._Sync__server is None) self.assertTrue(sync._Sync__connected) sync.xmlrpc_disconnect() self.assertTrue(sync._Sync__server is None) self.assertFalse(sync._Sync__connected) class SyncTestSendHosts(SyncServerTest): def setUp(self): super(SyncTestSendHosts, self).setUp() self.prefs = Prefs() self.prefs._Prefs__data['SYNC_SERVER'] = LOCAL_SYNC_SERVER_URL work_dir = mkdtemp() self.prefs._Prefs__data['WORK_DIR'] = work_dir self.test_hosts = ['host1', 'host2', 'host3'] with open(ospj(work_dir, SYNC_HOSTS), 'w') as f: for host in self.test_hosts: print(host, file=f) def test_send_hosts(self): sync = Sync(self.prefs) self.assertFalse(sync._Sync__hosts_added) self.assertTrue(sync.send_new_hosts()) self.assertEqual(sync._Sync__hosts_added, self.test_hosts) class SyncTestReceiveHosts(SyncServerTest): def setUp(self): super(SyncTestReceiveHosts, self).setUp() self.prefs = Prefs() self.prefs._Prefs__data['SYNC_SERVER'] = LOCAL_SYNC_SERVER_URL self.work_dir = mkdtemp() self.prefs._Prefs__data['WORK_DIR'] = self.work_dir self.test_hosts = ['host1', 'host2', 'host3'] self.remote_sync_server.add_hosts(self.test_hosts) def test_send_hosts(self): sync = Sync(self.prefs) self.assertEqual(sync.receive_new_hosts(), self.test_hosts) filename = ospj(self.work_dir, SYNC_RECEIVED_HOSTS) with open(filename) as f: hosts = [line.strip().split(':')[0] for line in f] self.assertEqual(self.test_hosts, hosts) denyhosts-2.10/tests/test_util.py000066400000000000000000000017371246776277600172440ustar00rootroot00000000000000from __future__ import print_function, unicode_literals import unittest from DenyHosts.util import is_false, is_true, parse_host # List of 2-tuples: line to parse, expected host HOST_TEST_DATA = [ ('host', 'host'), ('ALL:127.0.0.1', '127.0.0.1'), (3, ''), ] class UtilsTest(unittest.TestCase): def setUp(self): self.true_strings = ['1', 't', 'true', 'y', 'yes'] self.false_strings = ['', 'false', 'ye', 'tr'] def test_is_true(self): for string in self.true_strings: self.assertTrue(is_true(string)) for string in self.false_strings: self.assertFalse(is_true(string)) def test_is_false(self): for string in self.true_strings: self.assertFalse(is_false(string)) for string in self.false_strings: self.assertTrue(is_false(string)) def test_parse_host(self): for line, expected in HOST_TEST_DATA: self.assertEqual(parse_host(line), expected)