Skip to content
Snippets Groups Projects
Commit cb28dafe authored by Rob Swindell's avatar Rob Swindell :speech_balloon:
Browse files

Implement max concurrent connections (per IP) limit in web server, default: 10

We have this feature for the FTP, Mail, and Terminal servers (with no default
limit) but crazy abuse of my Synchronet web servers has now made this a
necessity for Vertrauen. A limit of 2 used to be considered reasonable long
ago. Today, 10 seems fair enough. Setting to 0 will impose no max-connections
per IP address (other than the total client limit per server).
parent 5c5d736e
Branches
Tags
No related merge requests found
Pipeline #7293 failed
......@@ -515,7 +515,7 @@ void sbbs_read_ini(
bbs->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
bbs->login_attempt = get_login_attempt_settings(list, section, global);
bbs->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
bbs->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
bbs->max_login_inactivity = (uint16_t)iniGetDuration(list, section, strMaxLoginInactivity, 10 * 60);
bbs->max_newuser_inactivity = (uint16_t)iniGetDuration(list, section, strMaxNewUserInactivity, 60 * 60);
......@@ -587,7 +587,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 = get_login_attempt_settings(list, section, global);
ftp->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
ftp->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
}
/***********************************************************************/
......@@ -688,7 +688,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 = get_login_attempt_settings(list, section, global);
mail->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
mail->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
mail->spam_block_duration = (uint)iniGetDuration(list, section, "SpamBlockDuration", 0);
mail->notify_offline_users = iniGetBool(list, section, "NotifyOfflineUsers", false);
}
......@@ -818,6 +818,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 = get_login_attempt_settings(list, section, global);
web->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, WEB_DEFAULT_MAX_CON_CONN);
}
free(global_interfaces);
......@@ -941,7 +942,7 @@ bool sbbs_write_ini(
break;
if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",bbs->outbuf_drain_timeout,&style))
break;
if(!iniSetInteger(lp,section,strMaxConConn,bbs->max_concurrent_connections,&style))
if(!iniSetUInteger(lp,section,strMaxConConn,bbs->max_concurrent_connections,&style))
break;
if(!iniSetDuration(lp, section, strMaxLoginInactivity, bbs->max_login_inactivity, &style))
break;
......@@ -1048,7 +1049,7 @@ bool sbbs_write_ini(
break;
if(!iniSetDuration(lp,section,strMaxInactivity,ftp->max_inactivity,&style))
break;
if(!iniSetInteger(lp,section,strMaxConConn,ftp->max_concurrent_connections,&style))
if(!iniSetUInteger(lp,section,strMaxConConn,ftp->max_concurrent_connections,&style))
break;
if(!iniSetDuration(lp,section,"QwkTimeout",ftp->qwk_timeout,&style))
break;
......@@ -1178,7 +1179,7 @@ bool sbbs_write_ini(
break;
if(!iniSetDuration(lp,section,"ConnectTimeout",mail->connect_timeout,&style))
break;
if(!iniSetInteger(lp,section,strMaxConConn,mail->max_concurrent_connections,&style))
if(!iniSetUInteger(lp,section,strMaxConConn,mail->max_concurrent_connections,&style))
break;
if(strcmp(mail->host_name,global->host_name)==0
......@@ -1422,6 +1423,8 @@ bool sbbs_write_ini(
break;
if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",web->outbuf_drain_timeout,&style))
break;
if(!iniSetUInteger(lp,section,strMaxConConn,web->max_concurrent_connections,&style))
break;
}
/***********************************************************************/
......
......@@ -897,6 +897,7 @@ static void websrvr_cfg(void)
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Access Logging", startup.options & WEB_OPT_HTTP_LOGGING ? str : strDisabled);
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Clients", maximum(startup.max_clients));
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Inactivity", vduration(startup.max_inactivity));
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase Index Script", startup.file_index_script);
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase VPath Prefix", startup.file_vpath_prefix);
snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase VPath for VHosts"
......@@ -992,39 +993,44 @@ static void websrvr_cfg(void)
startup.max_inactivity = (uint16_t)parse_duration(str);
break;
case 12:
SAFECOPY(str, maximum(startup.max_concurrent_connections));
if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent Connections (0=unlimited)", str, 10, K_EDIT | K_NUMBER) > 0)
startup.max_concurrent_connections = atoi(str);
break;
case 13:
uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Index Script"
,startup.file_index_script, sizeof(startup.file_index_script)-1, K_EDIT);
break;
case 13:
case 14:
uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Virtual Path Prefix"
,startup.file_vpath_prefix, sizeof(startup.file_vpath_prefix)-1, K_EDIT);
break;
case 14:
case 15:
startup.file_vpath_for_vhosts = !startup.file_vpath_for_vhosts;
break;
case 15:
case 16:
uifc.input(WIN_MID|WIN_SAV, 0, 0, "Authentication Methods"
,startup.default_auth_list, sizeof(startup.default_auth_list)-1, K_EDIT);
break;
case 16:
case 17:
SAFEPRINTF(str, "%u", startup.outbuf_drain_timeout);
if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Output Buffer Drain Timeout (milliseconds)"
,str, 5, K_NUMBER|K_EDIT) > 0)
startup.outbuf_drain_timeout = atoi(str);
break;
case 17:
case 18:
startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
break;
case 18:
case 19:
startup.options ^= WEB_OPT_NO_CGI;
break;
case 19:
case 20:
if(startup.options & WEB_OPT_NO_CGI)
break;
uifc.input(WIN_MID|WIN_SAV, 0, 0, "CGI Directory"
,startup.cgi_dir, sizeof(startup.cgi_dir)-1, K_EDIT);
break;
case 20:
case 21:
if(startup.options & WEB_OPT_NO_CGI)
break;
strListCombine(startup.cgi_ext, str, sizeof(str), ", ");
......@@ -1034,26 +1040,26 @@ static void websrvr_cfg(void)
uifc.changes = true;
}
break;
case 21:
case 22:
if(startup.options & WEB_OPT_NO_CGI)
break;
uifc.input(WIN_MID|WIN_SAV, 0, 0, "Default CGI MIME Content-Type"
,startup.default_cgi_content, sizeof(startup.default_cgi_content)-1, K_EDIT);
break;
case 22:
case 23:
if(startup.options & WEB_OPT_NO_CGI)
break;
duration_to_str(startup.max_cgi_inactivity, str, sizeof(str));
if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum CGI Inactivity", str, 10, K_EDIT) > 0)
startup.max_cgi_inactivity = (uint16_t)parse_duration(str);
break;
case 23:
case 24:
getar("Web Server Login", startup.login_ars);
break;
case 24:
case 25:
js_startup_cfg(&startup.js);
break;
case 25:
case 26:
login_attempt_cfg(&startup.login_attempt);
break;
default:
......@@ -1241,7 +1247,7 @@ static void ftpsrvr_cfg(void)
break;
case 11:
SAFECOPY(str, maximum(startup.max_concurrent_connections));
if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent Connections (0=unlimited)", str, 10, K_EDIT | K_NUMBER) > 0)
startup.max_concurrent_connections = atoi(str);
break;
case 12:
......
......@@ -82,6 +82,7 @@ static const char* error_302="302 Moved Temporarily";
static const char* error_307="307 Temporary Redirect";
static const char* error_404="404 Not Found";
static const char* error_416="416 Requested Range Not Satisfiable";
static const char* error_429="429 Too Many Requests";
static const char* error_500="500 Internal Server Error";
static const char* error_503="503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
static const char* unknown=STR_UNKNOWN_USER;
......@@ -125,6 +126,7 @@ static str_list_t shutdown_semfiles;
static str_list_t pause_semfiles;
static str_list_t cgi_env;
static struct mqtt mqtt;
static link_list_t current_connections;
static named_string_t** mime_types;
static named_string_t** cgi_handlers;
......@@ -770,6 +772,8 @@ static void update_clients(void)
static void client_on(SOCKET sock, client_t* client, bool update)
{
if(!update)
listAddNodeData(&current_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,true,sock,client,update);
mqtt_client_on(&mqtt, true, sock, client, update);
......@@ -777,6 +781,7 @@ static void client_on(SOCKET sock, client_t* client, bool update)
static void client_off(SOCKET sock)
{
listRemoveTaggedNode(&current_connections, sock, /* free_data */true);
if(startup!=NULL && startup->client_on!=NULL)
startup->client_on(startup->cbdata,false,sock,NULL,false);
mqtt_client_on(&mqtt, false, sock, NULL, false);
......@@ -6968,6 +6973,8 @@ static void cleanup(int code)
update_clients(); /* active_clients is destroyed below */
listFree(&current_connections);
if(protected_uint32_value(active_clients))
lprintf(LOG_WARNING,"!!!! Terminating with %u active clients", protected_uint32_value(active_clients));
else
......@@ -7175,6 +7182,7 @@ void web_server(void* arg)
protected_uint32_init(&thread_count, 0);
do {
listInit(&current_connections, LINK_LIST_MUTEX);
protected_uint32_init(&active_clients,0);
/* Setup intelligent defaults */
......@@ -7418,6 +7426,23 @@ void web_server(void* arg)
inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
if(startup->max_concurrent_connections > 0) {
int ip_len = strlen(host_ip) + 1;
uint connections = listCountMatches(&current_connections, host_ip, ip_len);
if(connections >= startup->max_concurrent_connections
&& !is_host_exempt(&scfg, host_ip, /* host_name */NULL)) {
lprintf(LOG_NOTICE, "%04d [%s] !Maximum concurrent connections (%u) exceeded"
,client_socket, host_ip, startup->max_concurrent_connections);
static int len_429;
if(len_429 < 1)
len_429 = strlen(error_429);
if(sendsocket(client_socket, error_429, len_429) != len_429)
lprintf(LOG_ERR, "%04d FAILED sending error 429", client_socket);
close_socket(&client_socket);
continue;
}
}
if(trashcan(&scfg,host_ip,"ip-silent")) {
close_socket(&client_socket);
continue;
......
......@@ -29,12 +29,14 @@
typedef struct {
STARTUP_COMMON_ELEMENTS
uint16_t max_clients;
uint max_clients;
#define WEB_DEFAULT_MAX_CLIENTS 100 /* 0=unlimited */
uint16_t max_inactivity;
uint max_inactivity;
#define WEB_DEFAULT_MAX_INACTIVITY 120 /* seconds */
uint16_t max_cgi_inactivity;
uint max_cgi_inactivity;
#define WEB_DEFAULT_MAX_CGI_INACTIVITY 120 /* seconds */
uint max_concurrent_connections;
#define WEB_DEFAULT_MAX_CON_CONN 10 /* 0=unlimited */
uint16_t port;
uint16_t tls_port;
str_list_t interfaces;
......@@ -56,7 +58,7 @@ typedef struct {
int tls_error_level; /* Cap the severity of TLS error log messages */
char default_cgi_content[128];
char default_auth_list[128];
uint16_t outbuf_drain_timeout;
uint outbuf_drain_timeout;
/* JavaScript operating parameters */
js_startup_t js;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment