diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7b62d930629dc16d70e761d8b67c683a9ba3ec7 --- /dev/null +++ b/src/sbbs3/main.cpp @@ -0,0 +1,3063 @@ +/* main.cpp */ + +/* Synchronet main/telnet server thread and related functions */ + +/* $Id$ */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright 2000 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 * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * See the GNU General Public License for more details: gpl.txt or * + * http://www.fsf.org/copyleft/gpl.html * + * * + * Anonymous FTP access to the most recent released source is available at * + * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * + * * + * Anonymous CVS access to the development source and modification history * + * is available at cvs.synchro.net:/cvsroot/sbbs, example: * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * + * (just hit return, no password is necessary) * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * You are encouraged to submit any modifications (preferably in Unix diff * + * format) via e-mail to mods@synchro.net * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#ifdef __unix__ + #include <signal.h> /* do we need bsd/signal on Linux? */ +#endif + +#include "sbbs.h" +#include "telnet.h" +#include "bbs_thrd.h" + +//--------------------------------------------------------------------------- + +/* Temporary */ +int mswtyp=0; +uint riobp; + +#define STATUS_WFC "Listening" + +#define TIMEOUT_THREAD_WAIT 30 // Seconds +#define IO_THREAD_BUF_SIZE 10000 // Bytes + +// Globals +#ifdef _WIN32 +HANDLE exec_mutex; +#endif + +static uint node_threads_running=0; + +RingBuf* node_inbuf[MAX_NODES]; +SOCKET spy_socket[MAX_NODES]; +SOCKET node_socket[MAX_NODES]; +static SOCKET telnet_socket=INVALID_SOCKET; +static SOCKET rlogin_socket=INVALID_SOCKET; +static pthread_mutex_t event_mutex; +static sbbs_t* sbbs=NULL; +static scfg_t scfg; +static bool scfg_reloaded=true; +static char * text[TOTAL_TEXT]; + +#ifdef _WINSOCKAPI_ + +WSADATA WSAData; +static BOOL WSAInitialized=FALSE; + +static BOOL winsock_startup(void) +{ + int status; /* Status Code */ + + if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0) { + lprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus); + WSAInitialized=TRUE; + return(TRUE); + } + + lprintf("!WinSock startup ERROR %d", status); + return(FALSE); +} + +#else /* No WINSOCK */ + +#define winsock_startup() (TRUE) + +#endif + +extern "C" { + +static bbs_startup_t* startup=NULL; + +static void status(char* str) +{ + if(startup!=NULL && startup->status!=NULL) + startup->status(str); +} + +static void update_clients() +{ + if(startup!=NULL && startup->clients!=NULL) + startup->clients(node_threads_running); +} + +void client_on(SOCKET sock, client_t* client) +{ + if(startup!=NULL && startup->client_on!=NULL) + startup->client_on(TRUE,sock,client); +} + +static void client_off(SOCKET sock) +{ + if(startup!=NULL && startup->client_on!=NULL) + startup->client_on(FALSE,sock,NULL); +} + +static void thread_up() +{ + if(startup!=NULL && startup->thread_up!=NULL) + startup->thread_up(TRUE); +} + +static void thread_down() +{ + if(startup!=NULL && startup->thread_up!=NULL) + startup->thread_up(FALSE); +} + +SOCKET open_socket(int type) +{ + SOCKET sock; + + sock=socket(AF_INET, type, IPPROTO_IP); + if(sock!=INVALID_SOCKET && startup!=NULL && startup->socket_open!=NULL) + startup->socket_open(TRUE); + return(sock); +} + +int close_socket(SOCKET sock) +{ + int result; + + shutdown(sock,SHUT_RDWR); /* required on Unix */ + result=closesocket(sock); + if(/* result==0 && */ startup!=NULL && startup->socket_open!=NULL) + startup->socket_open(FALSE); + if(result!=0) + lprintf("!ERROR %d closing socket %d",ERROR_VALUE,sock); + return(result); +} + +int lputs(char* str) +{ + if(startup==NULL || startup->lputs==NULL) + return(0); + return(startup->lputs(str)); +} + +int lprintf(char *fmt, ...) +{ + va_list argptr; + char sbuf[1024]; + + if(startup==NULL || startup->lputs==NULL) + return(0); + va_start(argptr,fmt); + vsprintf(sbuf,fmt,argptr); + va_end(argptr); + return(startup->lputs(sbuf)); +} + +} /* extern "C" */ + + +BYTE* telnet_interpret(sbbs_t* sbbs, BYTE* inbuf, int inlen, + BYTE* outbuf, int& outlen) +{ + char str[32]; + BYTE* first_iac=NULL; + BYTE* first_cr=NULL; + int i; + + + first_iac=(BYTE*)memchr(inbuf, TELNET_IAC, inlen); + + if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) + && !(sbbs->console&CON_RAW_IN)) { + if(sbbs->telnet_last_rxch==CR) + first_cr=inbuf; + else + first_cr=(BYTE*)memchr(inbuf, CR, inlen); + } + + if(!sbbs->telnet_cmdlen && first_iac==NULL && first_cr==NULL) { + outlen=inlen; + return(inbuf); // no interpretation needed + } + + if(first_iac!=NULL || first_cr!=NULL) { + if(first_iac!=NULL && (first_cr==NULL || first_iac<first_cr)) + outlen=first_iac-inbuf; + else + outlen=first_cr-inbuf; + memcpy(outbuf, inbuf, outlen); + } else + outlen=0; + + for(i=outlen;i<inlen;i++) { + if(!(sbbs->telnet_mode&(TELNET_MODE_BIN_RX|TELNET_MODE_GATE)) + && !(sbbs->console&CON_RAW_IN)) { + if(sbbs->telnet_last_rxch==CR + && (inbuf[i]==LF || inbuf[i]==0)) { // CR/LF or CR/NUL, ignore 2nd char + sbbs->telnet_last_rxch=inbuf[i]; + continue; + } + sbbs->telnet_last_rxch=inbuf[i]; + } + + if(inbuf[i]==TELNET_IAC && sbbs->telnet_cmdlen==1) { /* escaped 255 */ + sbbs->telnet_cmdlen=0; + outbuf[outlen++]=TELNET_IAC; + continue; + } + if(inbuf[i]==TELNET_IAC || sbbs->telnet_cmdlen) { + sbbs->telnet_cmd[sbbs->telnet_cmdlen++]=inbuf[i]; + if(sbbs->telnet_cmdlen==2 && inbuf[i]<TELNET_WILL) { + if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf("Node %d %s telnet command: %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,telnet_cmd_desc(sbbs->telnet_cmd[2])); + sbbs->telnet_cmdlen=0; + } + else if(sbbs->telnet_cmdlen>=3) { + if(sbbs->telnet_cmd[2]==TELNET_BINARY) { + if(sbbs->telnet_cmd[1]==TELNET_WILL) + sbbs->telnet_mode|=TELNET_MODE_BIN_RX; + else if(sbbs->telnet_cmd[1]==TELNET_WONT) + sbbs->telnet_mode&=~TELNET_MODE_BIN_RX; + } + if(sbbs->telnet_cmd[2]==TELNET_ECHO) { + if(sbbs->telnet_cmd[1]==TELNET_DO) + sbbs->telnet_mode|=TELNET_MODE_ECHO; + else if(sbbs->telnet_cmd[1]==TELNET_DONT) { + sbbs->telnet_mode&=~TELNET_MODE_ECHO; + if(!(sbbs->telnet_mode&TELNET_MODE_GATE)) { + sprintf(str,"%c%c%c",TELNET_IAC,TELNET_WILL,TELNET_ECHO); + sbbs->putcom(str,3); + } + } + } + if(startup->options&BBS_OPT_DEBUG_TELNET) + lprintf("Node %d %s telnet command: %s %s" + ,sbbs->cfg.node_num + ,sbbs->telnet_mode&TELNET_MODE_GATE ? "passed-through" : "received" + ,telnet_cmd_desc(sbbs->telnet_cmd[1]) + ,telnet_opt_desc(sbbs->telnet_cmd[2])); + sbbs->telnet_cmdlen=0; + } + if(sbbs->telnet_mode&TELNET_MODE_GATE) // Pass-through commads + outbuf[outlen++]=inbuf[i]; + } else + outbuf[outlen++]=inbuf[i]; + } + return(outbuf); +} + +void input_thread(void *arg) +{ + BYTE inbuf[4000]; + BYTE telbuf[sizeof(inbuf)]; + BYTE *wrbuf; + int i,rd,wr,avail; + ulong total_recv=0; + fd_set socket_set; + sbbs_t* sbbs = (sbbs_t*) arg; + struct timeval tv; + + thread_up(); + + lprintf("Node %d input thread started",sbbs->cfg.node_num); + + pthread_mutex_init(&sbbs->input_thread_mutex,NULL); + sbbs->input_thread_running = true; + sbbs->console|=CON_R_INPUT; + + while(sbbs->online) { + + pthread_mutex_lock(&sbbs->input_thread_mutex); + + FD_ZERO(&socket_set); + FD_SET(sbbs->client_socket,&socket_set); + + tv.tv_sec=1; + tv.tv_usec=0; + + if((i=select(sbbs->client_socket+1,&socket_set,NULL,NULL,&tv))<1) { + pthread_mutex_unlock(&sbbs->input_thread_mutex); + if(i==0) { + mswait(1); + continue; + } + + if(ERROR_VALUE == ENOTSOCK) + lprintf("Node %d socket closed by peer on input->select", sbbs->cfg.node_num); + else if(ERROR_VALUE==EINTR) + lprintf("Node %d input_thread interrupted",sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNRESET) + lprintf("Node %d connection reset by peer on input->select", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNABORTED) + lprintf("Node %d connection aborted by peer on input->select", sbbs->cfg.node_num); + else + lprintf("!Node %d: ERROR %d input->select socket %d" + ,sbbs->cfg.node_num, ERROR_VALUE, sbbs->client_socket); + sbbs->online=0; + break; + } + + rd=RingBufFree(&sbbs->inbuf); + + if(!rd) { // input buffer full + // wait up to 5 seconds to empty (1 byte min) + time_t start=time(NULL); + while((rd=RingBufFree(&sbbs->inbuf))==0) { + if(time(NULL)-start>=5) { + rd=1; + break; + } + mswait(1); + } + } + + if(rd > (int)sizeof(inbuf)) + rd=sizeof(inbuf); + + rd = recv(sbbs->client_socket, (char*)inbuf, rd, 0); + + pthread_mutex_unlock(&sbbs->input_thread_mutex); + + if(rd == SOCKET_ERROR) + { + if(ERROR_VALUE == ENOTSOCK) + lprintf("Node %d socket closed by peer on receive", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNRESET) + lprintf("Node %d connection reset by peer on receive", sbbs->cfg.node_num); + else if(ERROR_VALUE==ECONNABORTED) + lprintf("Node %d connection aborted by peer on receive", sbbs->cfg.node_num); + else + lprintf("!Node %d: ERROR %d receiving from socket %d" + ,sbbs->cfg.node_num, ERROR_VALUE, sbbs->client_socket); + sbbs->online=0; + break; + } + + if(rd == 0) + { + lprintf("Node %d disconnected", sbbs->cfg.node_num); + sbbs->online=0; + break; + } + + total_recv+=rd; + + // telbuf and wr are modified to reflect telnet escaped data + wrbuf=telnet_interpret(sbbs, inbuf, rd, telbuf, wr); + if(wr > (int)sizeof(telbuf)) + lprintf("!TELBUF OVERFLOW (%d>%d)",wr,sizeof(telbuf)); + + avail=RingBufFree(&sbbs->inbuf); + + if(avail<wr) + lprintf("!INPUT BUFFER FULL (%d free)", avail); + else + RingBufWrite(&sbbs->inbuf, wrbuf, wr); +// if(wr>100) +// mswait(500); // Throttle sender + } + + sbbs->input_thread_running = false; + + pthread_mutex_destroy(&sbbs->input_thread_mutex); + + lprintf("Node %d input thread terminated (total bytes received: %lu)" + ,sbbs->cfg.node_num, total_recv); + + thread_down(); +} + +void output_thread(void* arg) +{ + char node[128]; + BYTE buf[IO_THREAD_BUF_SIZE]; + int i; + ulong avail; + ulong total_sent=0; + ulong bufbot=0; + ulong buftop=0; + sbbs_t* sbbs = (sbbs_t*) arg; + + thread_up(); + + if(sbbs->cfg.node_num) + sprintf(node,"Node %d",sbbs->cfg.node_num); + else + strcpy(node,sbbs->client_name); + lprintf("%s output thread started",node); + + sbbs->output_thread_running = true; + sbbs->console|=CON_R_ECHO; + + while(sbbs->client_socket!=INVALID_SOCKET && telnet_socket!=INVALID_SOCKET) { + if(bufbot==buftop) + avail=RingBufFull(&sbbs->outbuf); + else + avail=buftop-bufbot; + + if(!avail) { + sem_init(&sbbs->output_sem,0,0); + sem_wait(&sbbs->output_sem); + continue; } + + if(bufbot==buftop) { // linear buf empty, read from ring buf + if(avail>sizeof(buf)) { + lprintf("Reducing output buffer"); + avail=sizeof(buf); + } + buftop=RingBufRead(&sbbs->outbuf, buf, avail); + bufbot=0; + } + i = send(sbbs->client_socket, (char*)buf+bufbot, buftop-bufbot, 0); + if(i == SOCKET_ERROR) + { + if(ERROR_VALUE == ENOTSOCK) + lprintf("%s client socket closed on send", node); + else if(ERROR_VALUE==ECONNRESET) + lprintf("%s connection reset by peer on send", node); + else if(ERROR_VALUE==ECONNABORTED) + lprintf("%s connection aborted by peer on send", node); + else + lprintf("!%s: ERROR %d sending on socket %d" + ,node, ERROR_VALUE, sbbs->client_socket); + sbbs->online=0; + /* was break; on 4/7/00 */ + i=buftop-bufbot; // Pretend we sent it all + } + + if(sbbs->cfg.node_num && !(sbbs->sys_status&SS_FILEXFER)) { + /* Spy on the user locally */ + if(startup->node_spybuf!=NULL + && startup->node_spybuf[sbbs->cfg.node_num-1]!=NULL) + RingBufWrite(startup->node_spybuf[sbbs->cfg.node_num-1],buf+bufbot,i); + + /* Spy on the user remotely */ + if(spy_socket[sbbs->cfg.node_num-1]!=INVALID_SOCKET) + send(spy_socket[sbbs->cfg.node_num-1],(char*)buf+bufbot,i,0); + } + + bufbot+=i; + total_sent+=i; + } + + sbbs->spymsg("Disconnected"); + + sbbs->output_thread_running = false; + + lprintf("%s output thread terminated (total bytes sent: %lu)", node, total_sent); + + thread_down(); +} + +void event_thread(void* arg) +{ + char str[256]; + char semfile[128]; + int i,j,k; + int file; + int offset; + ulong l; + time_t now; + struct tm now_tm; + struct tm* tm; + time_t lastnodechk; + time_t lastprepack=0; + node_t node; + glob_t g; + sbbs_t* sbbs = (sbbs_t*) arg; + + lprintf("BBS Events thread started"); + + sbbs->event_thread_running = true; + + thread_up(); + + while(1) { + + pthread_mutex_lock(&event_mutex); + + if(sbbs->terminated || telnet_socket==INVALID_SOCKET) + break; + + if(scfg_reloaded==true) { + + for(i=0;i<TOTAL_TEXT;i++) + sbbs->text[i]=sbbs->text_sav[i]=text[i]; + + memcpy(&sbbs->cfg,&scfg,sizeof(scfg_t)); + + prep_dir(sbbs->cfg.data_dir, sbbs->cfg.temp_dir); + + // Read TIME.DAB + sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,0); + return; + } + for(i=0;i<sbbs->cfg.total_events;i++) { + sbbs->cfg.event[i]->last=0; + if(filelength(file)<(long)(sizeof(time_t)*(i+1))) + write(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); + else + read(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); + } + read(file,&lastprepack,sizeof(time_t)); + close(file); + + // Read QNET.DAB + sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,0); + return; + } + for(i=0;i<sbbs->cfg.total_qhubs;i++) { + sbbs->cfg.qhub[i]->last=0; + if(filelength(file)<(long)(sizeof(time_t)*(i+1))) + write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); + else + read(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); + } + close(file); + + // Read PNET.DAB + sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_RDWR|O_CREAT))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,0); + return; + } + for(i=0;i<sbbs->cfg.total_phubs;i++) { + sbbs->cfg.phub[i]->last=0; + if(filelength(file)<(long)(sizeof(time_t)*(i+1))) + write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t)); + else + read(file,&sbbs->cfg.phub[i]->last,sizeof(time_t)); + } + close(file); + + scfg_reloaded=false; + } + + now=time(NULL); + now_tm=*gmtime(&now); + + /* QWK events */ + if(!(startup->options&BBS_OPT_NO_QWK_EVENTS)) { + /* Import any REP files that have magically appeared (via FTP perhaps) */ + sprintf(str,"%sfile/",sbbs->cfg.data_dir); + offset=strlen(str); + strcat(str,"*.rep"); + glob(str,0,NULL,&g); + for(i=0;i<(int)g.gl_pathc;i++) { + sbbs->useron.number=atoi(g.gl_pathv[i]+offset); + getuserdat(&sbbs->cfg,&sbbs->useron); + if(sbbs->useron.number && flength(g.gl_pathv[i])>0) { + sbbs->online=ON_LOCAL; + lprintf("Un-packing QWK Reply packet from %s",sbbs->useron.alias); + sbbs->getusrsubs(); + sbbs->unpack_rep(g.gl_pathv[i]); + /* putuserdat? */ + remove(g.gl_pathv[i]); + } + } + globfree(&g); + + /* Create any QWK files that have magically appeared (via FTP perhaps) */ + sprintf(str,"%spack*.now",sbbs->cfg.data_dir); + offset=strlen(sbbs->cfg.data_dir)+4; + glob(str,0,NULL,&g); + for(i=0;i<(int)g.gl_pathc;i++) { + sbbs->useron.number=atoi(g.gl_pathv[i]+offset); + getuserdat(&sbbs->cfg,&sbbs->useron); + if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) { + lprintf("Packing QWK Message Packet for %s",sbbs->useron.alias); + sbbs->online=ON_LOCAL; + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + sbbs->getmsgptrs(); + sbbs->getusrsubs(); + sbbs->batdn_total=0; + sprintf(str,"%sfile%c%04u.qwk" + ,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number); + if(sbbs->pack_qwk(str,&l,1)) { + lprintf("Packing completed"); + sbbs->qwk_success(l,0,1); + sbbs->putmsgptrs(); + } else + lprintf("No packet created (no new messages)"); + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + sbbs->online=0; + } + remove(g.gl_pathv[i]); + } + globfree(&g); + + /* Create (pre-pack) QWK files for users configured as such */ + sprintf(semfile,"%sprepack.now",sbbs->cfg.data_dir); + if(sbbs->cfg.preqwk_ar[0] + && (fexist(semfile) || (now-lastprepack)/60>(60*24))) { + j=lastuser(&sbbs->cfg); + lprintf("Pre-packing QWK Message packets..."); + for(i=1;i<=j;i++) { + + sprintf(str,"%5u of %-5u",i,j); + status(str); + sbbs->useron.number=i; + getuserdat(&sbbs->cfg,&sbbs->useron); + + if(sbbs->useron.number + && !(sbbs->useron.misc&(DELETED|INACTIVE)) /* Pre-QWK */ + && sbbs->chk_ar(sbbs->cfg.preqwk_ar,&sbbs->useron)) { + for(k=1;k<=sbbs->cfg.sys_nodes;k++) { + sbbs->getnodedat(k,&node,0); + if((node.status==NODE_INUSE || node.status==NODE_QUIET + || node.status==NODE_LOGON) && node.useron==i) + break; + } + if(k<=sbbs->cfg.sys_nodes) /* Don't pre-pack with user online */ + continue; + lprintf("Pre-packing QWK for %s",sbbs->useron.alias); + sbbs->online=ON_LOCAL; + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + sbbs->getmsgptrs(); + sbbs->getusrsubs(); + sbbs->batdn_total=0; + sprintf(str,"%sfile%c%04u.qwk" + ,sbbs->cfg.data_dir,BACKSLASH,sbbs->useron.number); + if(sbbs->pack_qwk(str,&l,1)) { + sbbs->qwk_success(l,0,1); + sbbs->putmsgptrs(); + } + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + sbbs->online=0; + } + } + lastprepack=now; + sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_WRONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY); + break; } + lseek(file,(long)sbbs->cfg.total_events*4L,SEEK_SET); + write(file,&lastprepack,sizeof(time_t)); + close(file); + + remove(semfile); + status(STATUS_WFC); + } + } + + /* Node Daily Events */ + for(i=startup->first_node;i<=startup->last_node;i++) { + // Node Daily Event + sbbs->getnodedat(i,&node,0); + if(node.misc&NODE_EVENT && node.status==NODE_WFC) { + sbbs->getnodedat(i,&node,1); + node.status=NODE_EVENT_RUNNING; + sbbs->putnodedat(i,&node); + if(sbbs->cfg.node_daily[0]) { + sbbs->cfg.node_num=i; + strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[i-1]); + + status("Running node daily event"); + sbbs->logentry("!:","Run node daily event"); + sbbs->external( + sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL) + ,EX_OFFLINE); + status(STATUS_WFC); + } + sbbs->getnodedat(i,&node,1); + node.misc&=~NODE_EVENT; + node.status=NODE_WFC; + node.useron=0; + sbbs->putnodedat(i,&node); + } + } + + /* QWK Networking Call-out sempahores */ + for(i=0;i<sbbs->cfg.total_qhubs;i++) + if(sbbs->cfg.qhub[i]->node>=startup->first_node + && sbbs->cfg.qhub[i]->node<=startup->last_node) { + sprintf(str,"%sqnet/%s.now",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id); + if(fexist(str)) + sbbs->cfg.qhub[i]->last=-1; + } + + /* Timed Event sempahores */ + for(i=0;i<sbbs->cfg.total_events;i++) + if((sbbs->cfg.event[i]->node>=startup->first_node + && sbbs->cfg.event[i]->node<=startup->last_node) + || sbbs->cfg.event[i]->misc&EVENT_EXCL) { + sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code); + if(fexist(str)) + sbbs->cfg.event[i]->last=-1; + } + + /* QWK Networking Call-out Events */ + for(i=0;i<sbbs->cfg.total_qhubs;i++) { + if(sbbs->cfg.qhub[i]->node<startup->first_node || + sbbs->cfg.qhub[i]->node>startup->last_node) + continue; + + // See if any packets have come in + for(j=0;j<101;j++) { + sprintf(str,"%s%s.q%c%c",sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id + ,j>10 ? ((j-1)/10)+'0' : 'w' + ,j ? ((j-1)%10)+'0' : 'k'); + if(fexist(str)) { + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + if(sbbs->unpack_qwk(str,i)==false) { + char newname[MAX_PATH+1]; + sprintf(newname,"%s.%lx.bad",str,now); + remove(newname); + if(rename(str,newname)==0) { + char logmsg[MAX_PATH*3]; + sprintf(logmsg,"%s renamed to %s",str,newname); + sbbs->logline("Q!",logmsg); + } + } + remove(str); + } + } + + tm=gmtime(&sbbs->cfg.qhub[i]->last); /* Qnet call out based on time */ + if((tm==NULL || sbbs->cfg.qhub[i]->last==-1L /* or frequency */ + || ((sbbs->cfg.qhub[i]->freq + && (now-sbbs->cfg.qhub[i]->last)/60>sbbs->cfg.qhub[i]->freq) + || (sbbs->cfg.qhub[i]->time + && (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.qhub[i]->time + && (now_tm.tm_mday!=tm->tm_mday || now_tm.tm_mon!=tm->tm_mon))) + && sbbs->cfg.qhub[i]->days&(1<<now_tm.tm_wday))) { + sprintf(str,"%sqnet/%s.now" + ,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id); + remove(str); /* Remove semaphore file */ + sprintf(str,"%sqnet/%s.ptr" + ,sbbs->cfg.data_dir,sbbs->cfg.qhub[i]->id); + file=sbbs->nopen(str,O_RDONLY); + for(j=0;j<sbbs->cfg.qhub[i]->subs;j++) { + sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]]=0; + lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L,SEEK_SET); + read(file,&sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]],4); + } + if(file!=-1) + close(file); + if(sbbs->pack_rep(i)) { + if((file=sbbs->nopen(str,O_WRONLY|O_CREAT))==-1) + sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_CREAT); + else { + for(j=l=0;j<sbbs->cfg.qhub[i]->subs;j++) { + while(filelength(file)< + sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L) + write(file,&l,4); /* initialize ptrs to null */ + lseek(file,sbbs->cfg.sub[sbbs->cfg.qhub[i]->sub[j]]->ptridx*4L,SEEK_SET); + write(file,&sbbs->sub_ptr[sbbs->cfg.qhub[i]->sub[j]],4); } + close(file); } } + sbbs->delfiles(sbbs->cfg.temp_dir,ALLFILES); + + sbbs->cfg.qhub[i]->last=time(NULL); + sprintf(str,"%sqnet.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_WRONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY); + break; + } + lseek(file,sizeof(time_t)*i,SEEK_SET); + write(file,&sbbs->cfg.qhub[i]->last,sizeof(time_t)); + close(file); + + if(sbbs->cfg.qhub[i]->call[0]) { + sbbs->cfg.node_num=sbbs->cfg.qhub[i]->node; + strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]); + status("QWK Networking"); +#if 0 + sbbs->getnodedat(sbbs->cfg.qhub[i]->node,&node,1); + node.status=NODE_NETTING; + sbbs->putnodedat(sbbs->cfg.qhub[i]->node,&node); +#endif + sbbs->external( + sbbs->cmdstr(sbbs->cfg.qhub[i]->call,nulstr,nulstr,NULL) + ,EX_OFFLINE); + status(STATUS_WFC); + } + } + } + + /* PostLink Networking Call-out Events */ + for(i=0;i<sbbs->cfg.total_phubs;i++) { + if(sbbs->cfg.phub[i]->node<startup->first_node + || sbbs->cfg.phub[i]->node>startup->last_node) + continue; + tm=localtime(&sbbs->cfg.phub[i]->last); /* PostLink call out based on time */ + if(tm==NULL) + continue; + if(((sbbs->cfg.phub[i]->freq /* or frequency */ + && (now-sbbs->cfg.phub[i]->last)/60>sbbs->cfg.phub[i]->freq) + || (sbbs->cfg.phub[i]->time + && (now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.phub[i]->time + && (now_tm.tm_mday!=tm->tm_mday || now_tm.tm_mon!=tm->tm_mon))) + && sbbs->cfg.phub[i]->days&(1<<now_tm.tm_wday)) { + + sbbs->cfg.phub[i]->last=time(NULL); + sprintf(str,"%spnet.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_WRONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY); + break; + } + lseek(file,sizeof(time_t)*i,SEEK_SET); + write(file,&sbbs->cfg.phub[i]->last,sizeof(time_t)); + close(file); + + if(sbbs->cfg.phub[i]->call[0]) { + sbbs->cfg.node_num=sbbs->cfg.event[i]->node; + strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]); + status("PostLink Networking"); +#if 0 + sbbs->getnodedat(sbbs->cfg.phub[i]->node,&node,1); + node.status=NODE_NETTING; + sbbs->putnodedat(sbbs->cfg.phub[i]->node,&node); +#endif + sbbs->external( + sbbs->cmdstr(sbbs->cfg.phub[i]->call,nulstr,nulstr,NULL) + ,EX_OFFLINE); + status(STATUS_WFC); + } + } + } + + /* Timed Events */ + for(i=0;i<sbbs->cfg.total_events;i++) { + if(!sbbs->cfg.event[i]->node || sbbs->cfg.event[i]->node>sbbs->cfg.sys_nodes) + continue; + tm=gmtime(&sbbs->cfg.event[i]->last); + if(tm==NULL || sbbs->cfg.event[i]->last==-1 + || ((now_tm.tm_hour*60)+now_tm.tm_min>=sbbs->cfg.event[i]->time + && (now_tm.tm_mday!=tm->tm_mday || now_tm.tm_mon!=tm->tm_mon) + && sbbs->cfg.event[i]->days&(1<<now_tm.tm_wday))) { + + if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */ + + if(sbbs->cfg.event[i]->node<startup->first_node + || sbbs->cfg.event[i]->node>startup->last_node) { + sprintf(str,"Waiting for node %d to run timed event." + ,sbbs->cfg.event[i]->node); + status(str); + lastnodechk=0; /* really last event time check */ + while(!sbbs->terminated) { + mswait(1000); + now=time(NULL); + if(now-lastnodechk<10) + continue; + for(j=startup->first_node;j<=startup->last_node;j++) { + sbbs->getnodedat(j,&node,1); + if(node.status==NODE_WFC) + node.status=NODE_EVENT_LIMBO; + node.aux=sbbs->cfg.event[i]->node; + sbbs->putnodedat(j,&node); + } + +#if 0 + getnodedat(node_num,&thisnode,0); + if(thisnode.misc&NODE_DOWN) + return(0); +#endif + lastnodechk=now; + sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_RDONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY); + sbbs->cfg.event[i]->last=now; + continue; } + lseek(file,(long)i*4L,SEEK_SET); + read(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); + close(file); + if(now-sbbs->cfg.event[i]->last<(60*60)) /* event is done */ + break; } + sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code); + remove(str); + sbbs->cfg.event[i]->last=now; + } else { // Exclusive event to run on a node under our control + status("Waiting for all nodes to become inactive before " + "running timed event."); + lastnodechk=0; + while(!sbbs->terminated) { + mswait(1000); + now=time(NULL); + if(now-lastnodechk<10) + continue; + lastnodechk=now; +#if 0 + getnodedat(node_num,&thisnode,0); + if(thisnode.misc&NODE_DOWN) + return(0); +#endif + // Check/change the status of the nodes that we're in control of + for(j=startup->first_node;j<=startup->last_node;j++) { + sbbs->getnodedat(j,&node,1); + if(node.status==NODE_WFC) { + if(j==sbbs->cfg.event[i]->node) + node.status=NODE_EVENT_WAITING; + else + node.status=NODE_EVENT_LIMBO; + node.aux=sbbs->cfg.event[i]->node; + } + sbbs->putnodedat(j,&node); + } + + for(j=1;j<=sbbs->cfg.sys_nodes;j++) { + sbbs->getnodedat(j,&node,0); + if(j==sbbs->cfg.event[i]->node) { + if(node.status!=NODE_EVENT_WAITING) + break; + } else { + if(node.status!=NODE_OFFLINE + && node.status!=NODE_EVENT_LIMBO) + break; + } + } + if(j>sbbs->cfg.sys_nodes) /* all nodes either offline or in limbo */ + break; + sprintf(str,"Waiting for node %d (status=%d)" + ,j,node.status); + status(str); + } + } + } + else { /* non-exclusive */ + sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,0); + if(node.status!=NODE_WFC) + continue; + } + if(sbbs->cfg.event[i]->node<startup->first_node + || sbbs->cfg.event[i]->node>startup->last_node) { + sbbs->cfg.event[i]->last=now; + for(j=startup->first_node;j<=startup->last_node;j++) { + sbbs->getnodedat(j,&node,1); + node.status=NODE_WFC; + sbbs->putnodedat(j,&node); + } + } + else { + sbbs->cfg.node_num=sbbs->cfg.event[i]->node; + strcpy(sbbs->cfg.node_dir, sbbs->cfg.node_path[sbbs->cfg.node_num-1]); + + sprintf(str,"%s%s.now",sbbs->cfg.data_dir,sbbs->cfg.event[i]->code); + remove(str); + if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { + sbbs->getnodedat(sbbs->cfg.event[i]->node,&node,1); + node.status=NODE_EVENT_RUNNING; + sbbs->putnodedat(sbbs->cfg.event[i]->node,&node); + } + lprintf("Running event: %s",sbbs->cfg.event[i]->code); + sbbs->external( + sbbs->cmdstr(sbbs->cfg.event[i]->cmd,nulstr,nulstr,NULL) + ,EX_OFFLINE + ,sbbs->cfg.event[i]->dir); + sbbs->cfg.event[i]->last=time(NULL); + sprintf(str,"%stime.dab",sbbs->cfg.ctrl_dir); + if((file=sbbs->nopen(str,O_WRONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_WRONLY); + break; } + lseek(file,(long)i*4L,SEEK_SET); + write(file,&sbbs->cfg.event[i]->last,sizeof(time_t)); + close(file); + + if(sbbs->cfg.event[i]->misc&EVENT_EXCL) { /* exclusive event */ + // Check/change the status of the nodes that we're in control of + for(j=startup->first_node;j<=startup->last_node;j++) { + sbbs->getnodedat(j,&node,1); + node.status=NODE_WFC; + sbbs->putnodedat(j,&node); + } + } + } + status(STATUS_WFC); + } + } + pthread_mutex_unlock(&event_mutex); + + mswait(1000); + } + sbbs->event_thread_running = false; + + lprintf("BBS Event thread terminated"); + + thread_down(); + +} + + +//**************************************************************************** +sbbs_t::sbbs_t(ushort node_num, DWORD addr, char* name, SOCKET sd, + scfg_t* global_cfg, char* global_text[]) +{ + char nodestr[32]; + uint i; + + if(node_num) + sprintf(nodestr,"BBS Node %d",node_num); + else + strcpy(nodestr,name); + + lprintf("%s constructor using socket %d", nodestr, sd); + + memcpy(&cfg, global_cfg, sizeof(cfg)); + + cfg.node_num=node_num; + if(node_num) { + strcpy(cfg.node_dir, cfg.node_path[node_num-1]); + prep_dir(cfg.node_dir, cfg.temp_dir); + } else + prep_dir(cfg.data_dir, cfg.temp_dir); + + terminated = false; + event_thread_running = false; + input_thread_running = false; + output_thread_running = false; + + client_addr = addr; + client_socket = sd; + sprintf(client_name, "%.*s", (int)sizeof(client_name)-1, name); + client_socket_dup=INVALID_SOCKET; + + /* Init some important variables */ + + rio_abortable=false; + + console = 0; + online = 0; + outchar_esc = 0; + nodemsg_inside = 0; /* allows single nest */ + nodesync_inside = false; + errorlog_inside = false; + errormsg_inside = false; + gettimeleft_inside = false; + uselect_total = 0; + lbuflen = 0; + connection="Telnet"; + telnet_cmdlen=0; + telnet_mode=0; + telnet_last_rxch=0; + strcpy(cap_fname,"CAPTURE.TXT"); + sys_status=lncntr=tos=criterrs=keybufbot=keybuftop=lbuflen=slcnt=0L; + debug=1; + curatr=LIGHTGRAY; + errorlevel=0; + logcol=1; + next_event=0; + lastuseron[0]=0; + logfile_fp=NULL; + nodefile=-1; + node_ext=-1; + nodefile_fp=NULL; + node_ext_fp=NULL; + + for(i=0;i<TOTAL_TEXT;i++) + text[i]=text_sav[i]=global_text[i]; + + memset(&main_csi,0,sizeof(main_csi)); + memset(&thisnode,0,sizeof(thisnode)); + memset(&useron,0,sizeof(useron)); + memset(&inbuf,0,sizeof(inbuf)); + memset(&outbuf,0,sizeof(outbuf)); + memset(&smb,0,sizeof(smb)); + + global_str_vars=0; + global_str_var=NULL; + global_str_var_name=NULL; + global_int_vars=0; + global_int_var=NULL; + global_int_var_name=NULL; + sysvar_li=0; + sysvar_pi=0; + + cursub=NULL; + usrgrp=NULL; + usrsubs=NULL; + usrsub=NULL; + + sub_cfg=NULL; + sub_ptr=NULL; + sub_last=NULL; + sav_sub_cfg=NULL; + sav_sub_ptr=NULL; + sav_sub_last=NULL; + + + curdir=NULL; + usrlib=NULL; + usrdirs=NULL; + usrdir=NULL; + + batup_desc=NULL; + batup_name=NULL; + batup_misc=NULL; + batup_dir=NULL; + batup_alt=NULL; + + batdn_name=NULL; + batdn_dir=NULL; + batdn_offset=NULL; + batdn_size=NULL; + batdn_alt=NULL; + batdn_cdt=NULL; + + spymsg("Connected"); +} + +//**************************************************************************** +bool sbbs_t::init() +{ + char str[MAX_PATH]; + int result; + uint i,j,k,l; + node_t node; + socklen_t addr_len; + SOCKADDR_IN addr; + + if(cfg.node_num) { + RingBufInit(&inbuf, IO_THREAD_BUF_SIZE); + node_inbuf[cfg.node_num-1]=&inbuf; + } + + RingBufInit(&outbuf, IO_THREAD_BUF_SIZE); + + if(cfg.node_num && client_socket!=INVALID_SOCKET) { + +#ifdef _WIN32 + if(!DuplicateHandle(GetCurrentProcess(), + (HANDLE)client_socket, + GetCurrentProcess(), + (HANDLE*)&client_socket_dup, + 0, + TRUE, // Inheritable + DUPLICATE_SAME_ACCESS)) { + errormsg(WHERE,ERR_CREATE,"duplicate socket handle",client_socket); + return(false); + } +#endif + + addr_len=sizeof(addr); + if((result=getsockname(client_socket, (struct sockaddr *)&addr,&addr_len))!=0) { + lprintf("Node %d !ERROR %d (%d) getting address/port" + ,cfg.node_num, result, ERROR_VALUE); + return(false); + } + lprintf("Node %d attached to local interface %s port %d" + ,cfg.node_num, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + local_addr=addr.sin_addr.s_addr; + } + + comspec=getenv( +#ifdef __unix__ + "SHELL" +#else + "COMSPEC" +#endif + ); + if(comspec==NULL) { + errormsg(WHERE, ERR_CHK, "shell/comspec", 0); + return(false); + } + +#ifdef _WIN32 + output_sem=CreateEvent( + NULL // pointer to security attributes + ,true // flag for manual-reset event + ,false // flag for initial state + ,NULL // pointer to event-object name + ); + if(output_sem==NULL) { + errormsg(WHERE, ERR_CREATE, "output_sem", 0); + return(false); + } +#endif + sem_init(&output_sem,0,0); + + strcpy(str,cfg.temp_dir); + if(strcmp(str+1,":\\") && strcmp(str+1,":/")) /* not root directory */ + str[strlen(str)-1]=0; /* chop off '\' */ + md(str); + + /* Shared NODE files */ + sprintf(str,"%s%s",cfg.ctrl_dir,"node.dab"); + if((nodefile=nopen(str,O_DENYNONE|O_RDWR|O_CREAT))==-1) { + errormsg(WHERE, ERR_OPEN, str, cfg.node_num); + return(false); + } + memset(&node,0,sizeof(node_t)); /* write NULL to node struct */ + node.status=NODE_OFFLINE; + while(filelength(nodefile)<(long)(cfg.sys_nodes*sizeof(node_t))) { + lseek(nodefile,0L,SEEK_END); + write(nodefile,&node,sizeof(node_t)); + } + if(cfg.node_num) { + if(lock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t)) + || unlock(nodefile,(cfg.node_num-1)*sizeof(node_t),sizeof(node_t))) { + errormsg(WHERE, ERR_LOCK, str, cfg.node_num); + return(false); + } + } + close(nodefile); + nodefile=-1; + +#if 0 + sprintf(str,"%s%s",cfg.ctrl_dir,"node.exb"); + if((node_ext_fp=fnopen(&node_ext,str,O_DENYNONE|O_RDWR|O_CREAT))==NULL) { + errormsg(WHERE, ERR_FDOPEN, str, cfg.node_num); + return(false); + } +#endif + + if(cfg.node_num) { + sprintf(str,"%snode.log",cfg.node_dir); + if((logfile_fp=fopen(str,"a+b"))==NULL) { + errormsg(WHERE, ERR_OPEN, str, 0); + lprintf("Perhaps this node is already running"); + return(false); } + + if(filelength(fileno(logfile_fp))) { + log(crlf); + now=time(NULL); + struct tm * tm=localtime(&now); + if(tm!=NULL) + sprintf(str,"%02d:%02d%c %s %s %02d %u " + "End of preexisting log entry (possible crash)" + ,tm->tm_hour>12 ? tm->tm_hour-12 : tm->tm_hour==0 ? 12 : tm->tm_hour + ,tm->tm_min,tm->tm_hour>=12 ? 'p' : 'a',wday[tm->tm_wday] + ,mon[tm->tm_mon],tm->tm_mday,tm->tm_year+1900); + logline("L!",str); + log(crlf); + catsyslog(1); + } + + getnodedat(cfg.node_num,&thisnode,1); + /* thisnode.status=0; */ + thisnode.action=0; + thisnode.useron=0; + thisnode.aux=0; + thisnode.misc&=(NODE_EVENT|NODE_LOCK); + criterrs=thisnode.errors; + putnodedat(cfg.node_num,&thisnode); + } + +/** Put in if(cfg.node_num) ? (not needed for server and event threads) */ + backout(); + + /* Reset COMMAND SHELL */ + + main_csi.str=(char *)MALLOC(1024); + if(main_csi.str==NULL) { + errormsg(WHERE,ERR_ALLOC,"main_csi.str",1024); + return(false); + } + memset(main_csi.str,0,1024); +/***/ + + if(cfg.total_grps) { + + if((cursub=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { + errormsg(WHERE, ERR_ALLOC, "cursub", sizeof(uint)*cfg.total_grps); + return(false); + } + + if((usrgrp=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrgrp", sizeof(uint)*cfg.total_grps); + return(false); + } + + if((usrsubs=(uint *)MALLOC(sizeof(uint)*cfg.total_grps))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrsubs", sizeof(uint)*cfg.total_grps); + return(false); + } + + if((usrsub=(uint **)MALLOC(sizeof(uint *)*cfg.total_grps))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrsub", sizeof(uint)*cfg.total_grps); + return(false); + } + + if((sub_cfg=(ushort *)MALLOC(sizeof(ushort)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sub_cfg", sizeof(ushort)*cfg.total_subs); + return(false); + } + + if((sub_ptr=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sub_ptr", sizeof(ulong)*cfg.total_subs); + return(false); + } + + if((sub_last=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sub_last", sizeof(ulong)*cfg.total_subs); + return(false); + } + + if((sav_sub_cfg=(ushort *)MALLOC(sizeof(ushort)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sav_sub_cfg", sizeof(ushort)*cfg.total_subs); + return(false); + } + + if((sav_sub_ptr=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sav_sub_ptr", sizeof(ulong)*cfg.total_subs); + return(false); + } + + if((sav_sub_last=(ulong *)MALLOC(sizeof(ulong)*cfg.total_subs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "sav_sub_last", sizeof(ulong)*cfg.total_subs); + return(false); + } + + } + + + for(i=l=0;i<(uint)cfg.total_grps;i++) { + for(j=k=0;j<cfg.total_subs;j++) + if(cfg.sub[j]->grp==i) + k++; /* k = number of subs per grp[i] */ + if(k>l) l=k; /* l = the largest number of subs per grp */ + } + if(l) + for(i=0;i<cfg.total_grps;i++) + if((usrsub[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrsub[x]", sizeof(uint)*l); + return(false); + } + + if(cfg.total_libs) { + + if((curdir=(uint *)MALLOC(sizeof(uint)*cfg.total_libs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "curdir", sizeof(uint)*cfg.total_libs); + return(false); + } + + if((usrlib=(uint *)MALLOC(sizeof(uint)*cfg.total_libs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrlib", sizeof(uint)*cfg.total_libs); + return(false); + } + + if((usrdirs=(uint *)MALLOC(sizeof(uint)*cfg.total_libs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrdirs", sizeof(uint)*cfg.total_libs); + return(false); + } + + if((usrdir=(uint **)MALLOC(sizeof(uint *)*cfg.total_libs))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrdir", sizeof(uint)*cfg.total_libs); + return(false); + } + } + + for(i=l=0;i<cfg.total_libs;i++) { + for(j=k=0;j<cfg.total_dirs;j++) + if(cfg.dir[j]->lib==i) + k++; + if(k>l) l=k; /* l = largest number of dirs in a lib */ + } + if(l) { + l++; /* for temp dir */ + for(i=0;i<cfg.total_libs;i++) + if((usrdir[i]=(uint *)MALLOC(sizeof(uint)*l))==NULL) { + errormsg(WHERE, ERR_ALLOC, "usrdir[x]", sizeof(uint)*l); + return(false); + } + } + + if(cfg.max_batup) { + + if((batup_desc=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_desc", sizeof(char *)*cfg.max_batup); + return(false); + } + if((batup_name=(char **)MALLOC(sizeof(char *)*cfg.max_batup))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_name", sizeof(char *)*cfg.max_batup); + return(false); + } + if((batup_misc=(long *)MALLOC(sizeof(long)*cfg.max_batup))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_misc", sizeof(char *)*cfg.max_batup); + return(false); + } + if((batup_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batup))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_dir", sizeof(char *)*cfg.max_batup); + return(false); + } + if((batup_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batup))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_alt", sizeof(char *)*cfg.max_batup); + return(false); + } + for(i=0;i<cfg.max_batup;i++) { + if((batup_desc[i]=(char *)MALLOC(59))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_desc[x]", 59); + return(false); + } + if((batup_name[i]=(char *)MALLOC(13))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batup_name[x]", 13); + return(false); + } + } + } + + if(cfg.max_batdn) { + + if((batdn_name=(char **)MALLOC(sizeof(char *)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_name", sizeof(char *)*cfg.max_batdn); + return(false); + } + if((batdn_dir=(uint *)MALLOC(sizeof(uint)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_dir", sizeof(uint)*cfg.max_batdn); + return(false); + } + if((batdn_offset=(long *)MALLOC(sizeof(long)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_offset", sizeof(long)*cfg.max_batdn); + return(false); + } + if((batdn_size=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_size", sizeof(ulong)*cfg.max_batdn); + return(false); + } + if((batdn_cdt=(ulong *)MALLOC(sizeof(ulong)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_cdt", sizeof(long)*cfg.max_batdn); + return(false); + } + if((batdn_alt=(ushort *)MALLOC(sizeof(ushort)*cfg.max_batdn))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_alt", sizeof(ushort)*cfg.max_batdn); + return(false); + } + for(i=0;i<cfg.max_batdn;i++) + if((batdn_name[i]=(char *)MALLOC(13))==NULL) { + errormsg(WHERE, ERR_ALLOC, "batdn_name[x]", 13); + return(false); + } + } + + reset_logon_vars(); + +#if 0 + lprintf("temp_dir: %s", cfg.temp_dir); + lprintf("node_dir: %s", cfg.node_dir); + lprintf("ctrl_dir: %s", cfg.ctrl_dir); + lprintf("data_dir: %s", cfg.data_dir); + lprintf("text_dir: %s", cfg.text_dir); + lprintf("exec_dir: %s", cfg.exec_dir); +#endif + + online=ON_REMOTE; + + return(true); +} + +//**************************************************************************** +sbbs_t::~sbbs_t() +{ + uint i; + char node[32]; + + if(cfg.node_num) + sprintf(node,"Node %d", cfg.node_num); + else + strcpy(node,client_name); + + lprintf("%s destructor", node); + + hangup(); /* close socket handle */ + + if(client_socket_dup!=INVALID_SOCKET) + closesocket(client_socket_dup); /* close duplicate handle */ + + sem_post(&output_sem); /* just incase someone's waiting */ + sem_destroy(&output_sem); + + if(cfg.node_num) + node_inbuf[cfg.node_num-1]=NULL; + if(!input_thread_running) + RingBufDispose(&inbuf); + if(!output_thread_running) + RingBufDispose(&outbuf); + + /* Close all open files */ +#if 0 /* old way, not compatible with Linux Samba client */ + if(nodefile_fp!=NULL) { + fclose(nodefile_fp); + nodefile_fp=NULL; + } + if(node_ext_fp!=NULL) { + fclose(node_ext_fp); + node_ext_fp=NULL; + } +#else + if(nodefile!=-1) { + close(nodefile); + nodefile=-1; + } + if(node_ext!=-1) { + close(node_ext); + node_ext=-1; + } +#endif + if(logfile_fp!=NULL) { + fclose(logfile_fp); + logfile_fp=NULL; + } + + /********************************/ + /* Free allocated class members */ + /********************************/ + + /* Reset text.dat */ + + for(i=0;i<TOTAL_TEXT && text!=NULL;i++) + if(text[i]!=text_sav[i]) { + if(text[i]!=nulstr) + FREE(text[i]); + } + + /* Global command shell vars */ + + freevars(&main_csi); + clearvars(&main_csi); + FREE_AND_NULL(main_csi.str); /* crash */ + FREE_AND_NULL(main_csi.cs); + + for(i=0;i<global_str_vars && global_str_var!=NULL;i++) + FREE_AND_NULL(global_str_var[i]); + + FREE_AND_NULL(global_str_var); + FREE_AND_NULL(global_str_var_name); + global_str_vars=0; + + FREE_AND_NULL(global_int_var); + FREE_AND_NULL(global_int_var_name); + global_int_vars=0; + + /* Sub-board variables */ + for(i=0;i<cfg.total_grps && usrsub!=NULL;i++) + FREE_AND_NULL(usrsub[i]); + + FREE_AND_NULL(cursub); + FREE_AND_NULL(usrgrp); + FREE_AND_NULL(usrsubs); + FREE_AND_NULL(usrsub); + FREE_AND_NULL(sub_cfg); + FREE_AND_NULL(sub_ptr); + FREE_AND_NULL(sub_last); + FREE_AND_NULL(sav_sub_cfg); + FREE_AND_NULL(sav_sub_ptr); + FREE_AND_NULL(sav_sub_last); + + + /* File Directory variables */ + for(i=0;i<cfg.total_libs && usrdir!=NULL;i++) + FREE_AND_NULL(usrdir[i]); + + FREE_AND_NULL(curdir); + FREE_AND_NULL(usrlib); + FREE_AND_NULL(usrdirs); + FREE_AND_NULL(usrdir); + + /* Batch upload vars */ + for(i=0;i<cfg.max_batup && batup_desc!=NULL && batup_name!=NULL;i++) { + FREE_AND_NULL(batup_desc[i]); + FREE_AND_NULL(batup_name[i]); + } + + FREE_AND_NULL(batup_desc); + FREE_AND_NULL(batup_name); + FREE_AND_NULL(batup_misc); + FREE_AND_NULL(batup_dir); + FREE_AND_NULL(batup_alt); + + /* Batch download vars */ + for(i=0;i<cfg.max_batdn && batdn_name!=NULL;i++) + FREE_AND_NULL(batdn_name[i]); + + FREE_AND_NULL(batdn_name); + FREE_AND_NULL(batdn_dir); + FREE_AND_NULL(batdn_offset); + FREE_AND_NULL(batdn_size); + FREE_AND_NULL(batdn_cdt); + FREE_AND_NULL(batdn_alt); + + /******************************************/ + /* Free allocated configuration variables */ + /******************************************/ +} + +/****************************************************************************/ +/* Network open function. Opens all files DENYALL and retries LOOP_NOPEN */ +/* number of times if the attempted file is already open or denying access */ +/* for some other reason. All files are opened in BINARY mode. */ +/****************************************************************************/ +int sbbs_t::nopen(char *str, int access) +{ + char logstr[256]; + int file,share,count=0; + + if(access&O_DENYNONE) { + share=SH_DENYNO; + access&=~O_DENYNONE; } + else if(access==O_RDONLY) share=SH_DENYWR; + else share=SH_DENYRW; + while(((file=sopen(str,O_BINARY|access,share))==-1) + && errno==EACCES && count++<LOOP_NOPEN) + if(count>10) + mswait(55); + if(count>(LOOP_NOPEN/2) && count<=LOOP_NOPEN) { + sprintf(logstr,"NOPEN COLLISION - File: %s Count: %d" + ,str,count); + logline("!!",logstr); } + if(file==-1 && errno==EACCES) + bputs("\7\r\nNOPEN: ACCESS DENIED\r\n\7"); + return(file); +} + +void sbbs_t::spymsg(char*msg) +{ + char str[512]; + struct in_addr addr; + + if(!cfg.node_num) + return; + + addr.s_addr=client_addr; + sprintf(str,"\r\n\r\n*** Spy Message ***\r\nNode %d: %s [%s]\r\n*** %s ***\r\n\r\n" + ,cfg.node_num,client_name,inet_ntoa(addr),msg); + if(startup->node_spybuf!=NULL + && startup->node_spybuf[cfg.node_num-1]!=NULL) + RingBufWrite(startup->node_spybuf[cfg.node_num-1],(uchar*)str,strlen(str)); + + if(cfg.node_num && spy_socket[cfg.node_num-1]!=INVALID_SOCKET) + send(spy_socket[cfg.node_num-1],str,strlen(str),0); +} + +#define MV_BUFLEN 4096 + +/****************************************************************************/ +/* Moves or copies a file from one dir to another */ +/* both 'src' and 'dest' must contain full path and filename */ +/* returns 0 if successful, -1 if error */ +/****************************************************************************/ +int sbbs_t::mv(char *src, char *dest, char copy) +{ + char str[256],*buf,atr=curatr; + int ind,outd; + uint chunk=MV_BUFLEN; + ulong length,l; + /* struct ftime ftime; */ + FILE *inp,*outp; + + if(!stricmp(src,dest)) /* source and destination are the same! */ + return(0); + if(!fexist(src)) { + bprintf("\r\n\7MV ERROR: Source doesn't exist\r\n'%s'\r\n" + ,src); + return(-1); } + if(!copy && fexist(dest)) { + bprintf("\r\n\7MV ERROR: Destination already exists\r\n'%s'\r\n" + ,dest); + return(-1); } + if(!copy && ((src[1]!=':' && dest[1]!=':') + || (src[1]==':' && dest[1]==':' && toupper(src[0])==toupper(dest[0])))) { + if(rename(src,dest)) { /* same drive, so move */ + bprintf("\r\nMV ERROR: Error renaming '%s'" + "\r\n to '%s'\r\n\7",src,dest); + return(-1); } + return(0); } + attr(WHITE); + if((ind=nopen(src,O_RDONLY))==-1) { + errormsg(WHERE,ERR_OPEN,src,O_RDONLY); + return(-1); } + if((inp=fdopen(ind,"rb"))==NULL) { + close(ind); + errormsg(WHERE,ERR_FDOPEN,str,O_RDONLY); + return(-1); } + setvbuf(inp,NULL,_IOFBF,32*1024); + if((outd=nopen(dest,O_WRONLY|O_CREAT|O_TRUNC))==-1) { + fclose(inp); + errormsg(WHERE,ERR_OPEN,dest,O_WRONLY|O_CREAT|O_TRUNC); + return(-1); } + if((outp=fdopen(outd,"wb"))==NULL) { + close(outd); + fclose(inp); + errormsg(WHERE,ERR_FDOPEN,dest,O_WRONLY|O_CREAT|O_TRUNC); + return(-1); } + setvbuf(outp,NULL,_IOFBF,8*1024); + length=filelength(ind); + if(!length) { + fclose(inp); + fclose(outp); + errormsg(WHERE,ERR_LEN,src,0); + return(-1); } + if((buf=(char *)MALLOC(MV_BUFLEN))==NULL) { + fclose(inp); + fclose(outp); + errormsg(WHERE,ERR_ALLOC,nulstr,MV_BUFLEN); + return(-1); } + l=0L; + while(l<length) { + bprintf("%2lu%%",l ? (long)(100.0/((float)length/l)) : 0L); + if(l+chunk>length) + chunk=length-l; + if(fread(buf,1,chunk,inp)!=chunk) { + FREE(buf); + fclose(inp); + fclose(outp); + errormsg(WHERE,ERR_READ,src,chunk); + return(-1); } + if(fwrite(buf,1,chunk,outp)!=chunk) { + FREE(buf); + fclose(inp); + fclose(outp); + errormsg(WHERE,ERR_WRITE,dest,chunk); + return(-1); } + l+=chunk; + bputs("\b\b\b"); } + bputs(" \b\b\b"); /* erase it */ + attr(atr); + /* getftime(ind,&ftime); + setftime(outd,&ftime); */ + FREE(buf); + fclose(inp); + fclose(outp); + if(!copy && remove(src)) { + errormsg(WHERE,ERR_REMOVE,src,0); + return(-1); } + return(0); +} + +/****************************************************************************/ +/* Reads data from dsts.dab into stats structure */ +/* If node is zero, reads from ctrl\dsts.dab, otherwise from each node */ +/****************************************************************************/ +BOOL DLLCALL getstats(scfg_t* cfg, char node, stats_t* stats) +{ + char str[256]; + int file; + + sprintf(str,"%sdsts.dab",node ? cfg->node_path[node-1] : cfg->ctrl_dir); + if((file=nopen(str,O_RDONLY))==-1) { +// errormsg(WHERE,ERR_OPEN,str,O_RDONLY); + return(FALSE); + } + lseek(file,4L,SEEK_SET); /* Skip update time/date */ + read(file,stats,sizeof(stats_t)); + close(file); + return(TRUE); +} + + +void sbbs_t::hangup(void) +{ + mswait(1000); /* Give socket output buffer time to flush */ + riosync(0); + if(client_socket!=INVALID_SOCKET) { +// lprintf("Hangup: closing socket %d",client_socket); + close_socket(client_socket); + client_socket=INVALID_SOCKET; + closesocket(client_socket_dup); + client_socket_dup=INVALID_SOCKET; + } + sem_post(&output_sem); + online=0; +} + +int sbbs_t::incom(void) +{ + uchar ch; + + if(!RingBufRead(&inbuf, &ch, 1)) + return(NOINP); + + if(rio_abortable && ch==3) { /* Ctrl-C */ + lprintf("Node %d Ctrl-C hit",cfg.node_num); + sys_status|=SS_ABORT; + rioctl(IOFO); + return(NOINP); } + return(ch); +} + +int sbbs_t::outcom(uchar ch) +{ + if(!RingBufFree(&outbuf)) + return(TXBOF); + RingBufWrite(&outbuf, &ch, 1); + sem_post(&output_sem); + return(0); +} + +void sbbs_t::putcom(char *str, int len) +{ + int i; + + if(!len) + len=strlen(str); + for(i=0;i<len && online; i++) + outcom(str[i]); +} + +void sbbs_t::riosync(char abortable) +{ + if(useron.misc&(RIP|WIP)) /* don't allow abort with RIP or WIP */ + abortable=0; /* mainly because of ANSI cursor posistion response */ + if(sys_status&SS_ABORT) /* no need to sync if already aborting */ + return; + time_t start=time(NULL); + while(online && rioctl(TXBC)) { /* wait up to three minutes for tx buf empty */ + if(abortable && rioctl(RXBC)) { /* incoming characer */ + rioctl(IOFO); /* flush output */ + sys_status|=SS_ABORT; /* set abort flag so no pause */ + break; /* abort sync */ + } + if(time(NULL)-start>180) { /* timeout */ + rioctl(IOCS|PAUSE); + break; + } + mswait(100); + } +} + +/* Legacy Remote I/O Control Interface */ +int sbbs_t::rioctl(ushort action) +{ + int mode; + int state; + + switch(action) { + case GVERS: /* Get version */ + return(0x200); + case GUART: /* Get UART I/O address, not available */ + return(0xffff); + case GIRQN: /* Get IRQ number, not available */ + return((int)client_socket); + case GBAUD: /* Get current bit rate */ + return(0xffff); + case RXBC: /* Get receive buffer count */ + // ulong cnt; + // ioctlsocket (client_socket,FIONREAD,&cnt); + return(/* cnt+ */RingBufFull(&inbuf)); + case RXBS: /* Get receive buffer size */ + return(RingBufFree(&inbuf)); + case TXBC: /* Get transmit buffer count */ + return(RingBufFull(&outbuf)); + case TXBS: /* Get transmit buffer size */ + return(RingBufFree(&outbuf)); + case TXBF: /* Get transmit buffer free space */ + return(RingBufFree(&outbuf)); + case IOMODE: + mode=0; + if(rio_abortable) + mode|=ABORT; + return(mode); + case IOSTATE: + state=0; + if(sys_status&SS_ABORT) + state|=ABORT; + return(state); + case IOFI: /* Flush input buffer */ + RingBufReInit(&inbuf); + break; + case IOFO: /* Flush output buffer */ + RingBufReInit(&outbuf); + break; + case IOFB: /* Flush both buffers */ + RingBufReInit(&inbuf); + RingBufReInit(&outbuf); + break; + case LFN81: + case LFE71: + case FIFOCTL: + return(0); + } + + if((action&0xff)==IOSM) { /* Get/Set/Clear mode */ + if(action&ABORT) + rio_abortable=true; + return(0); } + + + if((action&0xff)==IOCM) { /* Get/Set/Clear mode */ + if(action&ABORT) + rio_abortable=false; + return(0); } + + if((action&0xff)==IOSS) { /* Set state */ + if(action&ABORT) + sys_status|=SS_ABORT; + return(0); } + + if((action&0xff)==IOCS) { /* Clear state */ + if(action&ABORT) + sys_status&=~SS_ABORT; + return(0); } + + if((action&0xff)==TXSYNC) { /* Synchronize transmition */ + #if 0 /* TXSYNC */ + + c=action>>8; /* Number of seconds */ + w=110+(c*1000); /* Number of milliseconds */ + start=clock(); + while(clock()-start<w) { + if(!outbufcnt()) { + #ifdef __OS2__ + DosDevIOCtl(rio_handle + ,IOCTL_ASYNC, 0x69 /* Query # of chars in tx queue */ + ,NULL + ,0 + ,NULL + ,&getqueue + ,sizeof(getqueue) + ,NULL); + if(getqueue.cur==0) /* Empty outbound queue */ + return(0); + #else // Win32 + ClearCommError((HANDLE)rio_handle,&l,&comstat); + if(comstat.cbOutQue==0) /* Empty outbound queue */ + return(0); + #endif + } + mswait(1); + } + #endif + return(1); } + + return(0); +} + +void sbbs_t::reset_logon_vars(void) +{ + int i; + + /* bools */ + qwklogon=false; + + sys_status&=~(SS_USERON|SS_TMPSYSOP|SS_LCHAT|SS_ABORT + |SS_PAUSEON|SS_PAUSEOFF|SS_EVENT|SS_NEWUSER|SS_NEWDAY); + cid[0]=0; + wordwrap[0]=0; + question[0]=0; + menu_dir[0]=0; + menu_file[0]=0; + rows=0; + lncntr=0; + autoterm=0; + keybufbot=keybuftop=lbuflen=0; + slcnt=0; + altul=0; + timeleft_warn=0; + logon_uls=logon_ulb=logon_dls=logon_dlb=0; + logon_posts=logon_emails=logon_fbacks=0; + batdn_total=batup_total=0; + usrgrps=usrlibs=0; + curgrp=curlib=0; + for(i=0;i<cfg.total_libs;i++) + curdir[i]=0; + for(i=0;i<cfg.total_grps;i++) + cursub[i]=0; + cur_cps=3000; + cur_rate=30000; + dte_rate=38400; + main_cmds=xfer_cmds=posts_read=0; + lastnodemsg=0; + lastnodemsguser[0]=0; +} + +/****************************************************************************/ +/* Writes NODE.LOG at end of SYSTEM.LOG */ +/****************************************************************************/ +void sbbs_t::catsyslog(int crash) +{ + char str[256]; + char HUGE16 *buf; + int i,file; + long length; + struct tm * tm; + + if(logfile_fp==NULL) { + sprintf(str,"%snode.log",cfg.node_dir); + if((logfile_fp=fopen(str,"rb"))==NULL) { + errormsg(WHERE,ERR_OPEN,str,O_RDONLY); + return; + } + } + length=ftell(logfile_fp); + if(length) { + if((buf=(char HUGE16 *)LMALLOC(length))==NULL) { + errormsg(WHERE,ERR_ALLOC,str,length); + return; } + rewind(logfile_fp); + if(fread(buf,1,length,logfile_fp)!=(size_t)length) { + errormsg(WHERE,ERR_READ,"log file",length); + FREE((char *)buf); + return; + } + now=time(NULL); + tm=gmtime(&now); + if(tm==NULL) + return; + sprintf(str,"%slogs/%2.2d%2.2d%2.2d.log",cfg.data_dir,tm->tm_mon+1,tm->tm_mday + ,TM_YEAR(tm->tm_year)); + if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) { + errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT); + FREE((char *)buf); + return; } + if(lwrite(file,buf,length)!=length) { + close(file); + errormsg(WHERE,ERR_WRITE,str,length); + FREE((char *)buf); + return; } + close(file); + if(crash) { + for(i=0;i<2;i++) { + sprintf(str,"%scrash.log",i ? cfg.data_dir : cfg.node_dir); + if((file=nopen(str,O_WRONLY|O_APPEND|O_CREAT))==-1) { + errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_APPEND|O_CREAT); + FREE((char *)buf); + return; } + if(lwrite(file,buf,length)!=length) { + close(file); + errormsg(WHERE,ERR_WRITE,str,length); + FREE((char *)buf); + return; } + close(file); } } + FREE((char *)buf); } + + fclose(logfile_fp); + + sprintf(str,"%snode.log",cfg.node_dir); + if((logfile_fp=fopen(str,"w+b"))==NULL) /* Truncate NODE.LOG */ + errormsg(WHERE,ERR_OPEN,str,O_WRONLY|O_TRUNC); +} + + +void sbbs_t::logoffstats() +{ + char str[256]; + int i,file; + stats_t stats; + +#if 0 + if(thisnode.status==NODE_QUIET) /* Quiet users aren't counted */ + return; +#endif + + if(REALSYSOP && !(cfg.sys_misc&SM_SYSSTAT)) + return; + + for(i=0;i<2;i++) { + sprintf(str,"%sdsts.dab",i ? cfg.ctrl_dir : cfg.node_dir); + if((file=nopen(str,O_RDWR))==-1) { + errormsg(WHERE,ERR_OPEN,str,O_RDWR); + return; } + lseek(file,12L,SEEK_SET); /* Skip timestamp, logons and logons today */ + read(file,&stats.timeon,4); /* Total time on system */ + read(file,&stats.ttoday,4); /* Time today on system */ + read(file,&stats.uls,4); /* Uploads today */ + read(file,&stats.ulb,4); /* Upload bytes today */ + read(file,&stats.dls,4); /* Downloads today */ + read(file,&stats.dlb,4); /* Download bytes today */ + read(file,&stats.ptoday,4); /* Posts today */ + read(file,&stats.etoday,4); /* Emails today */ + read(file,&stats.ftoday,4); /* Feedback sent today */ + read(file,&stats.nusers,2); /* New users today */ + + if(!(useron.rest&FLAG('Q'))) { /* Don't count QWKnet nodes */ + stats.timeon+=(now-logontime)/60; + stats.ttoday+=(now-logontime)/60; + stats.ptoday+=logon_posts; + } + stats.uls+=logon_uls; + stats.ulb+=logon_ulb; + stats.dls+=logon_dls; + stats.dlb+=logon_dlb; + stats.etoday+=logon_emails; + stats.ftoday+=logon_fbacks; + if(sys_status&SS_NEWUSER) + stats.nusers++; + + lseek(file,12L,SEEK_SET); + write(file,&stats.timeon,4); /* Total time on system */ + write(file,&stats.ttoday,4); /* Time today on system */ + write(file,&stats.uls,4); /* Uploads today */ + write(file,&stats.ulb,4); /* Upload bytes today */ + write(file,&stats.dls,4); /* Downloads today */ + write(file,&stats.dlb,4); /* Download bytes today */ + write(file,&stats.ptoday,4); /* Posts today */ + write(file,&stats.etoday,4); /* Emails today */ + write(file,&stats.ftoday,4); /* Feedback sent today */ + write(file,&stats.nusers,2); /* New users today */ + close(file); + } +} + +void node_thread(void* arg) +{ + char str[128]; + char uname[LEN_ALIAS+1]; + int file; + uint i,j; + uint curshell=0; + time_t now; + node_t node; + user_t user; + sbbs_t* sbbs = (sbbs_t*) arg; + + node_threads_running++; + update_clients(); + thread_up(); + + if(sbbs->answer()) { + + if(sbbs->qwklogon) { + sbbs->getsmsg(sbbs->useron.number); + sbbs->qwk_sec(); + } else while(sbbs->useron.number + && (sbbs->main_csi.misc&CS_OFFLINE_EXEC || sbbs->online)) { + + if(!sbbs->main_csi.cs || curshell!=sbbs->useron.shell) { + if(sbbs->useron.shell>=sbbs->cfg.total_shells) + sbbs->useron.shell=0; + sprintf(str,"%s%s.bin",sbbs->cfg.exec_dir + ,sbbs->cfg.shell[sbbs->useron.shell]->code); + if((file=sbbs->nopen(str,O_RDONLY))==-1) { + sbbs->errormsg(WHERE,ERR_OPEN,str,O_RDONLY); + sbbs->hangup(); + break; } + FREE_AND_NULL(sbbs->main_csi.cs); + sbbs->freevars(&sbbs->main_csi); + sbbs->clearvars(&sbbs->main_csi); + + sbbs->main_csi.length=filelength(file); + if((sbbs->main_csi.cs=(uchar *)MALLOC(sbbs->main_csi.length))==NULL) { + close(file); + sbbs->errormsg(WHERE,ERR_ALLOC,str,sbbs->main_csi.length); + sbbs->hangup(); + break; } + + if(lread(file,sbbs->main_csi.cs,sbbs->main_csi.length) + !=(int)sbbs->main_csi.length) { + sbbs->errormsg(WHERE,ERR_READ,str,sbbs->main_csi.length); + close(file); + FREE(sbbs->main_csi.cs); + sbbs->main_csi.cs=NULL; + sbbs->hangup(); + break; } + close(file); + + curshell=sbbs->useron.shell; + sbbs->main_csi.ip=sbbs->main_csi.cs; + sbbs->menu_dir[0]=0; + sbbs->menu_file[0]=0; + } + if(sbbs->exec(&sbbs->main_csi)) + break; + + } + } + +#ifdef _WIN32 + if(startup->hangup_sound[0] && !(startup->options&BBS_OPT_MUTE)) + PlaySound(startup->hangup_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif + + sbbs->hangup(); /* just to be sure we shut down the output_thread */ + client_off(node_socket[sbbs->cfg.node_num-1]); + node_socket[sbbs->cfg.node_num-1]=INVALID_SOCKET; + + sbbs->logout(); + sbbs->logoffstats(); /* Updates both system and node dsts.dab files */ + + if(sbbs->sys_status&SS_DAILY) { // New day, run daily events/maintenance + + now=time(NULL); + + sbbs->getnodedat(sbbs->cfg.node_num,&node,1); + node.status=NODE_EVENT_RUNNING; + sbbs->putnodedat(sbbs->cfg.node_num,&node); + +// status("Running system daily maintenance"); + sbbs->logentry("!:","Ran system daily maintenance"); + lprintf("Checking users..."); + j=lastuser(&sbbs->cfg); + for(i=1;i<=j;i++) { + + sprintf(str,"%5u of %-5u",i,j); + status(str); + user.number=i; + getuserdat(&sbbs->cfg,&user); + + /***********************************************/ + /* Fix name (name.dat and user.dat) mismatches */ + /***********************************************/ + username(&sbbs->cfg,i,uname); + if(user.misc&DELETED) { + if(strcmp(uname,"DELETED USER")) + sbbs->putusername(i,nulstr); + continue; } + + if(strcmp(user.alias,uname)) + sbbs->putusername(i,user.alias); + + if(!(user.misc&(DELETED|INACTIVE)) + && user.expire && (ulong)user.expire<=(ulong)now) { + putsmsg(&sbbs->cfg,i,sbbs->text[AccountHasExpired]); + sprintf(str,"%s #%u Expired",user.alias,user.number); + sbbs->logentry("!%",str); + if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOVAL + && sbbs->cfg.level_expireto[user.level]<10) { + user.flags1=sbbs->cfg.val_flags1[sbbs->cfg.level_expireto[user.level]]; + user.flags2=sbbs->cfg.val_flags2[sbbs->cfg.level_expireto[user.level]]; + user.flags3=sbbs->cfg.val_flags3[sbbs->cfg.level_expireto[user.level]]; + user.flags4=sbbs->cfg.val_flags4[sbbs->cfg.level_expireto[user.level]]; + user.exempt=sbbs->cfg.val_exempt[sbbs->cfg.level_expireto[user.level]]; + user.rest=sbbs->cfg.val_rest[sbbs->cfg.level_expireto[user.level]]; + if(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]]) + user.expire=now + +(sbbs->cfg.val_expire[sbbs->cfg.level_expireto[user.level]]*24*60*60); + else + user.expire=0; + user.level=sbbs->cfg.val_level[sbbs->cfg.level_expireto[user.level]]; } + else { + if(sbbs->cfg.level_misc[user.level]&LEVEL_EXPTOLVL) + user.level=sbbs->cfg.level_expireto[user.level]; + else + user.level=sbbs->cfg.expired_level; + user.flags1&=~sbbs->cfg.expired_flags1; /* expired status */ + user.flags2&=~sbbs->cfg.expired_flags2; /* expired status */ + user.flags3&=~sbbs->cfg.expired_flags3; /* expired status */ + user.flags4&=~sbbs->cfg.expired_flags4; /* expired status */ + user.exempt&=~sbbs->cfg.expired_exempt; + user.rest|=sbbs->cfg.expired_rest; + user.expire=0; } + putuserrec(&sbbs->cfg,i,U_LEVEL,2,ultoa(user.level,str,10)); + putuserrec(&sbbs->cfg,i,U_FLAGS1,8,ultoa(user.flags1,str,16)); + putuserrec(&sbbs->cfg,i,U_FLAGS2,8,ultoa(user.flags2,str,16)); + putuserrec(&sbbs->cfg,i,U_FLAGS3,8,ultoa(user.flags3,str,16)); + putuserrec(&sbbs->cfg,i,U_FLAGS4,8,ultoa(user.flags4,str,16)); + putuserrec(&sbbs->cfg,i,U_EXPIRE,8,ultoa(user.expire,str,16)); + putuserrec(&sbbs->cfg,i,U_EXEMPT,8,ultoa(user.exempt,str,16)); + putuserrec(&sbbs->cfg,i,U_REST,8,ultoa(user.rest,str,16)); + if(sbbs->cfg.expire_mod[0]) { + sbbs->useron=user; + sbbs->online=ON_LOCAL; + sbbs->exec_bin(sbbs->cfg.expire_mod,&sbbs->main_csi); + sbbs->online=0; } + } + + /***********************************************************/ + /* Auto deletion based on expiration date or days inactive */ + /***********************************************************/ + if(!(user.exempt&FLAG('P')) /* Not a permanent account */ + && !(user.misc&(DELETED|INACTIVE)) /* alive */ + && (sbbs->cfg.sys_autodel && (now-user.laston)/(long)(24L*60L*60L) + > sbbs->cfg.sys_autodel)) { /* Inactive too long */ + sprintf(str,"Auto-Deleted %s #%u",user.alias,user.number); + sbbs->logentry("!*",str); + sbbs->delallmail(i); + sbbs->putusername(i,nulstr); + putuserrec(&sbbs->cfg,i,U_MISC,8,ultoa(user.misc|DELETED,str,16)); } + } + + lprintf("Purging deleted/expired e-mail"); + sprintf(sbbs->smb.file,"%smail",sbbs->cfg.data_dir); + sbbs->smb.retry_time=sbbs->cfg.smb_retry_time; + if((i=smb_open(&sbbs->smb))!=0) + sbbs->errormsg(WHERE,ERR_OPEN,sbbs->smb.file,i,sbbs->smb.last_error); + else { + if((i=smb_locksmbhdr(&sbbs->smb))!=0) + sbbs->errormsg(WHERE,ERR_LOCK,sbbs->smb.file,i,sbbs->smb.last_error); + else + sbbs->delmail(0,MAIL_ALL); + smb_close(&sbbs->smb); + } + + + sbbs->sys_status&=~SS_DAILY; + if(sbbs->cfg.sys_daily[0]) { +// status("Running system daily event"); + sbbs->logentry("!:","Ran system daily event"); + sbbs->external(sbbs->cmdstr(sbbs->cfg.sys_daily,nulstr,nulstr,NULL) + ,EX_OFFLINE); + } + } + + // Node Daily Event + sbbs->getnodedat(sbbs->cfg.node_num,&node,0); + if(node.misc&NODE_EVENT) { + sbbs->getnodedat(sbbs->cfg.node_num,&node,1); + node.status=NODE_EVENT_RUNNING; + sbbs->putnodedat(sbbs->cfg.node_num,&node); + if(sbbs->cfg.node_daily[0]) { +// status("Running node daily event"); + sbbs->logentry("!:","Run node daily event"); + sbbs->external( + sbbs->cmdstr(sbbs->cfg.node_daily,nulstr,nulstr,NULL) + ,EX_OFFLINE); + } + sbbs->getnodedat(sbbs->cfg.node_num,&node,1); + node.misc&=~NODE_EVENT; + sbbs->putnodedat(sbbs->cfg.node_num,&node); + } + + // Wait for all node threads to terminate + if(sbbs->input_thread_running || sbbs->output_thread_running) { + lprintf("Waiting for node %d %s to terminate..." + ,sbbs->cfg.node_num + ,(sbbs->input_thread_running && sbbs->output_thread_running) ? + "I/O threads" : sbbs->input_thread_running + ? "input thread" : "output thread"); + time_t start=time(NULL); + while(sbbs->input_thread_running + || sbbs->output_thread_running) { + if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { + lprintf("!TIMEOUT waiting for node %d " + "%s to terminate", sbbs->cfg.node_num + ,(sbbs->input_thread_running && sbbs->output_thread_running) ? + "I/O threads" + : sbbs->input_thread_running + ? "input thread" : "output thread"); + break; + } + mswait(100); + } + } + + sbbs->catsyslog(0); + + status(STATUS_WFC); + + sbbs->getnodedat(sbbs->cfg.node_num,&node,1); + if(node.misc&NODE_DOWN) + node.status=NODE_OFFLINE; + else + node.status=NODE_WFC; + node.misc&=~NODE_DOWN; + node.useron=0; + sbbs->putnodedat(sbbs->cfg.node_num,&node); + + if(!sbbs->input_thread_running && !sbbs->output_thread_running + && !sbbs->event_thread_running) { + delete sbbs; + node_threads_running--; + update_clients(); + thread_down(); + } +} + +time_t checktime(void) +{ + struct tm tm; + + memset(&tm,0,sizeof(tm)); + tm.tm_year=94; + tm.tm_mday=1; + return(mktime(&tm)-0x2D24BD00L); +} + +char* DLLCALL bbs_ver(void) +{ + static char ver[256]; + char compiler[32]; + + COMPILER_DESC(compiler); + + sprintf(ver,"Synchronet BBS/Telnet Server v%s%c%s SMBLIB v%s Compiled %s %s with %s" + ,VERSION, REVISION +#ifdef _DEBUG + ," Debug" +#else + ,"" +#endif + ,smb_lib_ver() + ,__DATE__, __TIME__, compiler + ); + + return(ver); +} + +void DLLCALL bbs_terminate(void) +{ + if(telnet_socket!=INVALID_SOCKET) { + lprintf("BBS Terminate: closing telnet socket %d",telnet_socket); + close_socket(telnet_socket); + telnet_socket=INVALID_SOCKET; + } +} + +static void cleanup(int code) +{ + if(telnet_socket!=INVALID_SOCKET) { + close_socket(telnet_socket); + telnet_socket=INVALID_SOCKET; + } + if(rlogin_socket!=INVALID_SOCKET) { + close_socket(rlogin_socket); + rlogin_socket=INVALID_SOCKET; + } + + +#ifdef _WINSOCKAPI_ + if(WSAInitialized && WSACleanup()!=0) + lprintf("!WSACleanup ERROR %d",ERROR_VALUE); +#endif + + free_cfg(&scfg); + free_text(text); + +#ifdef _WIN32 + CloseHandle(exec_mutex); +#endif + pthread_mutex_destroy(&event_mutex); + + lputs("BBS System thread terminated"); + status("Down"); + if(startup->terminated!=NULL) + startup->terminated(code); + thread_down(); +} + +void DLLCALL bbs_thread(void* arg) +{ + char * host_name; + char str[256]; + char logstr[256]; + SOCKADDR_IN server_addr={0}; + SOCKADDR_IN client_addr; + socklen_t client_addr_len; + SOCKET client_socket; + fd_set socket_set; + SOCKET high_socket_set; + int i; + int file; + int result; + BOOL option; + time_t t; + time_t start; + node_t node; + sbbs_t* events; + client_t client; + startup=(bbs_startup_t*)arg; + + if(startup==NULL) { + sbbs_beep(100,500); + fprintf(stderr, "No startup structure passed!\n"); + return; + } + + if(startup->size!=sizeof(bbs_startup_t)) { // verify size + sbbs_beep(100,500); + sbbs_beep(300,500); + sbbs_beep(100,500); + fprintf(stderr, "Invalid startup structure!\n"); + return; + } + + thread_up(); + + status("Initializing"); + +#ifdef __unix__ /* Ignore "Broken Pipe" signal */ + signal(SIGPIPE,SIG_IGN); +#endif + + memset(text, 0, sizeof(text)); + memset(&scfg, 0, sizeof(scfg)); + + node_threads_running=0; + + char compiler[32]; + COMPILER_DESC(compiler); + + lprintf("Synchronet BBS/Telnet Server Version %s Revision %c%s" + ,VERSION,REVISION +#ifdef _DEBUG + ," Debug" +#else + ,"" +#endif + ); + lprintf("Compiled %s %s with %s", __DATE__, __TIME__, compiler); + lprintf("SMBLIB v%s (format %x.%02x)",smb_lib_ver(),smb_ver()>>8,smb_ver()&0xff); + + if(startup->first_node<1 || startup->first_node>startup->last_node) { + lprintf("!ILLEGAL node configuration (first: %d, last: %d)" + ,startup->first_node, startup->last_node); + cleanup(1); + return; + } + + if(sizeof(node_t)!=SIZEOF_NODE_T) { + lprintf("!COMPILER ERROR: sizeof(node_t)=%d instead of %d" + ,sizeof(node_t),SIZEOF_NODE_T); + cleanup(1); + return; + } + + srand(time(NULL)); + + if(!(startup->options&BBS_OPT_LOCAL_TIMEZONE)) { + if(PUTENV("TZ=UCT0")) + lprintf("!putenv() FAILED"); + tzset(); + + if((t=checktime())!=0) { /* Check binary time */ + lprintf("!TIME PROBLEM (%ld)",t); + cleanup(1); + return; + } + } + +#ifdef _WIN32 + if((exec_mutex=CreateMutex(NULL,false,NULL))==NULL) { + lprintf("!ERROR %d creating exec_mutex", GetLastError()); + cleanup(1); + return; + } +#endif + + pthread_mutex_init(&event_mutex,NULL); + + if(!winsock_startup()) { + cleanup(1); + return; + } + + t=time(NULL); + lprintf("Initializing on %.24s with options: %lx" + ,ctime(&t),startup->options); + + /* Initial configuration and load from CNF files */ + scfg.startup=startup; + sprintf(scfg.ctrl_dir, "%.*s", (int)sizeof(scfg.ctrl_dir)-1 + ,startup->ctrl_dir); + lprintf("Loading configuration files from %s", scfg.ctrl_dir); + scfg.node_num=startup->first_node; + if(!load_cfg(&scfg, text)) { + lprintf("!Failed to load configuration files"); + cleanup(1); + return; + } + scfg_reloaded=true; + + if(startup->last_node>scfg.sys_nodes) { + lprintf("Specified last_node (%d) > sys_nodes (%d), auto-corrected" + ,startup->last_node, scfg.sys_nodes); + startup->last_node=scfg.sys_nodes; + } + + /* Create missing directories */ + make_data_dirs(&scfg); + + /* Create missing node directories and dsts.dab files */ + for(i=0;i<=scfg.sys_nodes;i++) { + if(i) + md(scfg.node_path[i-1]); + sprintf(str,"%sdsts.dab",i ? scfg.node_path[i-1] : scfg.ctrl_dir); + if(flength(str)<DSTSDABLEN) { + if((file=sopen(str,O_WRONLY|O_CREAT|O_APPEND, SH_DENYNO))==-1) { + close(file); + lprintf("!Error creating %s",str); + cleanup(1); + return; } + while(filelength(file)<DSTSDABLEN) + if(write(file,"\0",1)!=1) + break; /* Create NULL system dsts.dab */ + close(file); + } + } + + /* Initial global node variables */ + for(i=0;i<MAX_NODES;i++) { + node_inbuf[i]=NULL; + node_socket[i]=INVALID_SOCKET; + spy_socket[i]=INVALID_SOCKET; + } + + startup->node_inbuf=node_inbuf; + + /* open a socket and wait for a client */ + + telnet_socket = open_socket(SOCK_STREAM); + + if(telnet_socket == INVALID_SOCKET) { + lprintf("!ERROR %d creating Telnet socket", ERROR_VALUE); + cleanup(1); + return; + } + + lprintf("Telnet socket %d opened",telnet_socket); + + if(startup->options&BBS_OPT_KEEP_ALIVE) { + lprintf("Enabling WinSock Keep Alives"); + option = TRUE; + + result = setsockopt(telnet_socket, SOL_SOCKET, SO_KEEPALIVE + ,(char *)&option, sizeof(option)); + + if(result != 0) { + lprintf("!ERROR %d (%d) setting Telnet socket option", result, ERROR_VALUE); + cleanup(1); + return; + } + + } + + LINGER linger; + + linger.l_onoff=true; + linger.l_linger=5; /* seconds */ + + result = setsockopt(telnet_socket, SOL_SOCKET, SO_LINGER + ,(char *)&linger, sizeof(linger)); + + if(result != 0) { + lprintf("!ERROR %d (%d) setting Telnet socket options.", result, ERROR_VALUE); + cleanup(1); + return; + } + + /*****************************/ + /* Listen for incoming calls */ + /*****************************/ + memset(&server_addr, 0, sizeof(server_addr)); + + server_addr.sin_addr.s_addr = htonl(startup->telnet_interface); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(startup->telnet_port); + + result = bind(telnet_socket, (struct sockaddr *)&server_addr + ,sizeof(server_addr)); + + if(result != 0) { + lprintf("!ERROR %d (%d) binding Telnet socket to port %d" + ,result, ERROR_VALUE,startup->telnet_port); + cleanup(1); + return; + } + + result = listen(telnet_socket, 1); + + if(result != 0) { + lprintf("!ERROR %d (%d) listening on Telnet socket", result, ERROR_VALUE); + cleanup(1); + return; + } + lprintf("Telnet server listening on port %d",startup->telnet_port); + + if(startup->options&BBS_OPT_ALLOW_RLOGIN) { + + /* open a socket and wait for a client */ + + rlogin_socket = open_socket(SOCK_STREAM); + + if(rlogin_socket == INVALID_SOCKET) { + lprintf("!ERROR %d creating RLogin socket", ERROR_VALUE); + cleanup(1); + return; + } + + lprintf("RLogin socket %d opened",rlogin_socket); + + LINGER linger; + + linger.l_onoff=true; + linger.l_linger=5; /* seconds */ + + result = setsockopt(telnet_socket, SOL_SOCKET, SO_LINGER + ,(char *)&linger, sizeof(linger)); + + if(result != 0) { + lprintf("!ERROR %d (%d) setting RLogin socket options.", result, ERROR_VALUE); + cleanup(1); + return; + } + + /*****************************/ + /* Listen for incoming calls */ + /*****************************/ + memset(&server_addr, 0, sizeof(server_addr)); + + server_addr.sin_addr.s_addr = htonl(startup->rlogin_interface); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(startup->rlogin_port); + + result = bind(rlogin_socket, (struct sockaddr *)&server_addr + ,sizeof(server_addr)); + + if(result != 0) { + lprintf("!ERROR %d (%d) binding RLogin socket to port %d" + ,result, ERROR_VALUE,startup->rlogin_port); + cleanup(1); + return; + } + + result = listen(rlogin_socket, 1); + + if(result != 0) { + lprintf("!ERROR %d (%d) listening on RLogin socket", result, ERROR_VALUE); + cleanup(1); + return; + } + lprintf("RLogin server listening on port %d",startup->rlogin_port); + } + + + sbbs = new sbbs_t(0, server_addr.sin_addr.s_addr + ,"BBS System", telnet_socket, &scfg, text); + sbbs->online = 0; + if(sbbs->init()==false) { + lputs("!BBS initialization failed"); + cleanup(1); + return; + } + _beginthread(output_thread, 0, sbbs); + + events = new sbbs_t(0, server_addr.sin_addr.s_addr + ,"BBS Events", INVALID_SOCKET, &scfg, text); + events->online = 0; + if(events->init()==false) { + lputs("!Events initialization failed"); + cleanup(1); + return; + } + _beginthread(event_thread, 0, events); + + for(i=startup->first_node;i<=startup->last_node;i++) { + sbbs->getnodedat(i,&node,1); + node.status=NODE_WFC; + node.misc&=NODE_EVENT; + node.action=0; + sbbs->putnodedat(i,&node); + } + + lprintf("BBS System thread started for nodes %d through %d" + ,startup->first_node, startup->last_node); + status(STATUS_WFC); + + /* signal caller that we've started up successfully */ + if(startup->started!=NULL) + startup->started(); + + while(telnet_socket!=INVALID_SOCKET) { + + if(node_threads_running==0) { /* check for re-run flags */ + bool rerun=false; + for(i=startup->first_node;i<=startup->last_node;i++) { + sbbs->getnodedat(i,&node,0); + if(node.misc&NODE_RRUN) { + sbbs->getnodedat(i,&node,1); + if(!rerun) + lprintf("Node %d flagged for re-run",i); + rerun=true; + node.misc&=~NODE_RRUN; + sbbs->putnodedat(i,&node); + } + } + if(rerun) { + lprintf("Loading configuration files from %s", scfg.ctrl_dir); + scfg.node_num=startup->first_node; + pthread_mutex_lock(&event_mutex); + if(!load_cfg(&scfg, text)) { + lprintf("!Failed to load configuration files"); + break; + } + scfg_reloaded=true; + pthread_mutex_unlock(&event_mutex); + } + } + + + sbbs->online=0; + + /* now wait for connection */ + + FD_ZERO(&socket_set); + FD_SET(telnet_socket,&socket_set); + high_socket_set=telnet_socket+1; + if(startup->options&BBS_OPT_ALLOW_RLOGIN) { + FD_SET(rlogin_socket,&socket_set); + if(rlogin_socket+1>high_socket_set) + high_socket_set=rlogin_socket+1; + } + + struct timeval tv; + tv.tv_sec=2; + tv.tv_usec=0; + + if((i=select(high_socket_set,&socket_set,NULL,NULL,&tv))<1) { + if(i==0) { + mswait(1); + continue; + } + if(ERROR_VALUE==EINTR) + lprintf("Telnet Server listening interrupted"); + else if(ERROR_VALUE == ENOTSOCK) + lprintf("Telnet Server sockets closed"); + else + lprintf("!ERROR %d selecting sockets",ERROR_VALUE); + break; + } + + client_addr_len = sizeof(client_addr); + + bool rlogin = false; + + if(FD_ISSET(telnet_socket,&socket_set)) + client_socket = accept(telnet_socket, (struct sockaddr *)&client_addr + ,&client_addr_len); + else if(FD_ISSET(rlogin_socket,&socket_set)) { + client_socket = accept(rlogin_socket, (struct sockaddr *)&client_addr + ,&client_addr_len); + rlogin = true; + } else { + lprintf("!No sockets set by select"); + continue; + } + + + if(client_socket == INVALID_SOCKET) + { + if(ERROR_VALUE == ENOTSOCK || ERROR_VALUE == EINTR) + lputs("BBS socket closed"); + else + lprintf("!ERROR %d accept failed", ERROR_VALUE); + break; + } + lprintf("New client socket: %d",client_socket); + if(startup->socket_open!=NULL) + startup->socket_open(TRUE); + +#ifdef _WIN32 + if(startup->answer_sound[0] && !(startup->options&BBS_OPT_MUTE)) + PlaySound(startup->answer_sound, NULL, SND_ASYNC|SND_FILENAME); +#endif + + linger.l_onoff=true; + linger.l_linger=5; /* seconds */ + + result = setsockopt(client_socket, SOL_SOCKET, SO_LINGER + ,(char *)&linger, sizeof(linger)); + + if(result != 0) { + lprintf("!ERROR %d (%d) setting socket options.", result, ERROR_VALUE); + close_socket(client_socket); + continue; + } + + char host_ip[32]; + + strcpy(host_ip,inet_ntoa(client_addr.sin_addr)); + + lprintf("%s connection accepted from: %s" + , rlogin ? "RLogin" : "Telnet", host_ip); + + sbbs->client_socket=client_socket; // require for output to the user + sbbs->online=ON_REMOTE; + + if(sbbs->trashcan(host_ip,"ip")) { + close_socket(client_socket); + lprintf("IP blocked in ip.can"); + sprintf(logstr, "Blocked IP: %s",host_ip); + sbbs->logline("@!",logstr); + continue; + } + + if(rlogin) { + if(!sbbs->trashcan(host_ip,"rlogin")) { + close_socket(client_socket); + lprintf("IP not listed in rlogin.can"); + sprintf(logstr, "Invalid RLogin from: %s",host_ip); + sbbs->logline("@!",logstr); + continue; + } + sbbs->outcom(0); /* acknowledge RLogin per RFC 1282 */ + } + + sbbs->putcom(crlf); + sbbs->putcom(VERSION_NOTICE); + sbbs->putcom(crlf); + + sbbs->bprintf("Connection from: %s\r\n", host_ip); + + struct hostent* h; + if(startup->options&BBS_OPT_NO_HOST_LOOKUP) + h=NULL; + else { + sbbs->bprintf("Resolving host name..."); + h=gethostbyaddr((char *)&client_addr.sin_addr + ,sizeof(client_addr.sin_addr),AF_INET); + } + if(h!=NULL && h->h_name!=NULL) + host_name=h->h_name; + else + host_name="<no name>"; + + lprintf("Host name: %s", host_name); + + if(sbbs->trashcan(host_name,"host")) { + close_socket(client_socket); + lprintf("Host name blocked in host.can"); + sprintf(logstr, "Blocked Host Name: %s",host_name); + sbbs->logline("@!",logstr); + continue; + } + + /* Initialize client display */ + client.size=sizeof(client); + client.time=time(NULL); + sprintf(client.addr,"%.*s",(int)sizeof(client.addr)-1,host_ip); + sprintf(client.host,"%.*s",(int)sizeof(client.host)-1,host_name); + client.port=ntohs(client_addr.sin_port); + client.protocol=rlogin ? "RLogin":"Telnet"; + client.user="<unknown>"; + client_on(client_socket,&client); + + sprintf(logstr, "%s %s", host_name, host_ip); + sbbs->logline("@*",logstr); + + for(i=startup->first_node;i<=startup->last_node;i++) { + sbbs->getnodedat(i,&node,1); + if(node.status==NODE_WFC) { + node.status=NODE_LOGON; + sbbs->putnodedat(i,&node); + break; + } + sbbs->putnodedat(i,&node); + } + + if(i>startup->last_node) { + sbbs->putcom("\r\nSorry, all telnet nodes are in use or otherwise unavailable.\r\n"); + sbbs->putcom("Please try again later.\r\n"); + lprintf("No nodes available for login."); + mswait(3000); + close_socket(client_socket); + client_off(client_socket); + continue; + } + + node_socket[i-1]=client_socket; + + lprintf("Connecting client to node %d",i); + + sbbs_t* new_node = new sbbs_t(i, client_addr.sin_addr.s_addr, host_name + ,client_socket, &scfg, text); + + new_node->client=client; + + if(new_node->init()==false) { + lprintf("!Node %d Initialization failure",new_node->cfg.node_num); + sbbs->putcom("\r\nSorry, initialization failed. Try again later.\r\n"); + mswait(3000); + sbbs->getnodedat(new_node->cfg.node_num,&node,1); + node.status=NODE_WFC; + sbbs->putnodedat(new_node->cfg.node_num,&node); + delete new_node; + node_socket[i-1]=INVALID_SOCKET; + close_socket(client_socket); + client_off(client_socket); + continue; + } + + if(rlogin==true) { + new_node->connection="RLogin"; + new_node->sys_status|=SS_RLOGIN; + } + + new_node->input_thread=(HANDLE)_beginthread(input_thread,0, new_node); + _beginthread(output_thread, 0, new_node); + _beginthread(node_thread, 0, new_node); + } + + // Close all open sockets + for(i=0;i<MAX_NODES;i++) + if(node_socket[i]!=INVALID_SOCKET) { + lprintf("Closing node %d socket %d", i+1, node_socket[i]); + close_socket(node_socket[i]); + } + + sbbs->client_socket=INVALID_SOCKET; + events->terminated=true; + // Wake-up BBS output thread so it can terminate + sem_post(&sbbs->output_sem); + + // Wait for all node threads to terminate + if(node_threads_running) { + lprintf("Waiting for %d node threads to terminate...", node_threads_running); + start=time(NULL); + while(node_threads_running) { + if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { + lprintf("!TIMEOUT waiting for %d node thread(s) to " + "terminate", node_threads_running); + break; + } + mswait(100); + } + } + + // Wait for Events thread to terminate + if(events->event_thread_running) { + pthread_mutex_unlock(&event_mutex); + lprintf("Waiting for event thread to terminate..."); + start=time(NULL); + while(events->event_thread_running) { + if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { + lprintf("!TIMEOUT waiting for BBS event thread to " + "terminate"); + break; + } + mswait(100); + } + } + + // Wait for BBS output thread to terminate + if(sbbs->output_thread_running) { + lprintf("Waiting for system output thread to terminate..."); + start=time(NULL); + while(sbbs->output_thread_running) { + if(time(NULL)-start>TIMEOUT_THREAD_WAIT) { + lprintf("!TIMEOUT waiting for BBS output thread to " + "terminate"); + break; + } + mswait(100); + } + } + + // Set all nodes' status to OFFLINE + for(i=startup->first_node;i<=startup->last_node;i++) { + sbbs->getnodedat(i,&node,1); + node.status=NODE_OFFLINE; + sbbs->putnodedat(i,&node); + } + + if(!events->event_thread_running) + delete events; + + if(!sbbs->output_thread_running) + delete sbbs; + + cleanup(0); +} + + +