From 7da4af1be721f91fe9e28b6db37864f8a68aee3d Mon Sep 17 00:00:00 2001
From: rswindell <>
Date: Wed, 18 May 2016 10:15:13 +0000
Subject: [PATCH] Implement IP temporary ban: By default, after 20 consecutive
 (unique) failed login attempts, *or* a failed login attempt wtih a name from
 the name.can filter file. The default temporary ban duration is 10 minutes.
 The temporary ban thershold is configurable via LoginAttemptTempBanThreshold
 in sbbs.ini and the ban duration is configurable via
 LoginAttemptTempBanDuration (in seconds).

---
 src/sbbs3/ftpsrvr.c  | 15 ++++----
 src/sbbs3/ftpsrvr.h  |  5 +--
 src/sbbs3/login.cpp  |  6 ++--
 src/sbbs3/mailsrvr.c | 83 ++++++++++++++++++++++++++++++--------------
 src/sbbs3/mailsrvr.h |  7 ++--
 src/sbbs3/main.cpp   | 24 ++++++-------
 src/sbbs3/sbbs_ini.c | 65 +++++++++++++++++-----------------
 src/sbbs3/services.c | 33 +++++++++---------
 src/sbbs3/services.h |  7 ++--
 src/sbbs3/startup.h  | 21 ++++++-----
 src/sbbs3/userdat.c  | 22 +++++++++++-
 src/sbbs3/userdat.h  |  5 +--
 src/sbbs3/websrvr.c  | 16 +++++----
 src/sbbs3/websrvr.h  |  5 +--
 14 files changed, 181 insertions(+), 133 deletions(-)

diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index 62b6218980..9ab394b11a 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -2243,9 +2243,9 @@ static BOOL badlogin(SOCKET sock, ulong* login_attempts, char* user, char* passw
 
 	if(addr!=NULL) {
 		count=loginFailure(startup->login_attempt_list, addr, "FTP", user, passwd);
-		if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
+		if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
 			ftp_hacklog("FTP LOGIN", user, passwd, host, addr);
-		if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold) {
+		if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
 			inet_addrtop(addr, host_ip, sizeof(host_ip));
 			filter_ip(&scfg, "FTP", "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
 				,host, host_ip, user, /* fname: */NULL);
@@ -2255,7 +2255,7 @@ static BOOL badlogin(SOCKET sock, ulong* login_attempts, char* user, char* passw
 	} else
 		(*login_attempts)++;
 
-	mswait(startup->login_attempt_delay);	/* As recommended by RFC2577 */
+	mswait(startup->login_attempt.delay);	/* As recommended by RFC2577 */
 
 	if((*login_attempts)>=3) {
 		sockprintf(sock,"421 Too many failed login attempts.");
@@ -2421,8 +2421,9 @@ static void ctrl_thread(void* arg)
 	if(!(startup->options&FTP_OPT_NO_HOST_LOOKUP))
 		lprintf(LOG_INFO,"%04d Hostname: %s", sock, host_name);
 
-	if(trashcan(&scfg,host_ip,"ip")) {
-		lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", sock, host_ip);
+	ulong banned = loginBanned(&scfg, startup->login_attempt_list, &ftp.client_addr,  startup->login_attempt);
+	if((banned && lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", socket, host_ip, banned))
+		|| (trashcan(&scfg,host_ip,"ip") && lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", sock, host_ip))) {
 		sockprintf(sock,"550 Access denied.");
 		ftp_close_socket(&sock,__LINE__);
 		thread_down();
@@ -2460,11 +2461,11 @@ static void ctrl_thread(void* arg)
 	client.user="<unknown>";
 	client_on(sock,&client,FALSE /* update */);
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &ftp.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d Throttling suspicious connection from: %s (%u login attempts)"
 			,sock, host_ip, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	sockprintf(sock,"220-%s (%s)",scfg.sys_name, startup->host_name);
diff --git a/src/sbbs3/ftpsrvr.h b/src/sbbs3/ftpsrvr.h
index ff0ad17aac..540bbff1d5 100644
--- a/src/sbbs3/ftpsrvr.h
+++ b/src/sbbs3/ftpsrvr.h
@@ -100,10 +100,7 @@ typedef struct {
 	js_startup_t js;
 
 	/* Login Attempt parameters */
-	ulong	login_attempt_delay;
-	ulong	login_attempt_throttle;
-	ulong	login_attempt_hack_threshold;
-	ulong	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 	link_list_t* login_attempt_list;
 
 } ftp_startup_t;
diff --git a/src/sbbs3/login.cpp b/src/sbbs3/login.cpp
index 62526b6d94..6de4ba711e 100644
--- a/src/sbbs3/login.cpp
+++ b/src/sbbs3/login.cpp
@@ -148,11 +148,11 @@ void sbbs_t::badlogin(char* user, char* passwd)
 
 	SAFEPRINTF(reason,"%s LOGIN", connection);
 	count=loginFailure(startup->login_attempt_list, &client_addr, connection, user, passwd);
-	if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
+	if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
 		::hacklog(&cfg, reason, user, passwd, client_name, &client_addr);
-	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
+	if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold)
 		filter_ip(&cfg, connection, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
 			,client_name, client_ipaddr, user, /* fname: */NULL);
 
-	mswait(startup->login_attempt_delay);
+	mswait(startup->login_attempt.delay);
 }
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index d6aa5d47b3..ace1320303 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -522,7 +522,7 @@ static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxl
 		s=sockprintf(socket,"From: %s",p);	/* use original RFC822 header field */
 	else {
 		char fromname[256];
-		SAFECOPY(fromname, msg->from);
+		SAFEPRINTF(fromname, "\"%s\"", msg->from);
 		if(msg->from_net.type==NET_QWK && msg->from_net.addr!=NULL)
 			SAFEPRINTF2(fromaddr,"%s!%s"
 				,(char*)msg->from_net.addr
@@ -530,7 +530,7 @@ static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxl
 		else if(msg->from_net.type==NET_FIDO && msg->from_net.addr!=NULL) {
 			faddr_t* faddr = (faddr_t *)msg->from_net.addr;
 			char faddrstr[128];
-			SAFEPRINTF2(fromname,"%s (%s)", msg->from, smb_faddrtoa(faddr, NULL));
+			SAFEPRINTF2(fromname,"\"%s\" (%s)", msg->from, smb_faddrtoa(faddr, NULL));
 			if(faddr->point)
 				SAFEPRINTF4(faddrstr,"p%hu.f%hu.n%hu.z%hu"FIDO_TLD
 					,faddr->point, faddr->node, faddr->net, faddr->zone);
@@ -543,9 +543,9 @@ static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxl
 		else 
 			usermailaddr(&scfg,fromaddr,msg->from);
 		if(fromaddr[0]=='<')
-			s=sockprintf(socket,"From: \"%s\" %s",fromname,fromaddr);
+			s=sockprintf(socket,"From: %s %s",fromname,fromaddr);
 		else
-			s=sockprintf(socket,"From: \"%s\" <%s>",fromname,fromaddr);
+			s=sockprintf(socket,"From: %s <%s>",fromname,fromaddr);
 	}
 	if(!s)
 		return(0);
@@ -569,7 +569,7 @@ static ulong sockmimetext(SOCKET socket, smbmsg_t* msg, char* msgtxt, ulong maxl
 			else
 				s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,(char*)msg->to_net.addr);
 		} else if(msg->to_net.type==NET_FIDO) {
-			s=sockprintf(socket,"To: \"%s (%s)\"",msg->to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, NULL));
+			s=sockprintf(socket,"To: \"%s\" (%s)",msg->to, smb_faddrtoa((fidoaddr_t*)msg->to_net.addr, NULL));
 		} else {
 			usermailaddr(&scfg,toaddr,msg->to);
 			s=sockprintf(socket,"To: \"%s\" <%s>",msg->to,toaddr);
@@ -782,15 +782,15 @@ static void badlogin(SOCKET sock, const char* prot, const char* resp, char* user
 	if(addr!=NULL) {
 		SAFEPRINTF(reason,"%s LOGIN", prot);
 		count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
-		if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
+		if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
 			hacklog(&scfg, reason, user, passwd, host, addr);
 		inet_addrtop(addr, ip, sizeof(ip));
-		if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
+		if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold)
 			filter_ip(&scfg, (char*)prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
 				,host, ip, user, /* fname: */NULL);
 	}
 
-	mswait(startup->login_attempt_delay);
+	mswait(startup->login_attempt.delay);
 	sockprintf(sock,(char*)resp);
 }
 
@@ -853,9 +853,9 @@ static void pop3_thread(void* arg)
 	if(!(startup->options&MAIL_OPT_NO_HOST_LOOKUP) && (startup->options&MAIL_OPT_DEBUG_POP3))
 		lprintf(LOG_INFO,"%04d POP3 Hostname: %s", socket, host_name);
 
-	if(trashcan(&scfg,host_ip,"ip")) {
-		lprintf(LOG_NOTICE,"%04d !POP3 CLIENT IP ADDRESS BLOCKED: %s"
-			,socket, host_ip);
+	ulong banned = loginBanned(&scfg, startup->login_attempt_list, &pop3.client_addr,  startup->login_attempt);
+	if((banned && lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", socket, host_ip, banned))
+		|| (trashcan(&scfg,host_ip,"ip") && lprintf(LOG_NOTICE,"%04d !POP3 CLIENT IP ADDRESS BLOCKED: %s",socket, host_ip))) {
 		sockprintf(socket,"-ERR Access denied.");
 		mail_close_socket(socket);
 		thread_down();
@@ -887,11 +887,11 @@ static void pop3_thread(void* arg)
 	SAFEPRINTF(str,"POP3: %s", host_ip);
 	status(str);
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &pop3.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d POP3 Throttling suspicious connection from: %s (%u login attempts)"
 			,socket, host_ip, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	mail=NULL;
@@ -2217,6 +2217,24 @@ static int chk_received_hdr(SOCKET socket,const char *buf,IN_ADDR *dnsbl_result,
 	return(dnsbl_result->s_addr);
 }
 
+static void strip_char(char* str, char ch)
+{
+	char* src;
+	char* p;
+	char* tmp = strdup(str);
+
+	if(tmp == NULL)
+		return;
+	p=tmp;
+	for(src = str; *src; src++) {
+		if(*src != ch)
+			*(p++) = *src;
+	}
+	*p=0;
+	strcpy(str, tmp);
+	free(tmp);
+}
+
 static void parse_mail_address(char* p
 							   ,char* name, size_t name_len
 							   ,char* addr, size_t addr_len)
@@ -2238,20 +2256,21 @@ static void parse_mail_address(char* p
 	SAFECOPY(tmp,p);
 	p=tmp;
 	/* Get the "name" (if possible) */
-	if((tp=strchr(p,'('))!=NULL) {			/* name in parenthesis? */
+	if((tp=strchr(p,'"'))!=NULL) {	/* name in quotes? */
 		p=tp+1;
-		tp=strchr(p,')');
-	} else if((tp=strchr(p,'"'))!=NULL) {	/* name in quotes? */
+		tp=strrchr(p,'"');
+	} else if((tp=strchr(p,'('))!=NULL) {	/* name in parenthesis? */
 		p=tp+1;
-		tp=strchr(p,'"');
+		tp=strrchr(p,')');
 	} else if(*p=='<') {					/* address in brackets? */
 		p++;
-		tp=strchr(p,'>');
+		tp=strrchr(p,'>');
 	} else									/* name, then address in brackets */
 		tp=strchr(p,'<');
 	if(tp) *tp=0;
 	sprintf(name,"%.*s",(int)name_len,p);
 	truncsp(name);
+	strip_char(name, '\\');
 }
 
 /* Decode quoted-printable content-transfer-encoded text */
@@ -2377,7 +2396,8 @@ static void smtp_thread(void* arg)
 	char		spam_bait[MAX_PATH+1];
 	BOOL		spam_bait_result=FALSE;
 	char		spam_block[MAX_PATH+1];
-	char		spam_block_exempt[MAX_PATH+1];
+	char		spam_block_exemptions[MAX_PATH+1];
+	BOOL		spam_block_exempt=FALSE;
 	char		host_name[128];
 	char		host_ip[INET6_ADDRSTRLEN];
 	char		server_ip[INET6_ADDRSTRLEN];
@@ -2528,7 +2548,7 @@ static void smtp_thread(void* arg)
 
 	SAFEPRINTF(spam_bait,"%sspambait.cfg",scfg.ctrl_dir);
 	SAFEPRINTF(spam_block,"%sspamblock.cfg",scfg.ctrl_dir);
-	SAFEPRINTF(spam_block_exempt,"%sspamblock_exempt.cfg",scfg.ctrl_dir);
+	SAFEPRINTF(spam_block_exemptions,"%sspamblock_exempt.cfg",scfg.ctrl_dir);
 
 	inet_addrtop(&server_addr,server_ip,sizeof(server_ip));
 
@@ -2536,8 +2556,20 @@ static void smtp_thread(void* arg)
 		/* local connection */
 		dnsbl_result.s_addr=0;
 	} else {
+		ulong banned = loginBanned(&scfg, startup->login_attempt_list, &smtp.client_addr,  startup->login_attempt);
+		if(banned) {
+			lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", socket, host_ip, banned);
+			mail_close_socket(socket);
+			thread_down();
+			protected_uint32_adjust(&active_clients, -1);
+			update_clients();
+			free(mailproc_to_match);
+			return;
+		}
+
+		spam_block_exempt = findstr(host_ip,spam_block_exemptions) || findstr(host_name,spam_block_exemptions);
 		if(trashcan(&scfg,host_ip,"ip") 
-			|| (findstr(host_ip,spam_block) && !findstr(host_ip,spam_block_exempt))) {
+			|| (findstr(host_ip,spam_block) && !spam_block_exempt)) {
 			lprintf(LOG_NOTICE,"%04d !SMTP CLIENT IP ADDRESS BLOCKED: %s (%u total)"
 				,socket, host_ip, ++stats.sessions_refused);
 			sockprintf(socket,"550 CLIENT IP ADDRESS BLOCKED: %s", host_ip);
@@ -2550,7 +2582,7 @@ static void smtp_thread(void* arg)
 		}
 
 		if(trashcan(&scfg,host_name,"host") 
-			|| (findstr(host_name,spam_block) && !findstr(host_name,spam_block_exempt))) {
+			|| (findstr(host_name,spam_block) && !spam_block_exempt)) {
 			lprintf(LOG_NOTICE,"%04d !SMTP CLIENT HOSTNAME BLOCKED: %s (%u total)"
 				,socket, host_name, ++stats.sessions_refused);
 			sockprintf(socket,"550 CLIENT HOSTNAME BLOCKED: %s", host_name);
@@ -2641,11 +2673,11 @@ static void smtp_thread(void* arg)
 	SAFEPRINTF(str,"SMTP: %s",host_ip);
 	status(str);
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &smtp.client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d SMTP Throttling suspicious connection from: %s (%u login attempts)"
 			,socket, host_ip, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	/* SMTP session active: */
@@ -3849,8 +3881,7 @@ static void smtp_thread(void* arg)
 				if(relay_user.number==0) {
 					strcpy(tmp,"IGNORED");
 					if(dnsbl_result.s_addr==0						/* Don't double-filter */
-						&& !findstr(host_name,spam_block_exempt)
-						&& !findstr(host_ip,spam_block_exempt))	{ 
+						&& !spam_block_exempt)	{ 
 						lprintf(LOG_NOTICE,"%04d !BLOCKING IP ADDRESS: %s in %s", socket, host_ip, spam_block);
 						filter_ip(&scfg, "SMTP", reason, host_name, host_ip, reverse_path, spam_block);
 						strcat(tmp," and BLOCKED");
diff --git a/src/sbbs3/mailsrvr.h b/src/sbbs3/mailsrvr.h
index e8aae6b8fb..7af590539c 100644
--- a/src/sbbs3/mailsrvr.h
+++ b/src/sbbs3/mailsrvr.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 2014 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 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				*
@@ -120,10 +120,7 @@ typedef struct {
 	js_startup_t js;
 
 	/* Login Attempt parameters */
-	ulong	login_attempt_delay;
-	ulong	login_attempt_throttle;
-	ulong	login_attempt_hack_threshold;
-	ulong	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 	link_list_t* login_attempt_list;
 
 } mail_startup_t;
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index ff8e95b9f3..226518607f 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -4146,11 +4146,11 @@ void node_thread(void* arg)
 	}
 #endif
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &sbbs->client_addr)) > 1) {
 		lprintf(LOG_DEBUG,"Node %d Throttling suspicious connection from: %s (%u login attempts)"
 			,sbbs->cfg.node_num, sbbs->client_ipaddr, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	if(sbbs->answer()) {
@@ -5121,6 +5121,16 @@ NO_SSH:
 #endif
 			, host_ip, inet_addrport(&client_addr));
 
+		ulong banned = loginBanned(&scfg, startup->login_attempt_list, &client_addr,  startup->login_attempt);
+		if((banned && lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", client_socket, host_ip, banned))
+			|| (sbbs->trashcan(host_ip,"ip") && lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", client_socket, host_ip))) {
+			SSH_END();
+			close_socket(client_socket);
+			SAFEPRINTF(logstr, "Blocked IP: %s",host_ip);
+			sbbs->syslog("@!",logstr);
+			continue;
+		}
+
 #ifdef _WIN32
 		if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
 			PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
@@ -5202,16 +5212,6 @@ NO_SSH:
    		sbbs->client_socket=client_socket;	// required for output to the user
         sbbs->online=ON_REMOTE;
 
-		if(sbbs->trashcan(host_ip,"ip")) {
-			SSH_END();
-			close_socket(client_socket);
-			lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s"
-				,client_socket, host_ip);
-			SAFEPRINTF(logstr, "Blocked IP: %s",host_ip);
-			sbbs->syslog("@!",logstr);
-			continue;
-		}
-
 		if(rlogin)
 			sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */
 
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 1651ef109e..1162e3fd6c 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -64,6 +64,8 @@ static const char*	strHackAttemptSound="HackAttemptSound";
 static const char*	strLoginAttemptDelay="LoginAttemptDelay";
 static const char*	strLoginAttemptThrottle="LoginAttemptThrottle";
 static const char*	strLoginAttemptHackThreshold="LoginAttemptHackThreshold";
+static const char*	strLoginAttemptTempBanThreshold="LoginAttemptTempBanThreshold";
+static const char*	strLoginAttemptTempBanDuration="LoginAttemptTempBanDuration";
 static const char*	strLoginAttemptFilterThreshold="LoginAttemptFilterThreshold";
 static const char*	strJavaScriptMaxBytes		="JavaScriptMaxBytes";
 static const char*	strJavaScriptContextStack	="JavaScriptContextStack";
@@ -76,10 +78,6 @@ static const char*	strSemFileCheckFrequency	="SemFileCheckFrequency";
 #define DEFAULT_LOG_LEVEL				LOG_DEBUG
 #define DEFAULT_BIND_RETRY_COUNT		2
 #define DEFAULT_BIND_RETRY_DELAY		15
-#define DEFAULT_LOGIN_ATTEMPT_DELAY		5000	/* milliseconds */
-#define DEFAULT_LOGIN_ATTEMPT_THROTTLE	1000	/* milliseconds */
-#define DEFAULT_LOGIN_ATTEMPT_HACKLOG	10		/* write to hack.log after this many consecutive unique attempts */
-#define DEFAULT_LOGIN_ATTEMPT_FILTER	0		/* filter client IP address after this many consecutive unique attempts */
 
 void sbbs_get_ini_fname(char* ini_file, char* ctrl_dir, char* pHostName)
 {
@@ -190,6 +188,29 @@ BOOL sbbs_set_js_settings(
 	return(!failure);
 }
 
+static struct login_attempt_settings get_login_attempt_settings(str_list_t list, const char* section, global_startup_t* global)
+{
+	struct login_attempt_settings settings;
+
+	settings.delay				=iniGetInteger(list,section,strLoginAttemptDelay			,global == NULL ? 5000 : global->login_attempt.delay);
+	settings.throttle			=iniGetInteger(list,section,strLoginAttemptThrottle			,global == NULL ? 1000 : global->login_attempt.throttle);
+	settings.hack_threshold		=iniGetInteger(list,section,strLoginAttemptHackThreshold	,global == NULL ? 10 : global->login_attempt.hack_threshold);
+	settings.tempban_threshold	=iniGetInteger(list,section,strLoginAttemptTempBanThreshold	,global == NULL ? 20 : global->login_attempt.tempban_threshold);
+	settings.tempban_duration	=iniGetInteger(list,section,strLoginAttemptTempBanDuration	,global == NULL ? (10*60) : global->login_attempt.tempban_duration);
+	settings.filter_threshold	=iniGetInteger(list,section,strLoginAttemptFilterThreshold	,global == NULL ? 0 : global->login_attempt.filter_threshold);
+	return settings;
+}
+
+static void set_login_attempt_settings(str_list_t* lp, const char* section, struct login_attempt_settings settings, ini_style_t style)
+{
+	iniSetInteger(lp,section,strLoginAttemptDelay,settings.delay,&style);
+	iniSetInteger(lp,section,strLoginAttemptThrottle,settings.throttle,&style);
+	iniSetInteger(lp,section,strLoginAttemptHackThreshold,settings.hack_threshold,&style);
+	iniSetInteger(lp,section,strLoginAttemptTempBanThreshold,settings.tempban_threshold,&style);
+	iniSetInteger(lp,section,strLoginAttemptTempBanDuration,settings.tempban_duration,&style);
+	iniSetInteger(lp,section,strLoginAttemptFilterThreshold,settings.filter_threshold,&style);
+}
+
 static void get_ini_globals(str_list_t list, global_startup_t* global)
 {
 	const char* section = "Global";
@@ -221,10 +242,7 @@ static void get_ini_globals(str_list_t list, global_startup_t* global)
 	global->log_level=iniGetLogLevel(list,section,strLogLevel,DEFAULT_LOG_LEVEL);
 	global->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,DEFAULT_BIND_RETRY_COUNT);
 	global->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,DEFAULT_BIND_RETRY_DELAY);
-	global->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,DEFAULT_LOGIN_ATTEMPT_DELAY);
-	global->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,DEFAULT_LOGIN_ATTEMPT_THROTTLE);
-	global->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,DEFAULT_LOGIN_ATTEMPT_HACKLOG);
-	global->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,DEFAULT_LOGIN_ATTEMPT_FILTER);
+	global->login_attempt = get_login_attempt_settings(list, section, NULL);
 
 	/* Setup default values here */
 	global->js.max_bytes		= JAVASCRIPT_MAX_BYTES;
@@ -375,10 +393,8 @@ void sbbs_read_ini(
 
 		bbs->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		bbs->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
-		bbs->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,global->login_attempt_delay);
-		bbs->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,global->login_attempt_throttle);
-		bbs->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold);
-		bbs->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
+
+		bbs->login_attempt = get_login_attempt_settings(list, section, global);
 	}
 
 	/***********************************************************************/
@@ -453,10 +469,7 @@ void sbbs_read_ini(
 
 		ftp->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		ftp->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
-		ftp->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,global->login_attempt_delay);
-		ftp->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,global->login_attempt_throttle);
-		ftp->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold);
-		ftp->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
+		ftp->login_attempt = get_login_attempt_settings(list, section, global);
 	}
 
 	/***********************************************************************/
@@ -551,10 +564,7 @@ void sbbs_read_ini(
 
 		mail->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		mail->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
-		mail->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,global->login_attempt_delay);
-		mail->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,global->login_attempt_throttle);
-		mail->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold);
-		mail->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
+		mail->login_attempt = get_login_attempt_settings(list, section, global);
 	}
 
 	/***********************************************************************/
