Code review request
Philip Prindeville
philipp at redfish-solutions.com
Mon Aug 7 23:55:42 EDT 2006
Tom Yu wrote:
> jhutz> It is, but there was no attached patch.
>
>The mailing list server probably ate it. You may want to re-send
>without putting the patch in a separate MIME part.
>
>---Tom
>
>
And inline... of course, the tabs are converted to blanks...
--- 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
More information about the krbdev
mailing list