#ifdef AMIGA #include <exec/types.h> #endif #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/ioctl.h> #include <netinet/in.h> #ifndef AMIGA #include <time.h> #endif #include <fcntl.h> #include <errno.h> #ifndef AMIGA #include <arpa/telnet.h> #else #define IAC 255 #define DONT 254 #define DO 253 #define WILL 252 #define WONT 251 #define TELOPT_ECHO 1 #endif #include "System.h" #include "User.h" #ifdef AMIGA #include <pragmas/socket_pragmas.h> extern long SocketBase; #define errno Errno() #endif #define MK_NET(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d) /* * Implement embedded comms client, and telnet modes direct from * the server. This replaces the front end stuff to drive tcp/ip * on 5.14. We now do it all BSD style with sockets and select, * so the game shouldn't need a sys5 style interface anywhere. * * 1.00 AGC Created Initial Version For 5.16 * 1.01 AGC Fixed Minor Bugs/Upgrades * 1.02 AGC Added Client Mode Switch * 1.03 AGC Added '!' facility for nonclienters * 1.04 AGC Tidying Up Of Code * 1.05 AGC Added stuff for -p <port> * 1.06 AGC Spots dropped idle lines * 1.07 AGC HARDBANNER optional * 1.08 CCS Added TCP_WRAPPER and echo control * 1.09 AGC Removed wrapper, moved motd code. * 1.10 AGC Fix endian problems, also fixes for some dodgier tcp stacks * 1.11 AGC Client mode fixed (silly mistake), round robin scheduler. * 1.12 AGC BSX graphics support * 1.13 AGC Minor client mode bug, and @TMS bug fixed * 1.14 AGC 'Smart' prompt code. Only alpha prompts are sent to BSX users * 1.15 AGC Server supports AmiTCP. Small changes only. I am seriously impressed * with the AmiTCP package. */ extern USER UserList[]; Module "IPCDirect"; Version "1.15"; Author "Alan Cox"; /* * This is definitely a case for a big structure for each user, but * I do a lot of scanning of this data, even on fairly idle channels * so stuffing it into arrays, in this chosen order lowers paging * Its probably academic considering the amount of background stuff * people put in a typical abermud5 */ static char UserLine[MAXUSER][81]; static short UserPos[MAXUSER]; static char SnoopLine[MAXUSER][81]; static short SnoopPos[MAXUSER]; static char Prompts[MAXUSER][64]; /* Stuff below this comment gets scanned every second */ static char UserInput[MAXUSER][514]; static short InputPos[MAXUSER]; static char UserState[MAXUSER]; static char UserEcho[MAXUSER]; static int MainFD,AltFD,BsxFD; static fd_set ReadMask,WriteMask; int SysPort=TCP_PORT; /* * This info is needed elsewhere (BSX.c) */ int IsBSX(int u) { if(UserState[u]==2) return(1); return(0); } /* * Given a file descriptor, find its owner process. Assumes that all * channels have an owner. See changes to ComServer Handle_Login() to * make this true */ int FindUserFD(int fd) { int ct=0; while(ct<MAXUSER) { if(UserList[ct].us_Port&&(UserList[ct].us_Port->po_fd&255)==(fd&255)) { return(ct); } ct++; } Error("No User associated with fd"); /* Boom! */ } int IsUserFD(int fd) { int ct=0; while(ct<MAXUSER) { if(UserList[ct].us_Port&&(UserList[ct].us_Port->po_fd&255)==(fd&255)) return(ct); ct++; } return(-1); } /* * Grab incoming data from a channel. If we finish reading a line, make * a packet of it and stuff it into the main game. */ int ReadBlock(int u, COMTEXT *pkt) { /* We read only when we get something pending off select - so if we read nothing first try they have buggered off */ int gotany=0; char c; int l; unsigned char t_iac=IAC, t_will=WILL, t_wont=WONT, t_do =DO, t_dont=DONT; while((l=recv(UserList[u].us_Port->po_fd,&c,1,0))>0) { gotany=1; /* * Telnet traps - get dump any IAC stuff */ if( ((unsigned char) c) == t_iac) { read(UserList[u].us_Port->po_fd,&c,1); if( ((unsigned char)c== t_will) || ((unsigned char)c== t_wont) || ((unsigned char)c== t_do ) || ((unsigned char)c== t_dont) ) { read(UserList[u].us_Port->po_fd,&c,1); } continue; } if(c=='!'&&InputPos[u]==0&&UserState[u]==0) { c='\n'; InputPos[u]=strlen(UserInput[u]); } if(c==8||c==127) { if(InputPos[u]>0&&UserState[u]==0) InputPos[u]--; return(0); } if(c!='\r') { UserInput[u][InputPos[u]++]=c; if(InputPos[u]==513||c=='\n') { if(UserState[u]==0 && UserEcho[u]==1) { CharPut(u,'\r'); /* Fix echo in nonecho mode */ CharPut(u,'\n'); } UserInput[u][InputPos[u]-1]=0; if(UserState[u]>0 && UserEcho[u]==0) /* BSX expects us to neatly echo command */ { if(*UserInput[u]!='#') /* But not # commands */ { char *p=UserInput[u]; CharPut(u,'>'); CharPut(u,' '); while(*p) CharPut(u,*p++); CharPut(u,'\r'); CharPut(u,'\n'); } } strcpy(pkt->pa_Data,UserInput[u]); pkt->pa_Sender=u; if(UserState[u]==2 && UserInput[u][0]=='#') pkt->pa_Type=PACKET_BSXSCENE; /* BSX process the line */ else pkt->pa_Type=PACKET_COMMAND; InputPos[u]=0; return(1); } } } /* EAGAIN added for SYS5 machines */ if((l==-1 && errno!=EWOULDBLOCK && errno != EAGAIN) || gotany==0) { /* Network fault.. shove it */ UserList[u].us_Port->po_Flags|=FL_FAULT; FD_CLR(UserList[u].us_Port->po_fd,&ReadMask); FD_CLR(UserList[u].us_Port->po_fd,&WriteMask); } return(0); } /* * Client used to do formatting, so we have to do wrapping here now */ void SnoopPut(int u, char c) { SnoopLine[u][SnoopPos[u]++]=c; } void SnoopFlush(int u, int n) { if(UserState[u]==1) WriteSocket(u,"\002S",2); else WriteSocket(u,"|",1); WriteSocketText(u,SnoopLine[u],n); WriteSocket(u,"\r\n",2); if(UserState[u]==1) WriteSocket(u,"\003",1); SnoopPos[u]=0; } void SnoopCharPut(int u, char c) { if(SnoopPos[u]==79) { if(c==' '||c=='\n'||c=='\t') SnoopFlush(u,79); else { int ct=78; while(ct--) { if(SnoopLine[u][ct]==' '||SnoopLine[u][ct]=='\t') { SnoopFlush(u,ct); while(++ct<=78) SnoopPut(u,SnoopLine[u][ct]); SnoopPut(u,c); return; } } SnoopFlush(u,78); SnoopPut(u,c); } } else { if(c=='\n') { SnoopFlush(u,SnoopPos[u]); return; } SnoopPut(u,c); } return; } /* Formatting for normal text output */ void CharPut(int u, char c) { if(UserPos[u]==79) { if(c==' '||c=='\n'||c=='\t') LineFlush(u,79); else { int ct=78; while(ct--) { if(UserLine[u][ct]==' '||UserLine[u][ct]=='\t') { LineFlush(u,ct); while(++ct<=78) LinePut(u,UserLine[u][ct]); LinePut(u,c); return; } } LineFlush(u,78); LinePut(u,c); } } else { if(c=='\n') { LineFlush(u,UserPos[u]); return; } LinePut(u,c); } return; } void LinePut(int u, char c) { UserLine[u][UserPos[u]++]=c; } void LineFlush(int u, char n) { WriteSocketText(u,UserLine[u],n); WriteSocket(u,"\r\n",2); UserPos[u]=0; } void FieldShift(int u, int f) { if(f==0||f>77) return; do { CharPut(u,' '); } while(UserPos[u]%f!=0); } /* Fake the port binding operating - we use the fd that is involved as the key to the port */ PORT *Bind_Port(int u, int p) { PORT *a=Allocate(PORT); a->po_fd=(p&255); UserState[u]=0; UserEcho[u]=0; UserState[u]=p/256; UserPos[u]=0; UserInput[u][0]=0; Prompts[u][0]=0; InputPos[u]=0; SnoopPos[u]=0; a->po_Flags=0; a->po_SiloPtr=0; return(a); } /* Set up the master socket */ PORT *CreateMPort(int f) { PORT *p=Allocate(PORT); p->po_fd=Make_Socket(SysPort); MainFD=p->po_fd; FD_ZERO(&ReadMask); FD_SET(p->po_fd,&ReadMask); AltFD=Make_Socket(SysPort+1); FD_SET(AltFD,&ReadMask); BsxFD=Make_Socket(SysPort+2); FD_SET(BsxFD,&ReadMask); return(p); } /* Open a port, meaningless really */ PORT *OpenMPort(PORT *port) { port->po_Open++; return(port); } /* Close down a port */ int CloseMPort(PORT *port) { SiloFlush(port); port->po_Open--; #ifdef AMIGA CloseSocket(port->po_fd); #else close(port->po_fd); #endif FD_CLR(port->po_fd,&ReadMask); FD_CLR(port->po_fd,&WriteMask); free((char *)port); return 0; } /* We don't use the blocking switches at all in this set of IPC code */ void BlockOn(PORT *p){;} void BlockOff(PORT *p){;} /* * Take an outgoing packet service and turn it into action on the * sockets. */ int WriteMPort(PORT *a, COMTEXT *block, int len) { COMDATA *d=(COMDATA *)block; char *fp; int u=FindUserFD(a->po_fd); switch(block->pa_Type) { case PACKET_OUTPUT:; fp=block->pa_Data; while(*fp) CharPut(u,*fp++); break; case PACKET_CLEAR: fp=block->pa_Data; while(*fp) CharPut(u,*fp++); if(UserState[u]==2) WriteSocket(u,"@TMS",4); break; case PACKET_SETPROMPT: if(UserState[u]!=1) strcpy(Prompts[u],block->pa_Data); else { WriteSocket(u,"\002P",2); WriteSocket(u,block->pa_Data,strlen(block->pa_Data)); WriteSocket(u,"\003",1); } break; case PACKET_LOGINACCEPT: InputPos[u]=0; break; case PACKET_EDIT: if(UserState[u]==1) { WriteSocket(u,"\002T",2); WriteSocket(u,block->pa_Data,strlen(block->pa_Data)); WriteSocket(u,"\003",1); } break; case PACKET_SNOOPTEXT: fp=block->pa_Data; while(*fp) SnoopCharPut(u,*fp++); break; case PACKET_SETTITLE:break; case PACKET_LOOPECHO: break; /* Cant be bothered.. */ case PACKET_ECHOBACK:break; case PACKET_BSXSCENE: /* Graphics stuff - only goes to BSX users */ if(UserState[u]==2) WriteSocket(u,block->pa_Data,strlen(block->pa_Data)); break; case PACKET_ECHO: UserEcho[u]=d->pa_Data[0]; switch(UserState[u]) { case 1: WriteSocket(u,"\002E",2); WriteSocket(u,d->pa_Data[0]?"Y\003":"N\003",2); break; case 2: break; case 0: { unsigned char t_iac=IAC, t_will=WILL, t_wont=WONT, t_echo=TELOPT_ECHO; WriteSocket(u,(char *)&t_iac,1); WriteSocket(u,(d->pa_Data[0])?((char *)&t_will):((char *)&t_wont),1); WriteSocket(u,(char *)&t_echo,1); } break; /* Dont bother with echoing now */ } break; case PACKET_INPUT: switch(UserState[u]) { case 2: if(!isalpha(*Prompts[u])) break; WriteSocketText(u,Prompts[u],strlen(Prompts[u])); WriteSocketText(u,"\r\n",2); break; case 0: WriteSocketText(u,Prompts[u],strlen(Prompts[u])); break; case 1: if(isalpha(*Prompts[u])) { WriteSocketText(u,Prompts[u],strlen(Prompts[u])); WriteSocketText(u,"\r\n",2); } WriteSocket(u,"\002G\003",3); break; } break; case PACKET_SETFIELD: FieldShift(u,d->pa_Data[0]); break; } return(0); } /* Delete a closed port */ int DeleteMPort(PORT *a) { if(a->po_Open) { a->po_Flags|=FL_DELETE; return(0); } #ifdef AMIGA CloseSocket(a->po_fd); #else close(a->po_fd); #endif free((char *)a); return(0); } /* We don't use the service finding stuff any more */ int AssignService(char *s, PORT *p){return(0);} int DeAssignService(char *s){return(0);} PORT *FindService(char *s) {return NULL;} /* * Wait up to a second for a message to read. * * This code has changed only slightly for Beta3, but the implications are significant. Previously * lower numbered connections got a distinct advantage. The new code does a round robin scan of * file decriptors. */ int ReadMPort(PORT *a, COMTEXT *b) { struct timeval tvl; static long magic_time_cookie=0; long t; static int ct=0; /* Now static */ fd_set rfm;/*WriteMask;*/ int oct=ct; /* For non ANSI compilers... */ memcpy(&rfm,&ReadMask,sizeof(rfm)); /* Initialise tvl seperately to keep non ansi compilers happy */ tvl.tv_sec=1; tvl.tv_usec=0; FD_ZERO(&WriteMask); /* * Slow machine fix: Stops busy machine never running background jobs */ time(&t); if(t-magic_time_cookie>2) { magic_time_cookie=t; return(-2); } if(select(NFDBITS,&rfm,NULL,NULL,&tvl)==-1) { perror("select"); exit(1); } /* Now do reading */ do { if(ct==MainFD||ct==AltFD||ct==BsxFD) { if(FD_ISSET(ct,&rfm)) { return(MakeConnection(ct,b)); /* return(0);*/ } } else if(FD_ISSET(ct,&rfm)) { if(IsUserFD(ct)!=-1 && ReadBlock(FindUserFD(ct),b)==1) { return(0); } } ct++; if(ct==NFDBITS) ct=0; } while(ct!=oct); return(-2); } /* Handle non-blocking socket queues and the incomplete writes stuff */ int Silo(PORT *port, char *block, int len) { if(port->po_SiloPtr+len>8191-sizeof(int)) { port->po_Flags|=FL_FAULT; FD_CLR(port->po_fd,&ReadMask); FD_CLR(port->po_fd,&WriteMask); return(-20); } memcpy(port->po_Silo+port->po_SiloPtr,block,len); port->po_SiloPtr+=len; return(0); } int SiloFlush(PORT *port) { int sz; if(port==NULL) return(0); if(port->po_Flags&FL_FAULT) return(-1); sz=send(port->po_fd,port->po_Silo,port->po_SiloPtr,0); if(sz==-1&&errno!=EWOULDBLOCK&&errno!=EAGAIN) { port->po_Flags|=FL_FAULT; FD_CLR(port->po_fd,&ReadMask); FD_CLR(port->po_fd,&WriteMask); return(-1); } port->po_SiloPtr-=sz; /* Have to bcopy this one : must NOT memcpy it as we need to ensure it handles overlaps right. If you dont have such a beast write one If you have bcopy but not memcpy,then replace the memcpy's - thats ok */ if(port->po_SiloPtr==0) return(0); bcopy(port->po_Silo+sz,port->po_Silo,port->po_SiloPtr); return(-1); } /* Send data to someone */ int WriteSocket(int user, char *data, int bytes) { int l; if(SiloFlush(UserList[user].us_Port)) { return(Silo(UserList[user].us_Port,data,bytes)); } l=send(UserList[user].us_Port->po_fd,data,bytes,0); if(l<bytes) if(Silo(UserList[user].us_Port,data+l,bytes-l)!=0) return(-1); return(0); } /* BSX MUD uses @xyz as command strings. This means we have to quote our @ characters in BSX mode as @TXT@$ */ int WriteSocketText(int user, char *data, int bytes) { /* Do all we can to avoid lots of messy little writes */ /* If @TXT actually worked on the BSX client I might even bother checking this code works! */ #ifdef DONTDEF char *t; if(UserState[user]!=2) #endif return WriteSocket(user,data,bytes); #ifdef DONTDEF t=memchr(data,'@',bytes); if(t==NULL) return WriteSocket(user,data,bytes); /* We've tried all we can but we will have to do some thinking */ do { if(t!=data) { if(WriteSocket(user,data,t-bytes)==-1) return(-1); } if(WriteSocket(user,"@TXT@$",6)==-1) return(-1); bytes-=(t-data); bytes--; /* For the '@' too */ data=t+1; t=memchr(data,'@',bytes); } while(t!=NULL); /* All dealt with at last */ return(0); #endif } void WriteFlush(int u){;} /* Convert an IP address to a text string. We can't go further than ip names as the lookup can take up to 30 seconds. This is not acceptable in a server environment - note the address has already been fed through ntohl() */ char *NetName(unsigned long l) { int code[4]; static char name[64]; code[0]=l%65536; code[2]=l/65536; code[3]=code[2]/256; code[2]%=256; code[1]=code[0]/256; code[0]%=256; sprintf(name,"%d.%d.%d.%d",code[3],code[2],code[1],code[0]); return(name); } #ifdef TIME_LIMITS #define CLOSEHOUR 9 #define CHECK 18 int can_play_now(void) { time_t x; struct tm *a; time(&x); a = localtime(&x); return (a->tm_hour >= CHECK || a->tm_hour < CLOSEHOUR || a->tm_wday == 6 || a->tm_wday == 0); } #endif /* TIME_LIMITS */ /* * Fake up a login request packet */ int MakeConnection(int fd, COMTEXT *p) { struct sockaddr_in netaddr; socklen_t lv=sizeof(netaddr); int ofd=fd; #ifdef AMIGA long yes=1; #endif #ifndef AMIGA fcntl(fd,F_SETFL,O_NDELAY); #endif fd=accept(fd,(struct sockaddr *)&netaddr,&lv); #ifndef AMIGA fcntl(fd,F_SETFL,0); #endif p->pa_Type=PACKET_LOGINREQUEST; p->pa_Sender=fd; /* Reverse the address, that makes it easier to work with everywhere else */ netaddr.sin_addr.s_addr=ntohl(netaddr.sin_addr.s_addr); sprintf(p->pa_Data,"%s%s$%d$*","Internet:",NetName(netaddr.sin_addr.s_addr), fd+(ofd==AltFD?256:0)+(ofd==BsxFD?512:0)); /* SWANSEA STUFF - PLEASE ENFORCE ELSEWHERE TOO... */ if(netaddr.sin_addr.s_addr==MK_NET(137,44,1,1) ||netaddr.sin_addr.s_addr==MK_NET(143,52,2,10)) { sprintf(p->pa_Data,"BAD$SITE"); p->pa_Sender= -1; send(fd,"Not permitted from this site\n\r",30,0); #ifdef AMIGA CloseSocket(fd); #else close(fd); #endif return(-1); } /* printf("***GOT CONNECTION\n");*/ #ifndef AMIGA fcntl(fd,F_SETFL,O_NDELAY); #else /* AmiTCP has the BSD blocking ioctl() but lacks the use of fcntl() */ ioctl(fd,FIONBIO,&yes); #endif FD_SET(fd,&ReadMask); return(0); }