@@ -598,10 +608,7 @@ void sbbs_read_ini(
 
 		services->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		services->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
-		services->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,global->login_attempt_delay);
-		services->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,global->login_attempt_throttle);
-		services->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold);
-		services->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
+		services->login_attempt = get_login_attempt_settings(list, section, global);
 	}
 
 	/***********************************************************************/
@@ -695,10 +702,7 @@ void sbbs_read_ini(
 
 		web->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		web->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
-		web->login_attempt_delay=iniGetInteger(list,section,strLoginAttemptDelay,global->login_attempt_delay);
-		web->login_attempt_throttle=iniGetInteger(list,section,strLoginAttemptThrottle,global->login_attempt_throttle);
-		web->login_attempt_hack_threshold=iniGetInteger(list,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold);
-		web->login_attempt_filter_threshold=iniGetInteger(list,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold);
+		web->login_attempt = get_login_attempt_settings(list, section, global);
 	}
 
 	free(global_interfaces);
@@ -758,10 +762,7 @@ BOOL sbbs_write_ini(
 		iniSetLogLevel(lp,section,strLogLevel,global->log_level,&style);
 		iniSetInteger(lp,section,strBindRetryCount,global->bind_retry_count,&style);
 		iniSetInteger(lp,section,strBindRetryDelay,global->bind_retry_delay,&style);
-		iniSetInteger(lp,section,strLoginAttemptDelay,global->login_attempt_delay,&style);
-		iniSetInteger(lp,section,strLoginAttemptThrottle,global->login_attempt_throttle,&style);
-		iniSetInteger(lp,section,strLoginAttemptHackThreshold,global->login_attempt_hack_threshold,&style);
-		iniSetInteger(lp,section,strLoginAttemptFilterThreshold,global->login_attempt_filter_threshold,&style);
+		set_login_attempt_settings(lp, section, global->login_attempt, style);
 
 		/* JavaScript operating parameters */
 		if(!sbbs_set_js_settings(lp,section,&global->js,NULL,&style))
diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index 0477f66683..5cb5e10e70 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -331,15 +331,15 @@ static void badlogin(SOCKET sock, char* prot, char* user, char* passwd, char* ho
 
 	SAFEPRINTF(reason,"%s LOGIN", prot);
 	count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
-	if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold)
+	if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold)
 		hacklog(&scfg, reason, user, passwd, host, addr);
