@(include "config") @(include "utils") @(do (defstruct client-info nil (ip nil) (access-hist nil) (points 0) (banned nil) (extrainfo nil)) (defvarl client-hash (hash :equal-based)) (defvar *off* "") ;; set this to "#" to comment out commands (defun get-client-info (ip) (hash-update-1 client-hash ip [iff have identity (ret (new client-info ip ip))] nil)) ;;; Report activity of an ip address, for a given time ;;; Levels are 0 through 5. 0 is normal access: client isn't ;;; doing anything wrong, but should be monitored for excessive activity. ;;; Any other level is points which are accumulated. ;;; 5 points lead to a ban, whose severity depends on how many ;;; points in excess of 5 there are. (defun report (ip time level : extrainfo) (let ((cli (get-client-info ip))) (push time cli.access-hist) (when extrainfo (pushnew extrainfo cli.extrainfo)) (if (> level 0) (let* ((points (inc cli.points level)) (severity (- points 5))) (if (>= severity 0) (progn (set cli.points 0) (ban cli time (ban-duration severity)))))) (process-histories time) (do-expiry time))) (defun get-info (ip) (iflet ((cli [client-hash ip])) cli.extrainfo)) (defun clear (ip) (del [client-hash ip])) (defun ban-duration (severity) [*ban-duration* (min (- (length *ban-duration*) 1) severity)]) (defun ban (cli time howlong) (let* ((until (if cli.banned (to cli.banned))) (new-until (+ time howlong))) (cond ((not cli.banned) (unless *dry-run* (sh `@{*off*}iptables -I INPUT 1 -s @{cli.ip} -i @{*iface*} -j DROP`)) (debug "banned ~a for ~a starting on ~a\n" cli.ip (hrtime howlong) (time-string-local time "%c")) (set cli.banned (rcons time new-until))) ((> new-until until) (debug "extending ban on ~a for ~a starting on ~a\n" cli.ip (hrtime howlong) (time-string-local time "%c")) (set cli.banned (rcons time new-until)))))) (defun process-histories (time) (let ((long-range (- time *long-period*)) (short-range (- time *short-period*))) (dohash (ip cli client-hash) (let* ((nacc (remove-if (op < @1 long-range) cli.access-hist)) (long-count (length nacc)) (short-count (count-if (op >= @1 short-range) nacc))) (if nacc (set cli.access-hist nacc) (progn (set cli.access-hist nil) (set cli.extrainfo nil))) (cond ((> long-count *long-limit*) (ban cli time *long-ban*)) ((> short-count *short-limit*) (ban cli time *short-ban*))))))) (defun do-expiry (now-time) (dohash (ip cli client-hash) (when (and cli.banned (<= (to cli.banned) now-time)) (unban cli)) (when (null cli.access-hist) (del [client-hash ip])))) (defun unban (cli) (unless *dry-run* (sh `@{*off*}iptables -D INPUT -s @{cli.ip} -i @{*iface*} -j DROP`)) (debug "unbanned ~a\n" cli.ip) (set cli.banned nil)) (cond (*dry-run* (set *stdlog* *stdout*)) (t (daemon nil nil) (openlog `txrban-@self` log-pid log-authpriv))))