After 28000 attempts to bruteforce ftp connections last night I have made ftpd a little less friendly. 1/ Drop the connection after 5 auth failures. 2/ Slow up the startup if we previously had an auth failure on this IP address. the delay is exponential (to make them suffer) and is reset as soon as we get a valid authenticated (I.E. not none or anonymous) connection from this IP. You will need to create /ftp/tmp with 0777 mode (and chmod +t it) to enable latter part. -Steve Notes: Wed May 2 16:17:52 EDT 2007 geoff for now, i've added exponentially-increasing delays for repeated attempts within a single ftpd session. aux/listen might be a better place for connection-rate limiting. Reference: /n/sources/patch/saved/ftp-greylist Date: Wed May 2 16:12:47 CES 2007 Signed-off-by: steve@quintile.net Reviewed-by: geoff --- /sys/src/cmd/ip/ftpd.c Wed May 2 16:07:11 2007 +++ /sys/src/cmd/ip/ftpd.c Wed May 2 16:07:06 2007 @@ -33,6 +33,9 @@ /* maximum ms we'll wait for a command */ Maxwait= 1000*60*30, /* inactive for 30 minutes, we hang up */ + /* maximum pad passwords before we giveup */ + Maxbadpass= 5, + Maxerr= 128, Maxpath= 512, }; @@ -73,6 +76,9 @@ int sodoff(void); int accessok(char*); +void slowbrutes(void); +void knockknock(void); + typedef struct Cmd Cmd; struct Cmd { @@ -225,6 +231,7 @@ Binit(&in, 0, OREAD); reply("220 Plan 9 FTP server ready"); alarm(Maxwait); + slowbrutes(); while(cmd = Brdline(&in, '\n')){ alarm(0); @@ -554,6 +561,7 @@ int passcmd(char *response) { + int rc; char namefile[128]; AuthInfo *ai; @@ -573,7 +581,8 @@ return reply("530 Not logged in"); createperm = 0664; /* login has already setup the namespace */ - return loginuser(user, nil, 0); + rc = loginuser(user, nil, 0); + goto done; } else { /* for everyone else, do challenge response */ if(ch == nil) @@ -581,10 +590,15 @@ ch->resp = response; ch->nresp = strlen(response); ai = auth_response(ch); - if(ai == nil) - return reply("530 Not logged in: %r"); - if(auth_chuid(ai, nil) < 0) - return reply("530 Not logged in: %r"); + if(ai == nil){ + rc = reply("530 Not logged in: %r"); + goto done; + + } + if(auth_chuid(ai, nil) < 0){ + rc = reply("530 Not logged in: %r"); + goto done; + } auth_freechal(ch); ch = nil; @@ -593,10 +607,14 @@ strcpy(mailaddr, user); createperm = 0660; if(access(namefile, 0) == 0) - return loginuser(user, namefile, 0); + rc = loginuser(user, namefile, 0); else - return loginuser(user, "/lib/namespace", 0); + rc = loginuser(user, "/lib/namespace", 0); + goto done; } +done: + knockknock(); + return rc; } /* @@ -1830,3 +1848,105 @@ return r; } + +static int mkdirs(char *); + +/* + * if any directories leading up to path don't exist, create them. + * modifies but restores path. + */ +static int +mkpdirs(char *path) +{ + int rv = 0; + char *sl = strrchr(path, '/'); + + if (sl != nil) { + *sl = '\0'; + rv = mkdirs(path); + *sl = '/'; + } + return rv; +} + +/* + * if path or any directories leading up to it don't exist, create them. + * modifies but restores path. + */ +static int +mkdirs(char *path) +{ + int fd; + + if (access(path, AEXIST) >= 0) + return 0; + + /* make presumed-missing intermediate directories */ + if (mkpdirs(path) < 0) + return -1; + + /* make final directory */ + fd = create(path, OREAD, 0777|DMDIR); + if (fd < 0) + /* + * we may have lost a race; if the directory now exists, + * it's okay. + */ + return access(path, AEXIST) < 0? -1: 0; + close(fd); + return 0; +} + +/* + * Slow up our startup time exponentially when an IP address fails to + * login, to discourage people from brute force-ing our accounts. + */ +void +slowbrutes(void) +{ + Dir *d; + char fn[128]; + + snprint(fn, sizeof(fn), "/ftp/tmp/%s", nci->rsys); + if((d = dirstat(fn)) == nil) + return; + sleep(d->length*d->length*100); + free(d); +} + +/* + * Only allow Maxbadpass attempts to login in this session + * + * A valid login (other than none and anonymous) reactivates + * an IP address slowed due to apparant brute forcing. + */ +void +knockknock(void) +{ + int fd; + char fn[128]; + static int sessfail = 0; + static int watched = 0; + + snprint(fn, sizeof(fn), "/ftp/tmp/%s", nci->rsys); + if(loggedin){ + remove(fn); + return; + } + + if(++sessfail >= Maxbadpass) + exits("too many pad passwords"); + + if(watched == 0){ + if((fd = open(fn, OWRITE)) == -1){ + if(mkpdirs(fn) != 0) + return; + if((fd = create(fn, OWRITE, DMAPPEND|0666)) == -1) + return; + } + write(fd, "x", 1); + close(fd); + watched++; + } +} +