diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c index f5c89121452fe15aad3445c30cb1a9183da6c216..dd12342bec5f85baf432af1ec1184423acc3faba 100644 --- a/src/sbbs3/sbbs_ini.c +++ b/src/sbbs3/sbbs_ini.c @@ -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; } /***********************************************************************/ diff --git a/src/sbbs3/scfg/scfgsrvr.c b/src/sbbs3/scfg/scfgsrvr.c index 32dc77a850ea29f2fcdd5a7de85b43388af0d9c8..d38c43b829f8ba06f97fd4d885d0447c1532224a 100644 --- a/src/sbbs3/scfg/scfgsrvr.c +++ b/src/sbbs3/scfg/scfgsrvr.c @@ -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: diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c index d39e51baa3f2a55cd7d8ef2c3e2c20e597c1b6d7..8b0e3a110c5ce9f61f29378553093ef9eda191f6 100644 --- a/src/sbbs3/websrvr.c +++ b/src/sbbs3/websrvr.c @@ -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(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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; diff --git a/src/sbbs3/websrvr.h b/src/sbbs3/websrvr.h index 8ce722d10db5bf8286970bdfbf3f7b84568997f2..4073c0607be9ee57da160834afb005d48805d792 100644 --- a/src/sbbs3/websrvr.h +++ b/src/sbbs3/websrvr.h @@ -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;