From 0db888da4821c49f8a4013d8fcf980afc733c348 Mon Sep 17 00:00:00 2001
From: rswindell <>
Date: Wed, 31 Aug 2011 19:34:48 +0000
Subject: [PATCH] Create and use an API for multiple login attempt tracking
 (not just the most recent), and automatically filter IPs of obvious hackers
 (100 consecutive unique failed login attempts).

---
 src/sbbs3/ftpsrvr.c  | 110 +++++++++++++++++++--------------------
 src/sbbs3/mailsrvr.c | 120 +++++++++++++++++++++++--------------------
 src/sbbs3/services.c | 102 ++++++++++++++++++++++++++----------
 src/sbbs3/userdat.c  |  75 +++++++++++++++++++++++++++
 src/sbbs3/userdat.h  |  24 ++++++++-
 5 files changed, 289 insertions(+), 142 deletions(-)

diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index 8d794ada86..da0f580e9c 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -89,6 +89,7 @@ static char		revision[16];
 static char 	*text[TOTAL_TEXT];
 static str_list_t recycle_semfiles;
 static str_list_t shutdown_semfiles;
+static link_list_t	login_attempt_list;
 
 #ifdef SOCKET_DEBUG
 	static BYTE 	socket_debug[0x10000]={0};
@@ -2314,6 +2315,16 @@ void ftp_printfile(SOCKET sock, const char* name, unsigned code)
 	}
 }
 
+static BOOL ftp_hacklog(char* prot, char* user, char* text, char* host, SOCKADDR_IN* addr)
+{
+#ifdef _WIN32
+	if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
+		PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
+#endif
+
+	return hacklog(&scfg, prot, user, text, host, addr);
+}
+
 /****************************************************************************/
 /* Consecutive failed login (possible password hack) attempt tracking		*/
 /****************************************************************************/
@@ -2324,42 +2335,22 @@ void ftp_printfile(SOCKET sock, const char* name, unsigned code)
 /* A successful login from the same host resets the counter.				*/
 /****************************************************************************/
 
