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