/* rsmtp.c */ #define RSMTPMASTER /* * Includes */ #include #include #include "vrsmtp.h" #include "dfault.h" #include "evtdef.h" #include "hstdef.h" #include "prodef.h" #include "scmnd.h" #include "cliutl.h" #include "tsxutl.h" #include "kbdutl.h" #include "rtfile.h" #include "datime.h" #include "inicli.h" #include "suspnd.h" #include "usrblk.h" #include "rcdlck.h" #include "mailbx.h" /* * Internal routines */ extern VOID printtxt(); /* (txt) print text */ extern int dosess(); /* session loop */ extern int setsmtp(); /* setup SMTP session */ extern VOID rsmtpresp(); /* (s) response to client */ extern VOID lsterr(); /* error lister */ extern int response(); /* dumby routine for TSXUTL.C */ extern VOID rsmtpd(); /* (code) process SMTP commands */ extern VOID putmail(); /* place mail in users mailboxes */ extern int index(); /* (i) get last mail number */ extern int getword(); /* (string,word) extract a word */ extern char *stptok(); /* (p,t,l,d) stop on token */ extern char *stpblk(); /* (ch) stop on block */ extern VOID strlwr(); /* (st) convert to lower case */ extern char *checkuser(); /* (us) check if this is a user */ /* * Global Variables For RSMTP Control Channel */ static int aflag = 0, /* Attach to Service Flag */ rflag = 0, /* RSMTP restart flag */ rsmtpcskt = -1, /* socket for incoming smtp */ mflag = 0, /* monitor processing */ waitpos = 0, /* marker for gathering strings from net */ rfstate = 0, /* state of the control channel */ retstate = 0, /* to emulate a subroutine call */ rcptcnt = 0, /* number of recipients */ rcnt = 0; /* number of characters from control */ /* * Global Buffers / Pointers */ #define DOMAIN (66) #define REVPATH (258) #define FORPATH (258) #define COMMAND (1026) #define RSPSTRING (514) #define RCPTNUM (32) #define RCPTLEN (16) #define GENBUFR (128) static char domain[DOMAIN], /* domain name */ revpath[REVPATH], /* reverse path */ forpath[FORPATH], /* forward path */ command[COMMAND], /* command string */ rsp[RSPSTRING], /* response string */ rcptname[RCPTNUM][USERPASSLEN+1], /* recipients */ rcptpath[RCPTNUM][DIRPATHLEN+1], /* mailboxes */ pathname[GENBUFR], /* space to keep path name */ scratch[GENBUFR]; /* scratch string */ struct userblock user; /* usernames / passwords / mailboxes */ char xs[2]; /* dumby buffer required by TSXUTC.C */ static char * /* mailbox file */ mailfl = "wf:mailbx.___"; static FILE * mailfh = NULL; static char * /* help file */ helpfl = "sy:rsmtp.hlp"; static FILE * helpfh = NULL; static char * /* password file */ passfl = "sy:paswrd.fil"; static FILE * passfh = NULL; /* * Debugging options * * bit 0 (0x02) internal events * bit 1 (0x01) dosess() events * bit 2 (0x04) rsmtpd() * bit 3 (0x08) putmail() / index() * bit 4 (0x10) ckeckuser() */ static char *usetxt[] = { "", #ifdef DEBUGOPTION " RSMTP [?] [-d level] [-hp filespec] [-aer]", " ? List the Help Text and Exit RSMTP", " d level Debug Level", " 1 - enable event printing", " 2 - enable dosess() event printing", " 4 - enable rsmtp() printing", " 8 - enable putmail() / index() printing", " 16 - enable checkuser() printing", #else " RSMTP [?] [-hp filespec] [-aer]", " ? List the Help Text and Exit RSMTP", #endif " a Attach to Service", " e Enable Monitoring of all RSMTP Transactions", " h Specify the Help Filespec", " p Specify the Password Filespec", " r Restart RSMTP after Disconnect", "", 0 }; /* * printout text */ VOID printtxt(txt) char **txt; { char **dp; for (dp = txt; *dp; dp++) { kb_puts(*dp); kb_nline(); } } main(argc,argv) int argc; char *argv[]; { int ev,i,j,k; register char c; register struct socket *skt; /* * Initialize for TSX+ clients */ inicli(); /* * Initialize kb handler * Set the break character to ^C */ kb_init(0x03); /* * identify self */ printtxt(vrstxt); /* * parse arguments */ for(i=1; i LOWWATER) { if(fgetss(rsp+4,RSPSTRING-5,fp) != NULL) { rsmtpresp(rsp); } else { fini = TRUE; } } else { suspnd(0); } } while(fini != TRUE); } /* * This is the error print routine for TSXUTL.C * It will be called only if there is an internal error */ VOID lsterr() { if(*errstr) { kb_puts(errstr); kb_nline(); errstr[0] = '\0'; } } /* * This routine is a dumby routine for TSXUTL.C * It should never be called, but returns a 1 [ABORT] */ int rtresp() { kb_puts("rtresp: This dumby routine should never be called"); return(1); } /* * Remote SMTP Server */ VOID rsmtpd(code) int code; { register struct socket *skt; register char *p,*q; char *s,*t,*u; char word[80]; int i,rsmtpcmnd; if(rsmtpcskt < 0) return; skt = (struct socket *) mapskt(rsmtpcskt); switch(rfstate) { case 0: if(code!=CONOPEN) break; sprintf(rsp, /*Net*/ "220 %s Simple Mail Transfer Service Ready.", skt->hostname /*Net*/ ); rsmtpresp(rsp); retstate = 1; rfstate = 50; waitpos = 0; break; case 1: /* * interpret commands that are * received from the other side * set to a safe state to handle recursion * wait for another command line from client */ retstate = 1; rfstate = 50; waitpos = 0; i = strlen(command); while(i && (command[--i] < 33)) { command[i] = '\0'; } if(getword(command,word) == FALSE) break; #ifdef DEBUGOPTION if(debug&0x04) { kbprintf("rsmtpd(%d):", code); kb_puts(word); kb_puts(" "); kb_puts(command); kb_puts("\r\n"); } #endif rsmtpcmnd = scmnd(word); switch(rsmtpcmnd) { case F_DATA: if(!rcptcnt) { rsmtpresp( /*Net*/ "554 No recipients." /*Net*/ ); break; } /* * Create a unique file name for mail data */ sprintf(mailfl,"wf:mailbx.%03d", tljobn); /* * Open mail file */ if((mailfh = fopen(mailfl, "wn")) == NULL) { rsmtpresp( /*Net*/ "552 Unable to open data file for mail." /*Net*/ ); } else { rsmtpresp( /*Net*/ "354 Start mail, end with '.\\r\\n' ." /*Net*/ ); fprintf(mailfh, /*Mail*/ "Received: by %s (%s)\r\n", skt->hostname, vrstxt[1] /*Mail*/ ); fprintf(mailfh, /*Mail*/ " %s\r\n", datime() /*Mail*/ ); rfstate = 50; retstate = 60; } break; case F_HELO: sprintf(rsp, /*Net*/ "250 %s", skt->hostname /*Net*/ ); rsmtpresp(rsp); /* * Log Session */ sprintf(scratch, /*Log*/ "RSMTP: %s", command /*Log*/ ); logsession(rsmtpcskt,scratch); break; case F_HELP: /*Net*/ rsmtpresp("214-Begin Help"); sprintf(rsp," %s", vrstxt[1]); rsmtpresp(rsp); p = skt->tcpout.i.ipsource; sprintf(rsp," Remote SMTP Server %s [%d.%d.%d.%d]", skt->hostname, *(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF); rsmtpresp(rsp); p = skt->tcpout.i.ipdest; sprintf(rsp," Initiated from host: %d.%d.%d.%d", *(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF); rsmtpresp(rsp); rsmtpresp(" "); rsmtpresp(" TSX-Plus RSMTP server, commands:"); rsmtpresp(" DATA EXPN* HELO HELP MAIL NOOP QUIT"); rsmtpresp(" RCPT RSET SAML SEND SOML TURN* VRFY*"); rsmtpresp(" [*] - unsupported commands"); rsmtpresp(" This implementation treats SAML == SEND == SOML == MAIL"); if((helpfh = rtopen(helpfl,"r",0,0)) != NULL) { rsmtpresp(" "); transfer(helpfh); rtclose(helpfl); } rsmtpresp("214 End Help"); /*Net*/ break; case F_MAIL: case F_SAML: case F_SEND: case F_SOML: /* * Initialize */ rcptcnt = 0; forpath[0] = '\0'; revpath[0] = '\0'; if((t = strchr(command,':')) != NULL) strncat(revpath,t+1,REVPATH-1); /* * Extract FROM:<...> */ strlwr(command); if(!strneq(command,"from:",5)) { rsmtpresp( /*Net*/ "501 Syntax error (no FROM:<...@...>)." /*Net*/ ); } else { rsmtpresp( /*Net*/ "250 Ok." /*Net*/ ); } break; case F_NOOP: rsmtpresp( /*Net*/ "200 Ok." /*Net*/ ); break; case F_QUIT: sprintf(scratch, /*Net*/ "221 %s Service closing transmission channel." /*Net*/ ); rsmtpresp(scratch); rcnt = -1; break; case F_RCPT: if((t = strchr(command,':')) != NULL) strncat(forpath,t+1,FORPATH-1); p = skt->tcpout.i.ipsource; sprintf(scratch,"[%d.%d.%d.%d]", *(p+0)&0xFF, *(p+1)&0xFF, *(p+2)&0xFF, *(p+3)&0xFF); p = scratch; q = skt->hostname; /* * Extract TO:<...> */ strlwr(command); if( !strneq(command,"to:",3) || (s = strchr(command,'<')) == NULL || (t = strchr(command,'@')) == NULL || (u = strchr(command,'>')) == NULL || (s > t) || (t > u) ) { rsmtpresp( /*Net*/ "501 Syntax error (no TO:<...@...>)." /*Net*/ ); break; } else if( !strneq(t+1,p,strlen(p)) && strncmp(t+1,q,u-(t+1)) ) { rsmtpresp( /*Net*/ "550 Not a local user, forwarding not implemented." /*Net*/ ); break; } else if(rcptcnt == RCPTNUM) { rsmtpresp( /*Net*/ "552 Exceeded storage allocation." /*Net*/ ); break; } strncpy(rcptname[rcptcnt],s+1,RCPTLEN); i = t - (s+1); if(i < RCPTLEN) { rcptname[rcptcnt][i] = '\0'; } else { rcptname[rcptcnt][RCPTLEN-1] = '\0'; } if((u = checkuser(rcptname[rcptcnt]))==NULL) { rsmtpresp( /*Net*/ "550 Unknown user." /*Net*/ ); } else if(!(user.userflag[1] & RSMTP_PRIV)) { rsmtpresp( /*Net*/ "550 User privilege insufficient to accept mail." /*Net*/ ); } else { strncpy(rcptpath[rcptcnt],u, DIRPATHLEN); rcptpath[rcptcnt][DIRPATHLEN] = '\0'; rcptcnt++; rsmtpresp( /*Net*/ "250 Ok." /*Net*/ ); } break; case F_RSET: /* * Initialize */ rcptcnt = 0; forpath[0] = '\0'; revpath[0] = '\0'; rsmtpresp( /*Net*/ "250 Ok." /*Net*/ ); break; case F_EXPN: case F_TURN: case F_VRFY: default: if(rsmtpcmnd != F_NULL) { /* * unimplemented commands */ sprintf(rsp, /*Net*/ "502 Command '%s' not implemented.", s_clist[rsmtpcmnd] /*Net*/ ); } else { /* * command not understood */ sprintf(rsp, /*Net*/ "500 Command '%s %s' not understood.", word,command /*Net*/ ); } rsmtpresp(rsp); break; } break; case 50: /* * subroutine to wait for end of line */ while( 0 < (rcnt = skread(rsmtpcskt,&command[waitpos],1))) { if(command[waitpos] == '\n') { skdeque(rsmtpcskt); /* * put in terminator */ command[++waitpos] = '\0'; rfstate = retstate; break; } else { if(waitpos < COMMAND-1) waitpos += rcnt; } suspnd(-1); } break; case 60: /* * subroutine to write out mail data */ rfstate = 50; retstate = 60; /* * check for end of data */ if(waitpos==3 && strneq(command,".\r\n",3)) { if(mailfh != NULL) { fclose(mailfh); mailfh = NULL; putmail(); delete(mailfl); } rsmtpresp( /*Net*/ "250 Ok." /*Net*/ ); retstate = 1; waitpos = 0; break; } /* * skip . if two .'s * at beginning of line */ if(strneq(command,"..",2)) { s = command+1; i = waitpos-1; } else { s = command; i = waitpos; } /* * write mail data to file */ if(mailfh != NULL && !fwrite(s,1,i,mailfh)) { /* * disk full err */ fclose(mailfh); mailfh = NULL; rsmtpresp( /*Net*/ "552 Disk write error, probably disk full." /*Net*/ ); } waitpos = 0; suspnd(-1); break; default: break; } if(rcnt < 0) { #ifdef DEBUGOPTION if(debug&0x04) { kbprintf("rsmtpd(%d): rfstate = %d, rcnt = %d\r\n", code,rfstate,rcnt); } #endif rcnt = 0; if(rsmtpcskt >= 0) { sksendwait(rsmtpcskt,1,(long) SENDWAIT); skclose(rsmtpcskt); rsmtpcskt = skrelease(rsmtpcskt,1); } if(rflag && !aflag) { /* * Start listening to the SMTP port */ while((rsmtpcskt = setsmtp()) < 0 && !fndbrk) { /* * Could not get a socket, wait 5 seconds */ suspnd(300); } } else { fndbrk = 1; } } } /* * putmail * * put the mail into each recipients mailbox directory */ VOID putmail() { FILE *inp,*oup; int c,i,j; char str[16]; if((inp = fopen(mailfl,"rn")) == NULL) return; for(i=0; i= (toword+ilen-1)) end++; *adv++=*p++; } } while(!end); *adv='\0'; return(p); } char *stpblk(ch) register char *ch; { while(*ch == ' ' || *ch == '\t') ch++; return(ch); } VOID strlwr(st) register char *st; { while(*st) { *st = tolower(*st); *st++; } } /* * checkuser (us) * * Check the password file for the users' name. * An inaccessable password file results in an invalid return. * * If the user is found then * (1) the mailbox path is set, else * (2) the default path is set, else * (3) invalid return * * An invalid return occurrs if the user is not found or the * directory path is invalid. * * Returns valid(char*)/invalid(NULL) */ char *checkuser(us) char *us; { if(NULL==(passfh = rtopen(passfl,"rn",0,0))) { #ifdef DEBUGOPTION if(debug&0x10) { kbprintf("checkuser(): no passfl: %s\r\n", passfl); } #endif return(NULL); } while (NULL != fread(&user,sizeof(struct userblock),1,passfh)) { /* * does user exist ? */ #ifdef DEBUGOPTION if(debug&0x10) { kbprintf("checkuser(): us = '%s', user = %s\r\n", us,user.username); } #endif if(!strncmp(us,&user.username,USERPASSLEN)) { /* * User found */ rtclose(passfh); getpath(pathname); /* * Set Users Mailbox Directory */ if(user.mailbox.path[0]!='\0' && !cd(&user.mailbox.path)) { cd(pathname); #ifdef DEBUGOPTION if(debug&0x10) { kbprintf("checkuser(): mailbox directory: %s\r\n", user.mailbox.path); } #endif return(user.mailbox.path); } else /* * Set Users default directory */ if(user.defdir.path[0]!='\0' && !cd(&user.defdir.path)) { cd(pathname); #ifdef DEBUGOPTION if(debug&0x10) { kbprintf("checkuser(): default directory: %s\r\n", user.defdir.path); } #endif return(user.defdir.path); } cd(pathname); return(NULL); } } rtclose(passfh); return(NULL); }