-static struct {
-	IN_ADDR addr;		/* host with consecutive failed login attmepts */
-	ulong	attempts;	/* number of consectuive failed login attempts */
-} bad_login;
-
-static void new_connection(SOCKET sock, SOCKADDR_IN* addr)
-{
-	if(bad_login.attempts && memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-		lprintf(LOG_WARNING,"%04d Delaying acceptance of suspicious connection from: %s", sock, inet_ntoa(addr->sin_addr));
-		mswait(bad_login.attempts*1000);
-	}
-}
-
-static void goodlogin(SOCKADDR_IN* addr)
-{
-	if(memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-		memset(&bad_login.addr,0,sizeof(bad_login.addr));
-		bad_login.attempts=0;
-	}
-}
-
 static BOOL badlogin(SOCKET sock, ulong* login_attempts, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
 {
+	ulong count;
+
 	if(addr!=NULL) {
-		if(memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-			if(++bad_login.attempts >= 10) {
-				hacklog(&scfg, "FTP LOGIN", user, passwd, host, addr);
-				*login_attempts=bad_login.attempts;
-			}
-		} else {
-			bad_login.attempts=1;
-			bad_login.addr = addr->sin_addr;
-		}
+		count=loginFailure(&login_attempt_list, addr, "FTP", user, passwd);
+		if(count>=LOGIN_ATTEMPT_HACKLOG)
+			ftp_hacklog("FTP LOGIN", user, passwd, host, addr);
+		if(count>=LOGIN_ATTEMPT_FILTER)
+			filter_ip(&scfg, "FTP", "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
+				,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
+		if(count > *login_attempts)
+			*login_attempts=count;
 	}
 
-	mswait(5000);	/* As recommended by RFC2577 */
+	mswait(LOGIN_ATTEMPT_DELAY);	/* As recommended by RFC2577 */
 
 	if(++(*login_attempts)>=3) {
 		sockprintf(sock,"421 Too many failed login attempts.");
@@ -2469,6 +2460,7 @@ static void ctrl_thread(void* arg)
 	JSString*	js_str;
 	js_branch_t	js_branch;
 #endif
+	list_node_t*	login_attempt;
 
 	SetThreadName("FTP CTRL");
 	thread_up(TRUE /* setuid */);
@@ -2483,7 +2475,7 @@ static void ctrl_thread(void* arg)
 	
 	lprintf(LOG_DEBUG,"%04d CTRL thread started", sock);
 
-	free(arg);	/* unexplicable assertion here on July 26, 2001 */
+	free(arg);
 
 #ifdef _WIN32
 	if(startup->answer_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
@@ -2564,6 +2556,12 @@ static void ctrl_thread(void* arg)
 	client.user="<unknown>";
 	client_on(sock,&client,FALSE /* update */);
 
+	if((login_attempt=loginAttempted(&login_attempt_list, &ftp.client_addr)) != NULL
+		&& ((login_attempt_t*)login_attempt->data)->count > 1) {
+		lprintf(LOG_NOTICE,"%04d Delaying suspicious connection from: %s", sock, inet_ntoa(ftp.client_addr.sin_addr));
+		mswait(((login_attempt_t*)login_attempt->data)->count*1000);
+	}
+
 	sockprintf(sock,"220-%s (%s)",scfg.sys_name, startup->host_name);
 	sockprintf(sock," Synchronet FTP Server %s-%s Ready"
 		,revision,PLATFORM_DESC);
@@ -2760,14 +2758,14 @@ static void ctrl_thread(void* arg)
 			}
 
 			/* Update client display */
-			if(user.pass[0])
+			if(user.pass[0]) {
 				client.user=user.alias;
-			else {	/* anonymous */
+				loginSuccess(&login_attempt_list, &ftp.client_addr);
+			} else {	/* anonymous */
 				sprintf(str,"%s <%.32s>",user.alias,password);
 				client.user=str;
 			}
 			client_on(sock,&client,TRUE /* update */);
-			goodlogin(&ftp.client_addr);
 
 			lprintf(LOG_INFO,"%04d %s logged in (%u today, %u total)"
 				,sock,user.alias,user.ltoday+1, user.logons+1);
@@ -2942,12 +2940,8 @@ static void ctrl_thread(void* arg)
 				lprintf(LOG_WARNING,"%04d !SUSPECTED BOUNCE ATTACK ATTEMPT by %s to %s port %u"
 					,sock,user.alias
 					,inet_ntoa(data_addr.sin_addr),data_addr.sin_port);
-				hacklog(&scfg, "FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
+				ftp_hacklog("FTP BOUNCE", user.alias, cmd, host_name, &ftp.client_addr);
 				sockprintf(sock,"504 Bad port number.");	
-#ifdef _WIN32
-				if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
-					PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
 				continue; /* As recommended by RFC2577 */
 			}
 			data_addr.sin_port=htons(data_addr.sin_port);
@@ -4162,11 +4156,7 @@ static void ctrl_thread(void* arg)
 					success=FALSE;
 					lprintf(LOG_WARNING,"%04d !ILLEGAL FILENAME ATTEMPT by %s: %s"
 						,sock,user.alias,p);
-					hacklog(&scfg, "FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
-#ifdef _WIN32
-					if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
-						PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
+					ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
 				} else {
 					if(fexistcase(fname)) {
 						success=TRUE;
@@ -4340,11 +4330,7 @@ static void ctrl_thread(void* arg)
 					lprintf(LOG_WARNING,"%04d !ILLEGAL FILENAME ATTEMPT by %s: %s"
 						,sock,user.alias,p);
 					sockprintf(sock,"553 Illegal filename attempt");
-					hacklog(&scfg, "FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
-#ifdef _WIN32
-					if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
-						PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
+					ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
 					continue;
 				}
 				SAFEPRINTF2(fname,"%s%s",scfg.dir[dir]->path,p);
@@ -4540,11 +4526,7 @@ static void ctrl_thread(void* arg)
 			!strnicmp(cmd,"SITE EXEC",9)) {
 			lprintf(LOG_WARNING,"%04d !SUSPECTED HACK ATTEMPT by %s: '%s'"
 				,sock,user.alias,cmd);
-			hacklog(&scfg, "FTP", user.alias, cmd, host_name, &ftp.client_addr);
-#ifdef _WIN32
-			if(startup->hack_sound[0] && !(startup->options&FTP_OPT_MUTE)) 
-				PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
-#endif
+			ftp_hacklog("FTP", user.alias, cmd, host_name, &ftp.client_addr);
 		}		
 		sockprintf(sock,"500 Syntax error: '%s'",cmd);
 		lprintf(LOG_WARNING,"%04d !UNSUPPORTED COMMAND from %s: '%s'"
@@ -4646,9 +4628,24 @@ static void ctrl_thread(void* arg)
 
 static void cleanup(int code, int line)
 {
+	char				tmp[128];
+	login_attempt_t*	login_attempt;
+
 #ifdef _DEBUG
 	lprintf(LOG_DEBUG,"0000 cleanup called from line %d",line);
 #endif
+
+	while((login_attempt=loginAttemptPop(&login_attempt_list)) != NULL) {
+		if(login_attempt->count > 1)
+			lprintf(LOG_NOTICE,"0000 Multiple (%u) failed login attempts from %s, last occurred on %.24s (user: %s, password: %s)"
+				,login_attempt->count, inet_ntoa(login_attempt->addr)
+				,ctime_r(&login_attempt->time, tmp)
+				,login_attempt->user
+				,login_attempt->pass
+				);
+		loginAttemptFree(login_attempt);
+	}
+	loginAttemptListFree(&login_attempt_list);
 	free_cfg(&scfg);
 	free_text(text);
 
@@ -4768,6 +4765,7 @@ void DLLCALL ftp_server(void* arg)
 	js_server_props.interface_addr=&startup->interface_addr;
 #endif
 
+	loginAttemptListInit(&login_attempt_list);
 
 	uptime=0;
 	served=0;
@@ -5013,8 +5011,6 @@ void DLLCALL ftp_server(void* arg)
 				continue;
 			}
 			
-			new_connection(client_socket, &client_addr);
-
 			if(active_clients>=startup->max_clients) {
 				lprintf(LOG_WARNING,"%04d !MAXIMUM CLIENTS (%d) reached, access denied"
 					,client_socket, startup->max_clients);
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index 759c471f4c..754529ecb6 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -103,6 +103,7 @@ static str_list_t recycle_semfiles;
 static str_list_t shutdown_semfiles;
 static int		mailproc_count;
 static js_server_props_t js_server_props;
+static link_list_t	login_attempt_list;
 
 struct {
 	volatile ulong	sockets;
@@ -723,46 +724,23 @@ static u_long resolve_ip(char *inaddr)
 /* A successful login from the same host resets the counter.				*/
 /****************************************************************************/
 
-static struct {
-	IN_ADDR addr;		/* host with consecutive failed login attmepts */
-	ulong	attempts;	/* number of consectuive failed login attempts */
-} bad_login;
-
-
-static void new_connection(SOCKET sock, const char* prot, SOCKADDR_IN* addr)
+static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
 {
-	if(bad_login.attempts && memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-		lprintf(LOG_WARNING,"%04d %s Delaying acceptance of suspicious connection from: %s", sock, prot, inet_ntoa(addr->sin_addr));
-		mswait(bad_login.attempts*1000);
-	}
-}
+	char	reason[128];
+	ulong	count;
 
-static void goodlogin(SOCKADDR_IN* addr)
-{
-	if(memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-		memset(&bad_login.addr,0,sizeof(bad_login.addr));
-		bad_login.attempts=0;
-	}
-}
-
-static void badlogin(SOCKET sock, BOOL pop3, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
-{
 	if(addr!=NULL) {
-		if(memcmp(&bad_login.addr,&addr->sin_addr,sizeof(bad_login.addr))==0) {
-			if(++bad_login.attempts >= 10)
-				hacklog(&scfg, pop3 ? "POP3 LOGIN" : "SMTP LOGIN", user, passwd, host, addr);
-		} else {
-			bad_login.attempts=1;
-			bad_login.addr = addr->sin_addr;
-		}
+		SAFEPRINTF(reason,"%s LOGIN", prot);
+		count=loginFailure(&login_attempt_list, addr, prot, user, passwd);
+		if(count>=LOGIN_ATTEMPT_HACKLOG)
+			hacklog(&scfg, reason, user, passwd, host, addr);
+		if(count>=LOGIN_ATTEMPT_FILTER)
+			filter_ip(&scfg, (char*)prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
+				,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
 	}
 
-	mswait(5000);
-
-	if(pop3)
-		sockprintf(sock,pop_err);
-	else /* SMTP */
-		sockprintf(sock,badauth_rsp);
+	mswait(LOGIN_ATTEMPT_DELAY);
+	sockprintf(sock,(char*)resp);
 }
 
 static void pop3_thread(void* arg)
@@ -796,6 +774,7 @@ static void pop3_thread(void* arg)
 	client_t	client;
 	mail_t*		mail;
 	pop3_t		pop3=*(pop3_t*)arg;
+	list_node_t*	login_attempt;
 
 	SetThreadName("POP3");
 	thread_up(TRUE /* setuid */);
@@ -865,6 +844,12 @@ static void pop3_thread(void* arg)
 	SAFEPRINTF(str,"POP3: %s", host_ip);
 	status(str);
 
+	if((login_attempt=loginAttempted(&login_attempt_list, &pop3.client_addr)) != NULL
+		&& ((login_attempt_t*)login_attempt->data)->count > 1) {
+		lprintf(LOG_NOTICE,"%04d POP3 Delaying suspicious connection from: %s", socket, inet_ntoa(pop3.client_addr.sin_addr));
+		mswait(((login_attempt_t*)login_attempt->data)->count*1000);
+	}
+
 	mail=NULL;
 
 	do {
@@ -923,19 +908,19 @@ static void pop3_thread(void* arg)
 			else
 				lprintf(LOG_NOTICE,"%04d !POP3 UNKNOWN USER: %s"
 					,socket, username);
-			badlogin(socket, /* pop3: */TRUE, username, password, host_name, &pop3.client_addr);
+			badlogin(socket, client.protocol, pop_err, username, password, host_name, &pop3.client_addr);
 			break;
 		}
 		if((i=getuserdat(&scfg, &user))!=0) {
 			lprintf(LOG_ERR,"%04d !POP3 ERROR %d getting data on user (%s)"
 				,socket, i, username);
-			badlogin(socket, /* pop3: */TRUE, NULL, NULL, NULL, NULL);
+			badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
 			break;
 		}
 		if(user.misc&(DELETED|INACTIVE)) {
 			lprintf(LOG_NOTICE,"%04d !POP3 DELETED or INACTIVE user #%u (%s)"
 				,socket, user.number, username);
-			badlogin(socket, /* pop3: */TRUE, NULL, NULL, NULL, NULL);
+			badlogin(socket, client.protocol, pop_err, NULL, NULL, NULL, NULL);
 			break;
 		}
 		if(apop) {
@@ -951,7 +936,7 @@ static void pop3_thread(void* arg)
 				lprintf(LOG_DEBUG,"%04d !POP3 calc digest: %s",socket,str);
 				lprintf(LOG_DEBUG,"%04d !POP3 resp digest: %s",socket,response);
 #endif
-				badlogin(socket, /* pop3: */TRUE, username, response, host_name, &pop3.client_addr);
+				badlogin(socket, client.protocol, pop_err, username, response, host_name, &pop3.client_addr);
 				break;
 			}
 		} else if(stricmp(password,user.pass)) {
@@ -961,11 +946,12 @@ static void pop3_thread(void* arg)
 			else
 				lprintf(LOG_NOTICE,"%04d !POP3 FAILED Password attempt for user %s"
 					,socket, username);
-			badlogin(socket, /* pop3: */TRUE, username, password, host_name, &pop3.client_addr);
+			badlogin(socket, client.protocol, pop_err, username, password, host_name, &pop3.client_addr);
 			break;
 		}
 
-		goodlogin(&pop3.client_addr);
+		if(user.pass[0])
+			loginSuccess(&login_attempt_list, &pop3.client_addr);
 
 		putuserrec(&scfg,user.number,U_COMP,LEN_COMP,host_name);
 		putuserrec(&scfg,user.number,U_NOTE,LEN_NOTE,host_ip);
@@ -2301,6 +2287,7 @@ static void smtp_thread(void* arg)
 	JSContext*	js_cx=NULL;
 	JSObject*	js_glob=NULL;
 	int32		js_result;
+	list_node_t*	login_attempt;
 	struct mailproc*	mailproc;
 
 	enum {
@@ -2499,6 +2486,12 @@ static void smtp_thread(void* arg)
 	SAFEPRINTF(str,"SMTP: %s",host_ip);
 	status(str);
 
+	if((login_attempt=loginAttempted(&login_attempt_list, &smtp.client_addr)) != NULL
+		&& ((login_attempt_t*)login_attempt->data)->count > 1) {
+		lprintf(LOG_NOTICE,"%04d SMTP Delaying suspicious connection from: %s", socket, inet_ntoa(smtp.client_addr.sin_addr));
+		mswait(((login_attempt_t*)login_attempt->data)->count*1000);
+	}
+
 	/* SMTP session active: */
 
 	sockprintf(socket,"220 %s Synchronet SMTP Server %s-%s Ready"
@@ -3322,19 +3315,19 @@ static void smtp_thread(void* arg)
 				else
 					lprintf(LOG_WARNING,"%04d !SMTP UNKNOWN USER: %s"
 						,socket, user_name);
-				badlogin(socket, /* pop3: */FALSE, user_name, user_pass, host_name, &smtp.client_addr);
+				badlogin(socket, client.protocol, badauth_rsp, user_name, user_pass, host_name, &smtp.client_addr);
 				break;
 			}
 			if((i=getuserdat(&scfg, &relay_user))!=0) {
 				lprintf(LOG_ERR,"%04d !SMTP ERROR %d getting data on user (%s)"
 					,socket, i, user_name);
-				badlogin(socket, /* pop3: */FALSE, NULL, NULL, NULL, NULL);
+				badlogin(socket, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
 				break;
 			}
 			if(relay_user.misc&(DELETED|INACTIVE)) {
 				lprintf(LOG_WARNING,"%04d !SMTP DELETED or INACTIVE user #%u (%s)"
 					,socket, relay_user.number, user_name);
-				badlogin(socket, /* pop3: */FALSE, NULL, NULL, NULL, NULL);
+				badlogin(socket, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
 				break;
 			}
 			if(stricmp(user_pass,relay_user.pass)) {
@@ -3344,11 +3337,12 @@ static void smtp_thread(void* arg)
 				else
 					lprintf(LOG_WARNING,"%04d !SMTP FAILED Password attempt for user %s"
 						,socket, user_name);
-				badlogin(socket, /* pop3: */FALSE, user_name, user_pass, host_name, &smtp.client_addr);
+				badlogin(socket, client.protocol, badauth_rsp, user_name, user_pass, host_name, &smtp.client_addr);
 				break;
 			}
 
-			goodlogin(&smtp.client_addr);
+			if(relay_user.pass[0])
+				loginSuccess(&login_attempt_list, &smtp.client_addr);
 
 			/* Update client display */
 			client.user=relay_user.alias;
@@ -3391,19 +3385,19 @@ static void smtp_thread(void* arg)
 			if((relay_user.number=matchuser(&scfg,user_name,FALSE))==0) {
 				lprintf(LOG_WARNING,"%04d !SMTP UNKNOWN USER: %s"
 					,socket, user_name);
-				badlogin(socket, /* pop3: */FALSE, user_name, user_pass, host_name, &smtp.client_addr);
+				badlogin(socket, client.protocol, badauth_rsp, user_name, user_pass, host_name, &smtp.client_addr);
 				break;
 			}
 			if((i=getuserdat(&scfg, &relay_user))!=0) {
 				lprintf(LOG_ERR,"%04d !SMTP ERROR %d getting data on user (%s)"
 					,socket, i, user_name);
-				badlogin(socket, /* pop3: */FALSE, NULL, NULL, NULL, NULL);
+				badlogin(socket, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
 				break;
 			}
 			if(relay_user.misc&(DELETED|INACTIVE)) {
 				lprintf(LOG_WARNING,"%04d !SMTP DELETED or INACTIVE user #%u (%s)"
 					,socket, relay_user.number, user_name);
-				badlogin(socket, /* pop3: */FALSE, NULL, NULL, NULL, NULL);
+				badlogin(socket, client.protocol, badauth_rsp, NULL, NULL, NULL, NULL);
 				break;
 			}
 			/* Calculate correct response */
@@ -3428,11 +3422,12 @@ static void smtp_thread(void* arg)
 				lprintf(LOG_DEBUG,"%04d !SMTP resp digest: %s"
 					,socket,p);
 #endif
-				badlogin(socket, /* pop3: */FALSE, user_name, p, host_name, &smtp.client_addr);
+				badlogin(socket, client.protocol, badauth_rsp, user_name, p, host_name, &smtp.client_addr);
 				break;
 			}
 
-			goodlogin(&smtp.client_addr);
+			if(relay_user.pass[0])
+				loginSuccess(&login_attempt_list, &smtp.client_addr);
 
 			/* Update client display */
 			client.user=relay_user.alias;
@@ -4710,7 +4705,22 @@ void DLLCALL mail_terminate(void)
 
 static void cleanup(int code)
 {
-	int i;
+	int					i;
+	char				tmp[128];
+	login_attempt_t*	login_attempt;
+
+	while((login_attempt=loginAttemptPop(&login_attempt_list)) != NULL) {
+		if(login_attempt->count > 1)
+			lprintf(LOG_NOTICE,"0000 %s Multiple (%u) failed login attempts from %s, last occurred on %.24s (user: %s, password: %s)"
+				,login_attempt->prot
+				,login_attempt->count, inet_ntoa(login_attempt->addr)
+				,ctime_r(&login_attempt->time, tmp)
+				,login_attempt->user
+				,login_attempt->pass
+				);
+		loginAttemptFree(login_attempt);
+	}
+	loginAttemptListFree(&login_attempt_list);
 
 	free_cfg(&scfg);
 
@@ -4875,6 +4885,8 @@ void DLLCALL mail_server(void* arg)
 	js_server_props.options=&startup->options;
 	js_server_props.interface_addr=&startup->interface_addr;
 
+	loginAttemptListInit(&login_attempt_list);
+
 	uptime=0;
 	memset(&stats,0,sizeof(stats));
 	startup->recycle_now=FALSE;
@@ -5280,8 +5292,6 @@ void DLLCALL mail_server(void* arg)
 					continue;
 				}
 
-				new_connection(client_socket, "SMTP", &client_addr);
-
 				if(active_clients>=startup->max_clients) {
 					lprintf(LOG_WARNING,"%04d SMTP !MAXIMUM CLIENTS (%u) reached, access denied (%u total)"
 						,client_socket, startup->max_clients, ++stats.connections_refused);
@@ -5346,8 +5356,6 @@ void DLLCALL mail_server(void* arg)
 					continue;
 				}
 
-				new_connection(client_socket, "POP3", &client_addr);
-
 				if(active_clients>=startup->max_clients) {
 					lprintf(LOG_WARNING,"%04d POP3 !MAXIMUM CLIENTS (%u) reached, access denied (%u total)"
 						,client_socket, startup->max_clients, ++stats.connections_refused);
diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index 34e3c1573c..58de450106 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -76,6 +76,7 @@ static volatile ulong	served=0;
 static char		revision[16];
 static str_list_t recycle_semfiles;
 static str_list_t shutdown_semfiles;
+static link_list_t login_attempt_list;
 
 typedef struct {
 	/* These are sysop-configurable */
@@ -442,13 +443,29 @@ js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
     return(JS_TRUE);
 }
 
+static void badlogin(SOCKET sock, char* prot, char* user, char* passwd, char* host, SOCKADDR_IN* addr)
+{
+	char reason[128];
+	ulong count;
+
+	SAFEPRINTF(reason,"%s LOGIN", prot);
+	count=loginFailure(&login_attempt_list, addr, prot, user, passwd);
+	if(count>=LOGIN_ATTEMPT_HACKLOG)
+		hacklog(&scfg, reason, user, passwd, host, addr);
+	if(count>=LOGIN_ATTEMPT_FILTER)
+		filter_ip(&scfg, prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
+			,host, inet_ntoa(addr->sin_addr), user, /* fname: */NULL);
+
+	mswait(LOGIN_ATTEMPT_DELAY);
+}
+
 static JSBool
 js_login(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
-	char*		p;
+	char*		user;
+	char*		pass;
 	JSBool		inc_logons=JS_FALSE;
 	jsval		val;
-	JSString*	js_str;
 	service_client_t* client;
 	jsrefcount	rc;
 
@@ -457,58 +474,50 @@ js_login(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	if((client=(service_client_t*)JS_GetContextPrivate(cx))==NULL)
 		return(JS_FALSE);
 
-	/* User name */
-	if((js_str=JS_ValueToString(cx, argv[0]))==NULL) 
+	/* User name or number */
+	if((user=js_ValueToStringBytes(cx, argv[0], NULL))==NULL) 
 		return(JS_FALSE);
 
-	if((p=JS_GetStringBytes(js_str))==NULL) 
+	/* Password */
+	if((pass=js_ValueToStringBytes(cx, argv[1], NULL))==NULL) 
 		return(JS_FALSE);
 
 	rc=JS_SUSPENDREQUEST(cx);
 	memset(&client->user,0,sizeof(user_t));
 
+	/* ToDo Deuce: did you mean to do this *before* the above memset(0) ? */
 	if(client->user.number) {
 		if(client->subscan!=NULL)
 			putmsgptrs(&scfg, client->user.number, client->subscan);
 	}
 
-	if(isdigit(*p))
-		client->user.number=atoi(p);
-	else if(*p)
-		client->user.number=matchuser(&scfg,p,FALSE);
+	if(isdigit(*user))
+		client->user.number=atoi(user);
+	else if(*user)
+		client->user.number=matchuser(&scfg,user,FALSE);
 
 	if(getuserdat(&scfg,&client->user)!=0) {
 		lprintf(LOG_NOTICE,"%04d %s !USER NOT FOUND: '%s'"
-			,client->socket,client->service->protocol,p);
+			,client->socket,client->service->protocol,user);
+		badlogin(client->socket, client->service->protocol, user, pass, client->client->host, &client->addr);
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
 
 	if(client->user.misc&(DELETED|INACTIVE)) {
 		lprintf(LOG_WARNING,"%04d %s !DELETED OR INACTIVE USER #%d: %s"
-			,client->socket,client->service->protocol,client->user.number,p);
+			,client->socket,client->service->protocol,client->user.number,user);
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
 
 	/* Password */
-	if(client->user.pass[0]) {
-		if((js_str=JS_ValueToString(cx, argv[1]))==NULL)  {
-			JS_RESUMEREQUEST(cx, rc);
-			return(JS_FALSE);
-		}
-
-		if((p=JS_GetStringBytes(js_str))==NULL) {
-			JS_RESUMEREQUEST(cx, rc);
-			return(JS_FALSE);
-		}
-
-		if(stricmp(client->user.pass,p)) { /* Wrong password */
-			lprintf(LOG_WARNING,"%04d %s !INVALID PASSWORD ATTEMPT FOR USER: %s"
-				,client->socket,client->service->protocol,client->user.alias);
-			JS_RESUMEREQUEST(cx, rc);
-			return(JS_TRUE);
-		}
+	if(client->user.pass[0] && stricmp(client->user.pass,pass)) { /* Wrong password */
+		lprintf(LOG_WARNING,"%04d %s !INVALID PASSWORD ATTEMPT FOR USER: %s"
+			,client->socket,client->service->protocol,client->user.alias);
+		badlogin(client->socket, client->service->protocol, user, pass, client->client->host, &client->addr);
+		JS_RESUMEREQUEST(cx, rc);
+		return(JS_TRUE);
 	}
 	JS_RESUMEREQUEST(cx, rc);
 
@@ -556,6 +565,9 @@ js_login(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 	val = BOOLEAN_TO_JSVAL(JS_TRUE);
 	JS_SetProperty(cx, obj, "logged_in", &val);
 
+	if(client->user.pass[0])
+		loginSuccess(&login_attempt_list, &client->addr);
+
 	*rval=BOOLEAN_TO_JSVAL(JS_TRUE);
 
 	return(JS_TRUE);
@@ -1006,6 +1018,7 @@ static void js_service_thread(void* arg)
 	client_t				client;
 	service_t*				service;
 	service_client_t		service_client;
+	list_node_t*			login_attempt;
 	/* JavaScript-specific */
 	char					spath[MAX_PATH+1];
 	char					fname[MAX_PATH+1];
@@ -1108,6 +1121,13 @@ static void js_service_thread(void* arg)
 
 	update_clients();
 
+	if((login_attempt=loginAttempted(&login_attempt_list, &service_client.addr)) != NULL
+		&& ((login_attempt_t*)login_attempt->data)->count > 1) {
+		lprintf(LOG_NOTICE,"%04d %s Delaying suspicious connection from: %s"
+			,socket, service->protocol, inet_ntoa(service_client.addr.sin_addr));
+		mswait(((login_attempt_t*)login_attempt->data)->count*1000);
+	}
+
 	/* RUN SCRIPT */
 	SAFECOPY(fname,service->cmd);
 	truncstr(fname," ");
@@ -1358,6 +1378,7 @@ static void native_service_thread(void* arg)
 	client_t				client;
 	service_t*				service;
 	service_client_t		service_client=*(service_client_t*)arg;
+	list_node_t*			login_attempt;
 
 	free(arg);
 
@@ -1452,6 +1473,13 @@ static void native_service_thread(void* arg)
 	/* Initialize client display */
 	client_on(socket,&client,FALSE /* update */);
 
+	if((login_attempt=loginAttempted(&login_attempt_list, &service_client.addr)) != NULL
+		&& ((login_attempt_t*)login_attempt->data)->count > 1) {
+		lprintf(LOG_NOTICE,"%04d %s Delaying suspicious connection from: %s"
+			,socket, service->protocol, inet_ntoa(service_client.addr.sin_addr));
+		mswait(((login_attempt_t*)login_attempt->data)->count*1000);
+	}
+
 	/* RUN SCRIPT */
 	if(strpbrk(service->cmd,"/\\")==NULL)
 		sprintf(cmd,"%s%s",scfg.exec_dir,service->cmd);
@@ -1606,6 +1634,22 @@ static service_t* read_services_ini(const char* services_ini, service_t* service
 
 static void cleanup(int code)
 {
+	char				tmp[128];
+	login_attempt_t*	login_attempt;
+
+	while((login_attempt=loginAttemptPop(&login_attempt_list)) != NULL) {
+		if(login_attempt->count > 1)
+			lprintf(LOG_NOTICE,"0000 %s Multiple (%u) failed login attempts from %s, last occurred on %.24s (user: %s, password: %s)"
+				,login_attempt->prot
+				,login_attempt->count, inet_ntoa(login_attempt->addr)
+				,ctime_r(&login_attempt->time, tmp)
+				,login_attempt->user
+				,login_attempt->pass
+				);
+		loginAttemptFree(login_attempt);
+	}
+	loginAttemptListFree(&login_attempt_list);
+
 	FREE_AND_NULL(service);
 	services=0;
 
@@ -1708,6 +1752,8 @@ void DLLCALL services_thread(void* arg)
 	if(startup->js.max_bytes==0)			startup->js.max_bytes=JAVASCRIPT_MAX_BYTES;
 	if(startup->js.cx_stack==0)				startup->js.cx_stack=JAVASCRIPT_CONTEXT_STACK;
 
+	loginAttemptListInit(&login_attempt_list);
+
 	uptime=0;
 	served=0;
 	startup->recycle_now=FALSE;
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index f1fdc4d549..9faac23c9b 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -2661,3 +2661,78 @@ BOOL DLLCALL check_name(scfg_t* cfg, const char* name)
  	return TRUE;
 } 
 
+/****************************************************************************/
+/* Login attempt/hack tracking												*/
+/****************************************************************************/
+
+/****************************************************************************/
+link_list_t* DLLCALL loginAttemptListInit(link_list_t* list)
+{
+	return listInit(list, LINK_LIST_MUTEX);
+}
+
+/****************************************************************************/
+BOOL DLLCALL loginAttemptListFree(link_list_t* list)
+{
+	return listFree(list);
+}
+
+/****************************************************************************/
+list_node_t* DLLCALL loginAttempted(link_list_t* list, SOCKADDR_IN* addr)
+{
+	list_node_t*		node;
+	login_attempt_t*	attempt;
+
+	for(node=listFirstNode(list); node!=NULL; node=listNextNode(node)) {
+		attempt=node->data;
+		if(memcmp(&attempt->addr,&addr->sin_addr,sizeof(attempt->addr))==0)
+			return node;
+	}
+	return NULL;
+}
+
+/****************************************************************************/
+login_attempt_t* DLLCALL loginAttemptPop(link_list_t* list)
+{
+	return listPopNode(list);
+}
+
+/****************************************************************************/
+void DLLCALL loginAttemptFree(void* data)
+{
+	free(data);
+}
+
+/****************************************************************************/
+void DLLCALL loginSuccess(link_list_t* list, SOCKADDR_IN* addr)
+{
+	list_node_t*		node;
+
+	if((node=loginAttempted(list, addr)) != NULL)
+		listRemoveNode(list, node, /* freeData: */TRUE);
+}
+
+/****************************************************************************/
+ulong DLLCALL loginFailure(link_list_t* list, SOCKADDR_IN* addr, const char* prot, const char* user, const char* pass)
+{
+	list_node_t*		node;
+	login_attempt_t*	attempt;
+
+	if((node=loginAttempted(list, addr)) != NULL) {
+		attempt=node->data;
+		/* Don't count consecutive duplicate attempts (same name and password): */
+		if(strcmp(attempt->user,user)==0 && (pass==NULL || strcmp(attempt->pass,pass)==0))
+			return attempt->count;
+	}
+	else if((attempt=calloc(sizeof(login_attempt_t),sizeof(char))) != NULL)
+		listPushNode(list, attempt);
+	if(attempt==NULL)
+		return 0;
+	attempt->prot=prot;
+	attempt->time=time(NULL);
+	attempt->addr=addr->sin_addr;
+	SAFECOPY(attempt->user, user);
+	SAFECOPY(attempt->pass, pass);
+	attempt->count++;
+	return attempt->count;
+}
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index f805b166f3..eef2199d63 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -8,7 +8,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2009 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 2011 Rob Swindell - http://www.synchro.net/copyright.html		*
  *																			*
  * This program is free software; you can redistribute it and/or			*
  * modify it under the terms of the GNU General Public License				*
@@ -65,6 +65,19 @@
 	#define DLLCALL
 #endif
 
+typedef struct {
+	IN_ADDR		addr;	/* host with consecutive failed login attmepts */
+	ulong		count;	/* number of consectuive failed login attempts */
+	time_t		time;	/* time of last attempt */
+	const char*	prot;	/* protocol used in last attempt */
+	char		user[128];
+	char		pass[128];
+} login_attempt_t;
+
+#define LOGIN_ATTEMPT_DELAY		5000	/* milliseconds */
+#define LOGIN_ATTEMPT_HACKLOG	10		/* write to hack.log after this many consecutive unique attempts */
+#define LOGIN_ATTEMPT_FILTER	100		/* filter client IP address after this many consecutive unique attempts */
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -126,6 +139,15 @@ DLLEXPORT time_t DLLCALL gettimeleft(scfg_t* cfg, user_t* user, time_t starttime
 
 DLLEXPORT BOOL	DLLCALL check_name(scfg_t* cfg, const char* name);
 
+/* Login attempt/hack tracking */
+DLLEXPORT link_list_t*		DLLCALL	loginAttemptListInit(link_list_t*);
+DLLEXPORT BOOL				DLLCALL	loginAttemptListFree(link_list_t*);
+DLLEXPORT list_node_t*		DLLCALL loginAttempted(link_list_t*, SOCKADDR_IN*);
+DLLEXPORT void				DLLCALL	loginSuccess(link_list_t*, SOCKADDR_IN*);
+DLLEXPORT ulong				DLLCALL loginFailure(link_list_t*, SOCKADDR_IN*, const char* prot, const char* user, const char* pass);
+DLLEXPORT login_attempt_t*	DLLCALL loginAttemptPop(link_list_t*);
+DLLEXPORT void				DLLCALL loginAttemptFree(void* data);
+
 #ifdef __cplusplus
 }
 #endif
-- 
GitLab