-	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold) {
+	if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold) {
 		inet_addrtop(addr, addr_ip, sizeof(addr_ip));
 		filter_ip(&scfg, prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
 			,host, addr_ip, user, /* fname: */NULL);
 	}
 
-	mswait(startup->login_attempt_delay);
+	mswait(startup->login_attempt.delay);
 }
 
 static JSBool
@@ -1085,11 +1085,11 @@ static void js_service_thread(void* arg)
 
 	update_clients();
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &service_client.addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%u login attempts)"
 			,socket, service->protocol, client.addr, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	/* RUN SCRIPT */
@@ -1427,11 +1427,11 @@ static void native_service_thread(void* arg)
 	/* Initialize client display */
 	client_on(socket,&client,FALSE /* update */);
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &service_client.addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%u login attempts)"
 			,socket, service->protocol, client.addr, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	/* RUN SCRIPT */
@@ -2104,21 +2104,22 @@ void DLLCALL services_thread(void* arg)
 						continue;
 					}
 
-	#ifdef _WIN32
-					if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)
-						&& !(service[i].options&BBS_OPT_MUTE))
-						PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
-	#endif
-
-					if(trashcan(&scfg,host_ip,"ip")) {
+					ulong banned = loginBanned(&scfg, startup->login_attempt_list, &client_addr,  startup->login_attempt);
+					if((banned && lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", socket, host_ip, banned))
+						|| (trashcan(&scfg,host_ip,"ip") && lprintf(LOG_NOTICE,"%04d !%s CLIENT BLOCKED in ip.can: %s"
+							,client_socket, service[i].protocol, host_ip))) {
 						FREE_AND_NULL(udp_buf);
-						lprintf(LOG_NOTICE,"%04d !%s CLIENT BLOCKED in ip.can: %s"
-							,client_socket, service[i].protocol, host_ip);
 						mswait(3000);
 						close_socket(client_socket);
 						continue;
 					}
 
+	#ifdef _WIN32
+					if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)
+						&& !(service[i].options&BBS_OPT_MUTE))
+						PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME);
+	#endif
+
 					if((client=malloc(sizeof(service_client_t)))==NULL) {
 						FREE_AND_NULL(udp_buf);
 						lprintf(LOG_CRIT,"%04d !%s ERROR allocating %u bytes of memory for service_client"
diff --git a/src/sbbs3/services.h b/src/sbbs3/services.h
index 642073f62c..662d9e92a2 100644
--- a/src/sbbs3/services.h
+++ b/src/sbbs3/services.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 2011 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 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				*
@@ -83,10 +83,7 @@ typedef struct {
 	js_startup_t js;
 
 	/* Login Attempt parameters */
-	ulong	login_attempt_delay;
-	ulong	login_attempt_throttle;
-	ulong	login_attempt_hack_threshold;
-	ulong	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 	link_list_t* login_attempt_list;
 
 } services_startup_t;
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index 5be96476f2..f6b839225e 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -58,6 +58,16 @@ typedef struct {
 	char	load_path[INI_MAX_VALUE_LEN];	/* additional (comma-separated) directories to search for load()ed scripts */
 } js_startup_t;
 
+/* Login Attempt parameters */
+struct login_attempt_settings {
+	ulong	delay;				/* in milliseconds */
+	ulong	throttle;			/* in milliseconds */
+	ulong	hack_threshold;
+	ulong	tempban_threshold;
+	ulong	tempban_duration;	/* in seconds */
+	ulong	filter_threshold;
+};
+
 typedef struct {
 
 	char	ctrl_dir[INI_MAX_VALUE_LEN];
@@ -71,10 +81,7 @@ typedef struct {
 	js_startup_t js;
 	uint	bind_retry_count;		/* Number of times to retry bind() calls */
 	uint	bind_retry_delay;		/* Time to wait between each bind() retry */
-	ulong	login_attempt_delay;
-	ulong	login_attempt_throttle;
-	ulong	login_attempt_hack_threshold;
-	ulong	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 
 } global_startup_t;
 
@@ -137,11 +144,7 @@ typedef struct {
 	/* JavaScript operating parameters */
 	js_startup_t js;
 
-	/* Login Attempt parameters */
-	ulong	login_attempt_delay;
-	ulong	login_attempt_throttle;
-	ulong	login_attempt_hack_threshold;
-	ulong	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 	link_list_t* login_attempt_list;
 
 } bbs_startup_t;
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 04ad99055a..eee753febc 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -2888,7 +2888,7 @@ ulong DLLCALL loginFailure(link_list_t* list, const union xp_sockaddr* addr, con
 	SAFECOPY(attempt->user, user);
 	SAFECOPY(attempt->pass, pass);
 	attempt->count++;
-	count = attempt->count-attempt->dupes;
+	count = attempt->count - attempt->dupes;
 	if(node==NULL)
 		listPushNodeData(list, attempt, sizeof(login_attempt_t));
 	listUnlock(list);
@@ -2896,6 +2896,26 @@ ulong DLLCALL loginFailure(link_list_t* list, const union xp_sockaddr* addr, con
 	return count;
 }
 
+ulong DLLCALL loginBanned(scfg_t* cfg, link_list_t* list, const union xp_sockaddr* addr, struct login_attempt_settings settings)
+{
+	list_node_t*		node;
+	login_attempt_t*	attempt;
+	BOOL				result = FALSE;
+	long				diff;
+	time32_t			now = time32(NULL);
+
+	listLock(list);
+	node = login_attempted(list, addr);
+	listUnlock(list);
+	if(node == NULL)
+		return 0;
+	attempt = node->data;
+	if(((settings.tempban_threshold && (attempt->count - attempt->dupes) >= settings.tempban_threshold)
+		|| trashcan(cfg, attempt->user, "name")) && now < (attempt->time + settings.tempban_duration))
+		return settings.tempban_duration - (now - attempt->time);
+	return 0;
+}
+
 /****************************************************************************/
 /* Message-new-scan pointer/configuration functions							*/
 /****************************************************************************/
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index 0f8496aad4..5eecdc2252 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -145,9 +145,9 @@ DLLEXPORT BOOL	DLLCALL check_name(scfg_t*, const char* name);
 
 /* Login attempt/hack tracking */
 typedef struct {
-	union xp_sockaddr addr;	/* host with consecutive failed login attmepts */
+	union xp_sockaddr addr;	/* host with consecutive failed login attempts */
 	ulong		count;	/* number of consecutive failed login attempts */
-	ulong		dupes;	/* number of consecutive dupliate login attempts (same name and password) */
+	ulong		dupes;	/* number of consecutive duplicate login attempts (same name and password) */
 	time32_t	time;	/* time of last attempt */
 	char		prot[32];	/* protocol used in last attempt */
 	char		user[128];
@@ -160,6 +160,7 @@ DLLEXPORT long				DLLCALL	loginAttemptListClear(link_list_t*);
 DLLEXPORT long				DLLCALL loginAttempts(link_list_t*, const union xp_sockaddr*);
 DLLEXPORT void				DLLCALL	loginSuccess(link_list_t*, const union xp_sockaddr*);
 DLLEXPORT ulong				DLLCALL loginFailure(link_list_t*, const union xp_sockaddr*, const char* prot, const char* user, const char* pass);
+DLLEXPORT ulong				DLLCALL loginBanned(scfg_t*, link_list_t*, const union xp_sockaddr*, struct login_attempt_settings);
 
 #ifdef __cplusplus
 }
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index 4017ed8979..a24970fdc9 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -1780,18 +1780,18 @@ static void badlogin(SOCKET sock, const char* prot, const char* user, const char
 
 	SAFEPRINTF(reason,"%s LOGIN", prot);
 	count=loginFailure(startup->login_attempt_list, addr, prot, user, passwd);
-	if(startup->login_attempt_hack_threshold && count>=startup->login_attempt_hack_threshold) {
+	if(startup->login_attempt.hack_threshold && count>=startup->login_attempt.hack_threshold) {
 		hacklog(&scfg, reason, user, passwd, host, addr);
 #ifdef _WIN32
 		if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE)) 
 			PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
 #endif
 	}
-	if(startup->login_attempt_filter_threshold && count>=startup->login_attempt_filter_threshold)
+	if(startup->login_attempt.filter_threshold && count>=startup->login_attempt.filter_threshold)
 		filter_ip(&scfg, prot, "- TOO MANY CONSECUTIVE FAILED LOGIN ATTEMPTS"
 			,host, inet_addrtop(addr, addrstr, sizeof(addrstr)), user, /* fname: */NULL);
 	if(count>1)
-		mswait(startup->login_attempt_delay);
+		mswait(startup->login_attempt.delay);
 }
 
 static BOOL check_ars(http_session_t * session)
@@ -6246,9 +6246,11 @@ void http_session_thread(void* arg)
 		}
 	}
 
+	ulong banned = loginBanned(&scfg, startup->login_attempt_list, &session.addr,  startup->login_attempt);
+
 	/* host_ip wasn't defined in http_session_thread */
-	if(trashcan(&scfg,session.host_ip,"ip")) {
-		lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip);
+	if((banned && lprintf(LOG_NOTICE, "%04d %s is TEMPORARILY BANNED (%lu more seconds)", socket, session.host_ip, banned))
+		|| (trashcan(&scfg,session.host_ip,"ip") && lprintf(LOG_NOTICE,"%04d !CLIENT BLOCKED in ip.can: %s", session.socket, session.host_ip))) {
 		close_session_socket(&session);
 		sem_wait(&session.output_thread_terminated);
 		sem_destroy(&session.output_thread_terminated);
@@ -6270,11 +6272,11 @@ void http_session_thread(void* arg)
 	session.client.size=sizeof(session.client);
 	client_on(session.socket, &session.client, /* update existing client record? */FALSE);
 
-	if(startup->login_attempt_throttle
+	if(startup->login_attempt.throttle
 		&& (login_attempts=loginAttempts(startup->login_attempt_list, &session.addr)) > 1) {
 		lprintf(LOG_DEBUG,"%04d %s Throttling suspicious connection from: %s (%u login attempts)"
 			,socket, session.client.protocol, session.host_ip, login_attempts);
-		mswait(login_attempts*startup->login_attempt_throttle);
+		mswait(login_attempts*startup->login_attempt.throttle);
 	}
 
 	session.last_user_num=-1;
diff --git a/src/sbbs3/websrvr.h b/src/sbbs3/websrvr.h
index 8a4bb3ee7f..14b65ed6a8 100644
--- a/src/sbbs3/websrvr.h
+++ b/src/sbbs3/websrvr.h
@@ -106,10 +106,7 @@ typedef struct {
 	js_startup_t js;
 
 	/* Login Attempt parameters */
-	uint32_t	login_attempt_delay;
-	uint32_t	login_attempt_throttle;
-	uint32_t	login_attempt_hack_threshold;
-	uint32_t	login_attempt_filter_threshold;
+	struct login_attempt_settings login_attempt;
 	link_list_t* login_attempt_list;
 
 } web_startup_t;
-- 
GitLab