This patch causes sftpd to match incoming usernames and IP addresses against a file listing allowed combinations. It was contributed by < Disastry at iname dot com >. To apply the patch: % cd sftpd-1.45 % patch < user-ip-patch.txt diff -u sftpd-1.45/sftpd.cpp sftpd-1.45-ip-patch/sftpd.cpp --- sftpd-1.45/sftpd.cpp Mon Sep 18 12:01:24 2000 +++ sftpd-1.45-ip-patch/sftpd.cpp Thu Sep 28 19:45:15 2000 @@ -9,6 +9,9 @@ #include // isdigit, isprint #include // signal #include // errno +#include // time, localtime, strftime +#include // struct tm +#include // getpid #include "socket.h" // socket funcs #include "sockutil.h" // utilities @@ -98,6 +101,73 @@ } +//----------------------------- + +bool SFTPD::Is959Allowed(char const *name) +{ + bool found = false; + char testname[100]; + char *ep; + + //allow959File = fopen("c:\\Net\\SafeTPd\\allow_959.txt", "r"); + if (allow959File) { + fseek(allow959File, 0, SEEK_SET); + while (!feof(allow959File) && !found) { + fgets(testname, sizeof(testname), allow959File); + if (*testname == '#') + continue; + ep = testname + strlen(testname) - 1; + if (*ep == '\n') + *ep = 0; + found = !strcmp(name, testname); + } + //fclose(allow959File); + } + return found; +} // IsDropAllowed + +bool SFTPD::IsIPAllowed(char const *name) +{ + bool found = false; + char testname[200]; + char *ep; + unsigned b1,b2,b3,b4,msk; + IPAddress testip, testmask; // dword + +// allowIPFile = fopen("c:\\Net\\SafeTPd\\allow_ip.txt", "r"); + if (allowIPFile) { + fseek(allowIPFile, 0, SEEK_SET); + while (!feof(allowIPFile) && !found) { + fgets(testname, sizeof(testname), allowIPFile); + if (*testname == '#') + continue; + if (!(ep = strchr(testname, ' '))) + continue; + *ep = 0; ep++; + if ((testname[0]=='*' && testname[1]==0) || + !strcmp(name, testname)) { + if (sscanf(ep, "%u.%u.%u.%u/%u", &b1,&b2,&b3,&b4,&msk) != 5) + continue; + if (b1>255 || b2>255 || b3>255 || b4>255 || msk>32) + continue; + testip = (IPAddress)b1<<24 | (IPAddress)b2<<16 | (IPAddress)b3<<8 | (IPAddress)b4; + testmask = msk ? 0xFFFFFFFF << (32-msk) : 0; + if ((relayClientAddress & testmask) == testip) { + found = true; + *(--ep) = ' '; + ep = testname + strlen(testname) - 1; + if (*ep == '\n') + *ep = 0; + log("allowed \"" << name << "\" from " << formatAddress(relayClientAddress) << ", rule: " << testname); + } + } + } // while + //fclose(allowIPFile); + } + if (!found) + log("denied " << name << " from " << formatAddress(relayClientAddress)); + return found; +} // IsDropAllowed // --------------------- general stuff ----------------------------- // this is used by the warning handler, and obviously its presence @@ -223,6 +293,9 @@ security(NULL) { instance = this; + + allowIPFile = NULL; + allow959File = NULL; } @@ -447,6 +520,38 @@ f_ing_strtoul(argv[a]+2, 0 /*auto-radix*/); break; + case '6': { // set ip allow file + FILE *fp = fopen(argString, "r"); + if (!fp) { + log("failed to open ip allow file " << (argString)); + return; + } + else { + if (allowIPFile) { + fclose(allowIPFile); + } + allowIPFile = fp; + } + break; + } + + case '7': { // set dropdown allow file + allowRfc959 = false; + allowRfc959_anon = false; + FILE *fp = fopen(argString, "r"); + if (!fp) { + log("failed to open dropdown allow file " << (argString)); + return; + } + else { + if (allow959File) { + fclose(allow959File); + } + allow959File = fp; + } + break; + } + case 'x': allowCleartext = true; break; @@ -583,6 +688,8 @@ " compatibility:\n" " -9 disallow RFC 959 (unencrypted) connections\n" " -8 disallow RFC 959 unless the user is anonymous\n" + " -7 allow RFC 959 connections for users listed in file\n" + " -6 allow connections only from certain users/IPs\n" " -3 disable 3rd-party transfer optimization\n" " -i
use alternate server address for authenication\n" " -K Kerberos compatibility (specify kftpd image)\n" @@ -1197,6 +1304,9 @@ // when 'msg' already has a CR; it should *not* be used to try to string // several parameters to 'writeToLog' together on a single output line) char const *fmt = (level & LL_NO_LF)? "%s" : "%s\n"; + char timestr[20]; + time_t ltime; + struct tm *tmtime; // only UNIX has syslog, and separately encapsulating this nonportability // doesn't seem to have much value @@ -1608,7 +1718,9 @@ // STATE_959_INTEROP, and therefore the only place that needs to // check allowRfc959 if (allowRfc959 || - (allowRfc959_anon && isAnonLogin(loginName))) { + (allowRfc959_anon && isAnonLogin(loginName)) || + Is959Allowed(loginName) + ) { // 959 dropdown state = STATE_959_INTEROP; log(LE_HANDOFF, "dropping down to 959 compatibility mode"); @@ -2056,6 +2168,11 @@ "your *first* command."); return true; } + } + if (!IsIPAllowed(name)) { + clientReply(RC_REQUEST_DENIED, + "wrong User name or IP address."); + return true; } // didn't do anything diff -u sftpd-1.45/sftpd.h sftpd-1.45-ip-patch/sftpd.h --- sftpd-1.45/sftpd.h Fri Sep 15 15:55:03 2000 +++ sftpd-1.45-ip-patch/sftpd.h Thu Sep 28 19:41:48 2000 @@ -167,7 +167,14 @@ // for access by warnHandler, which is a static fn static SFTPD *instance; + FILE *allow959File; + FILE *allowIPFile; + private: // funcs + + bool Is959Allowed(char const *name); + bool IsIPAllowed(char const *name); + // see code for comments about purpose, usage void handleOriginalRequest(Request const &req); void handleDecryptedRequest(Request const &req);