From 9b3be7dc63b3bb6a4b8a3e4e6b2e7f799fdaa5ee Mon Sep 17 00:00:00 2001 From: "Rob Swindell (on Windows 11)" <rob@synchro.net> Date: Thu, 14 Nov 2024 18:24:46 -0800 Subject: [PATCH] Only allow one FTP session per QWKnet user account Vertrauen's FTP server gets abused by QWKnet logins sometimes and handling the race conditions around QWK packet creation attempts is silly - there's no legit reason why a QWKnet account needs to be logged-in multiple times concurrently to the hub's FTP server, so reject the subsequent logins even when they're on different hosts (as is the case with Vertrauen). As part of this change: - fmutex() now takes an new time_t* argument to (optionally) store the time of the mutex file for helping logging (locked since when?). - time_as_hhmm() created to format a string as either HH:MM or HH:MM[a|p] (depending on system configuration for 12 or 24 hour time formatting). - renamed the old hhmmtostr ()to tm_as_hhmm() (since it takes a struct tm arg) and have it return a non-padded string (useful in more situations without requiring truncation) when the sysop prefers 24-hour time. --- src/sbbs3/answer.cpp | 4 ++-- src/sbbs3/date_str.c | 20 +++++++++++++++++--- src/sbbs3/date_str.h | 5 +++-- src/sbbs3/ftpsrvr.c | 16 +++++++++++++++- src/sbbs3/js_global.c | 2 +- src/sbbs3/logout.cpp | 10 +++------- src/sbbs3/main.cpp | 10 ++++++---- src/sbbs3/nopen.c | 13 +++++++++---- src/sbbs3/nopen.h | 2 +- src/sbbs3/sbbsecho.c | 11 +++++++---- 10 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/sbbs3/answer.cpp b/src/sbbs3/answer.cpp index 34ea886662..52a6085388 100644 --- a/src/sbbs3/answer.cpp +++ b/src/sbbs3/answer.cpp @@ -114,8 +114,8 @@ bool sbbs_t::answer() memset(&tm,0,sizeof(tm)); localtime_r(&now,&tm); - safe_snprintf(str,sizeof(str),"%s %s %s %02d %u Node %3u" - ,hhmmtostr(&cfg,&tm,str2) + safe_snprintf(str,sizeof(str),"%-6s %s %s %02d %u Node %3u" + ,tm_as_hhmm(&cfg, &tm, str2) ,wday[tm.tm_wday] ,mon[tm.tm_mon],tm.tm_mday,tm.tm_year+1900,cfg.node_num); logline("@ ",str); diff --git a/src/sbbs3/date_str.c b/src/sbbs3/date_str.c index c147a0f6ff..7afd30cc01 100644 --- a/src/sbbs3/date_str.c +++ b/src/sbbs3/date_str.c @@ -218,11 +218,12 @@ char* minutes_to_str(uint min, char* str, size_t size) } /****************************************************************************/ +/* Returns 5 or 6 character string, depending on configuration */ /****************************************************************************/ -char* hhmmtostr(scfg_t* cfg, struct tm* tm, char* str) +char* tm_as_hhmm(scfg_t* cfg, struct tm* tm, char* str) { - if(cfg->sys_misc&SM_MILITARY) - sprintf(str,"%02d:%02d " + if(cfg != NULL && (cfg->sys_misc & SM_MILITARY)) + sprintf(str,"%02d:%02d" ,tm->tm_hour,tm->tm_min); else sprintf(str,"%02d:%02d%c" @@ -231,6 +232,19 @@ char* hhmmtostr(scfg_t* cfg, struct tm* tm, char* str) return(str); } +/****************************************************************************/ +/* Returns 5 or 6 character string, depending on configuration */ +/****************************************************************************/ +char* time_as_hhmm(scfg_t* cfg, time_t t, char* str) +{ + struct tm tm; + if(localtime_r(&t, &tm) == NULL) { + strcpy(str,"??:??"); + return str; + } + return tm_as_hhmm(cfg, &tm, str); +} + /****************************************************************************/ /* Generates a 24 character ASCII string that represents the time_t pointer */ /* Used as a replacement for ctime() */ diff --git a/src/sbbs3/date_str.h b/src/sbbs3/date_str.h index 993750d032..04b0bffd66 100644 --- a/src/sbbs3/date_str.h +++ b/src/sbbs3/date_str.h @@ -41,8 +41,9 @@ DLLEXPORT char * datestr(scfg_t*, time_t, char* str); DLLEXPORT char * verbal_datestr(scfg_t*, time_t, char* str); DLLEXPORT char * sectostr(uint sec, char *str); DLLEXPORT char * seconds_to_str(uint, char*); -DLLEXPORT char * hhmmtostr(scfg_t* cfg, struct tm* tm, char* str); -DLLEXPORT char * timestr(scfg_t* cfg, time32_t intime, char* str); +DLLEXPORT char * tm_as_hhmm(scfg_t* cfg, struct tm*, char* buf); +DLLEXPORT char * time_as_hhmm(scfg_t* cfg, time_t, char* buf); +DLLEXPORT char * timestr(scfg_t* cfg, time32_t, char* str); DLLEXPORT char* minutes_to_str(uint min, char* str, size_t); #ifdef __cplusplus diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c index 216752481f..0e5ef8d16f 100644 --- a/src/sbbs3/ftpsrvr.c +++ b/src/sbbs3/ftpsrvr.c @@ -2134,6 +2134,7 @@ static void ctrl_thread(void* arg) char aliasfile[MAX_PATH+1]; char aliaspath[MAX_PATH+1]; char mls_path[MAX_PATH+1]; + char mutex_file[MAX_PATH+1]=""; char *mls_fname; char permstr[11]; char aliasline[512]; @@ -2595,6 +2596,17 @@ static void ctrl_thread(void* arg) continue; } + snprintf(mutex_file, sizeof mutex_file, "%suser/%04u.ftp", scfg.data_dir, user.number); + if(user.rest & FLAG('Q')) { // QWKnet accont + if(!fmutex(mutex_file, startup->host_name, /* max_age: */60 * 60, &t)) { + lprintf(LOG_NOTICE, "%04d <%s> QWKnet account already logged-in to FTP server: %s (since %s)" + ,sock, user.alias, mutex_file, time_as_hhmm(&scfg, t, str)); + sockprintf(sock, sess, "421 QWKnet accounts are limited to one concurrent FTP session"); + user.number = 0; + break; + } + } + /* Update client display */ if(user.pass[0]) { SAFECOPY(client.user, user.alias); @@ -4254,7 +4266,7 @@ static void ctrl_thread(void* arg) if(!fexistcase(qwkfile)) { lprintf(LOG_INFO,"%04d <%s> creating QWK packet...",sock,user.alias); sprintf(str,"%spack%04u.now",scfg.data_dir,user.number); - if(!fmutex(str, startup->host_name, /* max_age: */60 * 60)) { + if(!fmutex(str, startup->host_name, /* max_age: */60 * 60, /* time: */NULL)) { lprintf(LOG_WARNING, "%04d <%s> !ERROR %d (%s) creating mutex-semaphore file: %s" ,sock, user.alias, errno, strerror(errno), str); sockprintf(sock,sess,"451 Packet creation already in progress (are you logged-in concurrently?)"); @@ -4909,6 +4921,8 @@ static void ctrl_thread(void* arg) } if(user.number) { + if(*mutex_file != '\0') + ftp_remove(sock, __LINE__, mutex_file, user.alias, LOG_ERR); /* Update User Statistics */ if(!logoutuserdat(&scfg, &user, time(NULL), logintime)) lprintf(LOG_ERR,"%04d <%s> !ERROR in logoutuserdat", sock, user.alias); diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c index a6a611de6d..d22907967d 100644 --- a/src/sbbs3/js_global.c +++ b/src/sbbs3/js_global.c @@ -3548,7 +3548,7 @@ js_fmutex(JSContext *cx, uintN argc, jsval *arglist) } rc=JS_SUSPENDREQUEST(cx); - ret=fmutex(fname,text,max_age); + ret=fmutex(fname,text,max_age, /* time: */NULL); free(fname); if(text) free(text); diff --git a/src/sbbs3/logout.cpp b/src/sbbs3/logout.cpp index 11e997b018..27234c1e8c 100644 --- a/src/sbbs3/logout.cpp +++ b/src/sbbs3/logout.cpp @@ -33,16 +33,13 @@ void sbbs_t::logout(bool logged_in) int i,j; ushort ttoday; node_t node; - struct tm tm; now=time(NULL); - if(localtime_r(&now,&tm)==NULL) - errormsg(WHERE,ERR_CHK,"localtime",(ulong)now); if(!useron.number) { /* Not logged in, so do nothing */ if(!online) { - SAFEPRINTF2(str,"%s T:%3u sec\r\n" - ,hhmmtostr(&cfg,&tm,tmp) + SAFEPRINTF2(str,"%-6s T:%3u sec\r\n" + ,time_as_hhmm(&cfg, now, tmp) ,(uint)(now-answertime)); logline("@-",str); } @@ -133,8 +130,7 @@ void sbbs_t::logout(bool logged_in) putuserstr(useron.number, USER_CURSUB, cfg.sub[usrsub[curgrp][cursub[curgrp]]]->code); if(usrlibs>0) putuserstr(useron.number, USER_CURDIR, cfg.dir[usrdir[curlib][curdir[curlib]]]->code); - hhmmtostr(&cfg,&tm,str); - SAFECAT(str," "); + snprintf(str, sizeof str, "%-6s ", time_as_hhmm(&cfg, now, tmp)); if(sys_status&SS_USERON) { char ulb[64]; char dlb[64]; diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp index bd60e89e2f..aea3e414ac 100644 --- a/src/sbbs3/main.cpp +++ b/src/sbbs3/main.cpp @@ -2921,9 +2921,10 @@ void event_thread(void* arg) sbbs->useron.number = atoi(g.gl_pathv[i]+offset); getuserdat(&sbbs->cfg,&sbbs->useron); if(sbbs->useron.number != 0 && !(sbbs->useron.misc&(DELETED|INACTIVE))) { + time_t t; SAFEPRINTF(semfile,"%s.lock",g.gl_pathv[i]); - if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE)) { - sbbs->lprintf(LOG_INFO," %s exists (unpack in progress?)", semfile); + if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE, &t)) { + sbbs->lprintf(LOG_INFO," %s exists (unpack in progress?) since %s", semfile, time_as_hhmm(&sbbs->cfg, t, str)); continue; } sbbs->online=ON_LOCAL; @@ -2973,8 +2974,9 @@ void event_thread(void* arg) sbbs->lprintf(LOG_INFO, "QWK pack semaphore signaled: %s", g.gl_pathv[i]); sbbs->useron.number = atoi(g.gl_pathv[i]+offset); SAFEPRINTF2(semfile,"%spack%04u.lock",sbbs->cfg.data_dir,sbbs->useron.number); - if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE)) { - sbbs->lprintf(LOG_INFO,"%s exists (pack in progress?)", semfile); + time_t t; + if(!fmutex(semfile,startup->host_name,TIMEOUT_MUTEX_FILE, &t)) { + sbbs->lprintf(LOG_INFO,"%s exists (pack in progress?) since %s", semfile, time_as_hhmm(&sbbs->cfg, t, str)); continue; } getuserdat(&sbbs->cfg,&sbbs->useron); diff --git a/src/sbbs3/nopen.c b/src/sbbs3/nopen.c index b190de58bc..f6d196132f 100644 --- a/src/sbbs3/nopen.c +++ b/src/sbbs3/nopen.c @@ -109,7 +109,7 @@ bool ftouch(const char* fname) return true; } -bool fmutex(const char* fname, const char* text, long max_age) +bool fmutex(const char* fname, const char* text, long max_age, time_t* tp) { int file; time_t t; @@ -120,9 +120,14 @@ bool fmutex(const char* fname, const char* text, long max_age) text=hostname; #endif - if(max_age && (t=fdate(fname)) >= 0 && (time(NULL)-t) > max_age) { - if(remove(fname)!=0) - return false; + if(max_age > 0 || tp != NULL) { + if(tp == NULL) + tp = &t; + *tp = fdate(fname); + if(max_age > 0 && *tp != -1 && (time(NULL) - *tp) > max_age) { + if(remove(fname)!=0) + return false; + } } if((file=open(fname,O_CREAT|O_WRONLY|O_EXCL,DEFFILEMODE))<0) return false; diff --git a/src/sbbs3/nopen.h b/src/sbbs3/nopen.h index 9306406704..cbdd4a7cb8 100644 --- a/src/sbbs3/nopen.h +++ b/src/sbbs3/nopen.h @@ -36,7 +36,7 @@ extern "C" { int nopen(const char* str, uint access); FILE * fnopen(int* file, const char* str, uint access); bool ftouch(const char* fname); -bool fmutex(const char* fname, const char* text, long max_age); +bool fmutex(const char* fname, const char* text, long max_age, time_t*); bool fcompare(const char* fn1, const char* fn2); bool backup(const char* org, int backup_level, bool ren); diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c index 12f9efc124..6a0ea1591f 100644 --- a/src/sbbs3/sbbsecho.c +++ b/src/sbbs3/sbbsecho.c @@ -685,9 +685,11 @@ bool bso_lock_node(fidoaddr_t dest) if(strListFind(locked_bso_nodes, fname, /* case_sensitive: */true) >= 0) return true; for(unsigned attempt=0;;) { - if(fmutex(fname, program_id(), cfg.bsy_timeout)) + char tmp[128]; + time_t t; + if(fmutex(fname, program_id(), cfg.bsy_timeout, &t)) break; - lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s", smb_faddrtoa(&dest, NULL), fname); + lprintf(LOG_NOTICE, "Node (%s) externally locked via: %s (since %s)", smb_faddrtoa(&dest, NULL), fname, time_as_hhmm(&scfg, t, tmp)); if(++attempt >= cfg.bso_lock_attempts) { lprintf(LOG_WARNING, "Giving up after %u attempts to lock node %s", attempt, smb_faddrtoa(&dest, NULL)); return false; @@ -6428,8 +6430,9 @@ int main(int argc, char **argv) } SAFEPRINTF(path,"%ssbbsecho.bsy", scfg.ctrl_dir); - if(!fmutex(path, program_id(), cfg.bsy_timeout)) { - lprintf(LOG_WARNING, "Mutex file exists (%s): SBBSecho appears to be already running", path); + time_t t; + if(!fmutex(path, program_id(), cfg.bsy_timeout, &t)) { + lprintf(LOG_WARNING, "Mutex file exists (%s): SBBSecho appears to be already running since %s", path, time_as_hhmm(&scfg, t, str)); bail(1); } mtxfile_locked = true; -- GitLab