Ok, I conversed a bit with the krbdev group of developers at MIT and they expressed that the patches would be more easily integrated into future releases if they didn't include other dependencies (including licensing dependencies), and hence rewrote the patch to use db2 which is already heavily used in KRB5. So, here are the patches, tweaked accordingly. I've been running the code here a couple of weeks, and noticed that a lot of the software scanning/probing agents don't grok graylisting and immediately give up when their second connection fails due to the unexpired graylist. Again, if anyone is interested in trying these out, please send me your comments and experiences. You'll need to tweak your .spec file as attached as well. -Philip
--- src/appl/gssftp/ftpd/ftpd.c.graylist 2006-06-07 20:13:07.000000000 -0600 +++ src/appl/gssftp/ftpd/ftpd.c 2006-06-07 20:13:08.000000000 -0600 @@ -101,6 +101,10 @@ #endif #include "pathnames.h" #include <libpty.h> +#ifdef GRAYLIST +#include <db.h> +#include <limits.h> +#endif #ifdef NEED_SETENV extern int setenv(char *, char *, int); @@ -222,6 +226,32 @@ char rhost_addra[16]; char *rhost_sane; +#ifdef GRAYLIST +const char *gl_path= "/etc/ftpgraylist.db"; +int gl_initial = 0; +int gl_scale = 0; +int gl_max = 0; + +typedef struct { + time_t start; + unsigned shutout; +} graylist_t; + +#define RET_NOTFOUND RET_SPECIAL + +#define GL_INIT_MIN 5 +#define GL_INIT_MAX 7200 + +#define GL_SCALE_MIN 2 +#define GL_SCALE_MAX 100 +#define GL_SCALE_DFLT 2 + +#define GL_CAP_MIN 300 +#define GL_CAP_MAX (365 * 24 * 3600) + +static unsigned gl_connect(struct in_addr, int); +#endif + /* Defines for authlevel */ #define AUTHLEVEL_NONE 0 #define AUTHLEVEL_AUTHENTICATE 1 @@ -277,6 +307,18 @@ } #endif +#ifdef KRB5_KRB4_COMPAT +#define KRB5_OPTIONS "s:" +#else +#define KRB5_OPTIONS "" +#endif + +#ifdef GRAYLIST +#define GL_OPTIONS "G:g:S:" +#else +#define GL_OPTIONS +#endif + int stripdomain = 1; int maxhostlen = 0; int always_ip = 0; @@ -290,11 +332,11 @@ int addrlen, c, on = 1, tos, port = -1; extern char *optarg; extern int optopt; -#ifdef KRB5_KRB4_COMPAT - char *option_string = "AaCcdlp:r:s:T:t:U:u:vw:"; -#else /* !KRB5_KRB4_COMPAT */ - char *option_string = "AaCcdlp:r:T:t:U:u:vw:"; -#endif /* KRB5_KRB4_COMPAT */ + char *option_string = "AaCcdlp:r:T:t:U:u:vw:" KRB5_OPTIONS + GL_OPTIONS; +#ifdef GRAYLIST + unsigned expire; +#endif ftpusers = _PATH_FTPUSERS_DEFAULT; #ifdef KRB5_KRB4_COMPAT @@ -420,6 +462,29 @@ } break; } +#ifdef GRAYLIST + case 'G': + gl_max = atoi(optarg); + if (gl_max < GL_CAP_MIN || gl_max > GL_CAP_MAX) { + fprintf(stderr, "ftpd: bad arg to -G\n"); + exit(1); + } + break; + case 'g': + gl_initial = atoi(optarg); + if (gl_initial < GL_INIT_MIN || gl_initial > GL_INIT_MAX) { + fprintf(stderr, "ftpd: bad arg to -g\n"); + exit(1); + } + break; + case 'S': + gl_scale = atoi(optarg); + if (gl_scale < GL_SCALE_MIN || gl_scale > GL_SCALE_MAX) { + fprintf(stderr, "ftpd: bad arg to -S\n"); + exit(1); + } + break; +#endif default: fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n", (char)optopt); @@ -427,6 +492,21 @@ } } +#ifdef GRAYLIST + if (!gl_scale && gl_initial) + gl_scale = GL_SCALE_DFLT; + if (gl_max && !gl_initial) { + fprintf(stderr, "ftpd: -G must be used with -g\n"); + exit(1); + } else if (gl_initial && !gl_max) { + gl_max = gl_initial * (gl_scale * gl_scale * gl_scale * + gl_scale * gl_scale); + } else if (gl_initial > gl_max) { + fprintf(stderr, "ftpd: -g arg must be less than -G arg\n"); + exit(1); + } +#endif + if (port != -1) { struct sockaddr_in sin4; int s, ns, sz; @@ -538,6 +618,15 @@ stru = STRU_F; mode = MODE_S; tmpline[0] = '\0'; + +#ifdef GRAYLIST + expire = gl_connect(his_addr.sin_addr, 1); + if (expire != 0) { + reply(530, "Client graylisted for %u seconds.", expire); + dologout(0); + } +#endif + (void) gethostname(hostname, sizeof (hostname)); reply(220, "%s FTP server (%s) ready.", hostname, version); (void) setjmp(errcatch); @@ -909,6 +998,212 @@ guest = 0; } +#ifdef GRAYLIST +static void +gl_badlogin(addr) +struct in_addr addr; +{ + DB *db; + DBT key, content; + graylist_t rec; + time_t now; + int status; + + /* feature is disabled */ + if (!gl_initial) + return; + + syslog(LOG_DEBUG, "graylist: badlogin %s", inet_ntoa(addr)); + + db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL); + if (!db) { + syslog(LOG_ERR, "graylist: can't open database (errno %d)", + errno); + return; + } + + key.data = &addr; + key.size = sizeof(addr); + status = db->get(db, &key, &content, 0); + + time(&now); + + switch (status) { + case RET_SUCCESS: + memcpy(&rec, content.data, content.size); + /* if the graylist hasn't expired, then increment it. */ + if (now < rec.start + gl_max) { + rec.shutout *= gl_scale; + if (rec.shutout > gl_max) + rec.shutout = gl_max; + break; + } + /* FALLTHRU */ + case RET_NOTFOUND: + rec.shutout = gl_initial; + break; + case RET_ERROR: + /* for now, failure isn't bad because we're exiting + * anyway... but we might want to fail more dramatically + * so that the system administrator takes notice. + */ + syslog(LOG_ERR, "graylist: get (%s) failed (errno %d)", + inet_ntoa(addr), errno); + (void)db->close(db); + return; + } + + /* reset the timer... */ + rec.start = now; + + syslog(LOG_INFO, "graylisting %s for %u seconds", inet_ntoa(addr), + rec.shutout); + + content.data = &rec; + content.size = sizeof(rec); + status = db->put(db, &key, &content, 0); + + switch (status) { + case RET_SUCCESS: + break; + case RET_ERROR: + syslog(LOG_ERR, "graylist: put (%s) failed (errno %d)", + inet_ntoa(addr), errno); + break; + } + + (void)db->close(db); +} + +static unsigned +gl_connect(addr, punish) +struct in_addr addr; +int punish; +{ + DB *db; + DBT key, content; + graylist_t rec; + time_t now, expire; + unsigned interval; + int status; + + /* feature is disabled */ + if (!gl_initial) + return 0; + + syslog(LOG_DEBUG, "graylist: connect %s, %d", inet_ntoa(addr), punish); + + db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL); + if (!db) { + syslog(LOG_ERR, "graylist: can't open database (errno %d)", + errno); + return UINT_MAX; + } + + key.data = &addr; + key.size = sizeof(addr); + status = db->get(db, &key, &content, 0); + + time(&now); + + switch (status) { + case RET_SUCCESS: + /* handled outside switch */ + break; + case RET_NOTFOUND: +ok: db->close(db); + return 0; + case RET_ERROR: + syslog(LOG_ERR, "graylist: get (%s) failed (errno %d)", + inet_ntoa(addr), errno); + (void)db->close(db); + return UINT_MAX; + } + + memcpy(&rec, content.data, content.size); + + expire = rec.start + rec.shutout; + + /* we're past the expiry... but we don't delete the record; it + * can only get expunged by a successful login + */ + if (expire <= now) + goto ok; + + /* do we spank them for connecting before the graylist expires? */ + if (punish) { + rec.start = now; + rec.shutout *= gl_scale; + if (rec.shutout > gl_max) + rec.shutout = gl_max; + + content.data = &rec; + content.size = sizeof(rec); + status = db->put(db, &key, &content, 0); + + switch (status) { + case RET_SUCCESS: + break; + case RET_ERROR: + syslog(LOG_ERR, "graylist: put (%s) failed (errno %d)", + inet_ntoa(addr), errno); + db->close(db); + return UINT_MAX; + } + /* update expiry to reflect spanking */ + expire = rec.start + rec.shutout; + } + + syslog(LOG_INFO, "graylisted host %s %shas %u seconds remaining", + inet_ntoa(addr), (punish ? "now " : ""), expire - now); + + (void)db->close(db); + + /* how long before they can retry */ + return (expire - now); +} + +static void +gl_unblock(addr) +struct in_addr addr; +{ + DB *db; + DBT key; + time_t now, expire; + int status; + + /* feature is disabled */ + if (!gl_initial) + return; + + syslog(LOG_DEBUG, "graylist: authenticated %s", inet_ntoa(addr)); + + db = dbopen(gl_path, O_CREAT | O_RDWR, 0600, DB_HASH, NULL); + if (!db) { + syslog(LOG_ERR, "graylist: can't open database (errno %d)", + errno); + return; + } + + key.data = &addr; + key.size = sizeof(addr); + status = db->del(db, &key, 0); + + switch (status) { + case RET_SUCCESS: + case RET_NOTFOUND: + break; + case RET_ERROR: + syslog(LOG_ERR, "graylist: del (%s) failed (errno %d)", + inet_ntoa(addr), errno); + (void)db->close(db); + return; + } + + (void)db->close(db); +} +#endif + static int kpass(name, passwd) char *name, *passwd; @@ -1076,6 +1371,9 @@ syslog(LOG_NOTICE, "repeated login failures from %s (%s)", rhost_addra, remotehost); +#ifdef GRAYLIST + gl_badlogin(his_addr.sin_addr); +#endif dologout(0); } reply(530, "Login incorrect."); @@ -1084,6 +1382,10 @@ } login_attempts = 0; /* this time successful */ +#ifdef GRAYLIST + gl_unblock(his_addr.sin_addr); +#endif + login(passwd, 0); return; } --- src/appl/gssftp/ftpd/ftpd.M.graylist 2006-06-01 15:58:00.000000000 -0600 +++ src/appl/gssftp/ftpd/ftpd.M 2006-06-01 16:25:33.000000000 -0600 @@ -38,6 +38,7 @@ .B ftpd [\fB\-A \fP|\fB -a\fP] [\fB\-C\fP] [\fB\-c\fP] [\fB\-d\fP] [\fB\-l\fP] [\fB\-v\fP] [\fB\-T\fP \fImaxtimeout\fP] [\fB\-t\fP \fItimeout\fP] +[\fB\-g\fP \fIinitialgraylist \fP [\fB-G\fP \fImaxgraylist\fP] [\fB-S\fP \fIscalefactor\fP] [\fB\-p\fP \fIport\fP] [\fB\-U\fP \fIftpusers-file\fP] [\fB\-u\fP \fIumask\fP] [\fB\-r\fP \fIrealm-file\fP] [\fB\-s\fP \fIsrvtab\fP] [\fB\-w\fP{\fBip\fP|\fImaxhostlen\fP[\fB,\fP{\fBstriplocal\fP|\fBnostriplocal\fP}]}] @@ -116,6 +117,25 @@ file to use. The default value is normally .IR /etc/ftpusers . .TP +\fB\-g\fP \fIinitialgraylist\fP +Sets the initial time to graylist a user following 3 successive failed +logins on the same connection. Should be in the range of 5..300. +.TP +\fB-G\fP \fImaxgraylist\fP +Sets the maximum time to graylist a user should he attempt to reconnect +before the graylist expires, or fail to login correctly 3 times on the same +connection before the maximum time has expired (but after the current +graylist interval expired). Should be in the range of 300..31536000. +If unspecified, it defaults to the \fIinitialgraylist\fP multipled by the +scalefactor to the fifth power. +.TP +\fB-S\fP \fIscalefactor\fP +Sets the scaling factor to increase the graylist period by should the +user attempt to connect before the graylist expires. That is, if the +user connects before the current graylist expires, then the +current graylist will be increased by \fIscalefactor\fP times. Should +be in the range of 2..100. If unspecified, defaults to 2. +.TP \fB\-u\fP \fIumask\fP Sets the umask for the ftpd process. The default value is normally 027. .TP
--- krb5.spec 2005-06-29 20:28:32.000000000 -0600 +++ krb5-tweaked.spec 2006-06-11 12:25:31.000000000 -0600 @@ -7,7 +7,7 @@ Summary: The Kerberos network authentication system. Name: krb5 Version: 1.3.6 -Release: 7 +Release: 8 # Maybe we should explode from the now-available-to-everybody tarball instead? # http://web.mit.edu/kerberos/www/dist/krb5/1.3/krb5-1.3.5.tar Source0: krb5-%{version}.tar.gz @@ -66,6 +66,8 @@ Patch38: krb5-1.4-ncurses.patch Patch39: krb5-1.4.1-api.patch Patch40: krb5-1.4.1-telnet-environ.patch +Patch99: gssftpd-graylist-db4.patch + License: MIT, freely distributable. URL: http://web.mit.edu/kerberos/www/ Group: System Environment/Libraries @@ -829,6 +831,8 @@ %patch38 -p1 -b .ncurses %patch39 -p1 -b .api %patch40 -p1 -b .telnet-environ +%patch99 -p0 -b .graylist + cp src/krb524/README README.krb524 find . -type f -name "*.info-dir" -exec rm -fv "{}" ";" gzip doc/*.ps @@ -847,11 +851,13 @@ %ifarch %{ix86} s390 ppc sparc DEFINES="-D_FILE_OFFSET_BITS=64" ; export DEFINES %endif +DEFINES="$DEFINES -DGRAYLIST" CFLAGS="`echo $RPM_OPT_FLAGS $ARCH_OPT_FLAGS $DEFINES $INCLUDES -fPIC`" %configure \ CC=%{__cc} \ CFLAGS="$CFLAGS" \ LDFLAGS="-pie" \ + FTPD_LIBS='$(KDB5_LIBS)' \ CPPFLAGS="$DEFINES $INCLUDES" \ --enable-shared --enable-static \ --bindir=%{krb5prefix}/bin \
-- fedora-devel-list mailing list fedora-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/fedora-devel-list