Re: Graylisting clients w/ gss (krb5)'s ftpd

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Fedora Announce]     [Fedora Kernel]     [Fedora Testing]     [Fedora Formulas]     [Fedora PHP Devel]     [Kernel Development]     [Fedora Legacy]     [Fedora Maintainers]     [Fedora Desktop]     [PAM]     [Red Hat Development]     [Gimp]     [Yosemite News]
  Powered by Linux