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