19339Seric # include <errno.h> 24549Seric # include "sendmail.h" 311728Seric # include <signal.h> 44549Seric 55181Seric # ifndef SMTP 6*16886Seric SCCSID(@(#)srvrsmtp.c 4.9 08/11/84 (no SMTP)); 75181Seric # else SMTP 84556Seric 9*16886Seric SCCSID(@(#)srvrsmtp.c 4.9 08/11/84); 105181Seric 114549Seric /* 124549Seric ** SMTP -- run the SMTP protocol. 134549Seric ** 144549Seric ** Parameters: 154549Seric ** none. 164549Seric ** 174549Seric ** Returns: 184549Seric ** never. 194549Seric ** 204549Seric ** Side Effects: 214549Seric ** Reads commands from the input channel and processes 224549Seric ** them. 234549Seric */ 244549Seric 254549Seric struct cmd 264549Seric { 274549Seric char *cmdname; /* command name */ 284549Seric int cmdcode; /* internal code, see below */ 294549Seric }; 304549Seric 314549Seric /* values for cmdcode */ 324549Seric # define CMDERROR 0 /* bad command */ 334549Seric # define CMDMAIL 1 /* mail -- designate sender */ 344976Seric # define CMDRCPT 2 /* rcpt -- designate recipient */ 354549Seric # define CMDDATA 3 /* data -- send message text */ 369339Seric # define CMDRSET 4 /* rset -- reset state */ 379339Seric # define CMDVRFY 5 /* vrfy -- verify address */ 389339Seric # define CMDHELP 6 /* help -- give usage info */ 399339Seric # define CMDNOOP 7 /* noop -- do nothing */ 409339Seric # define CMDQUIT 8 /* quit -- close connection and die */ 419339Seric # define CMDHELO 9 /* helo -- be polite */ 429339Seric # define CMDDBGQSHOW 10 /* showq -- show send queue (DEBUG) */ 439339Seric # define CMDDBGDEBUG 11 /* debug -- set debug mode */ 449339Seric # define CMDVERB 12 /* verb -- go into verbose mode */ 459339Seric # define CMDDBGKILL 13 /* kill -- kill sendmail */ 469339Seric # define CMDDBGWIZ 14 /* wiz -- become a wizard */ 479339Seric # define CMDONEX 15 /* onex -- sending one transaction only */ 489339Seric # define CMDDBGSHELL 16 /* shell -- give us a shell */ 494549Seric 504549Seric static struct cmd CmdTab[] = 514549Seric { 524549Seric "mail", CMDMAIL, 534976Seric "rcpt", CMDRCPT, 544549Seric "data", CMDDATA, 554549Seric "rset", CMDRSET, 564549Seric "vrfy", CMDVRFY, 577762Seric "expn", CMDVRFY, 584549Seric "help", CMDHELP, 594549Seric "noop", CMDNOOP, 604549Seric "quit", CMDQUIT, 614976Seric "helo", CMDHELO, 628544Seric "verb", CMDVERB, 639314Seric "onex", CMDONEX, 645003Seric # ifdef DEBUG 659339Seric "showq", CMDDBGQSHOW, 668544Seric "debug", CMDDBGDEBUG, 678544Seric "kill", CMDDBGKILL, 688544Seric "wiz", CMDDBGWIZ, 699339Seric "shell", CMDDBGSHELL, 705003Seric # endif DEBUG 714549Seric NULL, CMDERROR, 724549Seric }; 734549Seric 748544Seric # ifdef DEBUG 758544Seric bool IsWiz = FALSE; /* set if we are a wizard */ 7615596Seric char *WizWord; /* the wizard word to compare against */ 778544Seric # endif DEBUG 789339Seric bool InChild = FALSE; /* true if running in a subprocess */ 799378Seric bool OneXact = FALSE; /* one xaction only this run */ 8011146Seric char *RealHostName = NULL; /* verified hostname, set in daemon.c */ 8111146Seric 829339Seric #define EX_QUIT 22 /* special code for QUIT command */ 838544Seric 844549Seric smtp() 854549Seric { 864549Seric register char *p; 878544Seric register struct cmd *c; 884549Seric char *cmd; 894549Seric extern char *skipword(); 904549Seric extern bool sameword(); 914549Seric bool hasmail; /* mail command received */ 924713Seric int rcps; /* number of recipients */ 935003Seric auto ADDRESS *vrfyqueue; 9412612Seric ADDRESS *a; 958544Seric char inp[MAXLINE]; 967124Seric extern char Version[]; 977356Seric extern tick(); 988544Seric extern bool iswiz(); 999349Seric extern char *arpadate(); 10011151Seric extern char *macvalue(); 10112612Seric extern ADDRESS *recipient(); 1024549Seric 1035003Seric hasmail = FALSE; 1044713Seric rcps = 0; 1057363Seric if (OutChannel != stdout) 1067363Seric { 1077363Seric /* arrange for debugging output to go to remote host */ 1087363Seric (void) close(1); 1097363Seric (void) dup(fileno(OutChannel)); 1107363Seric } 11111931Seric settime(); 11216153Seric expand("\001e", inp, &inp[sizeof inp], CurEnv); 11310708Seric message("220", inp); 1144549Seric for (;;) 1154549Seric { 11612612Seric /* arrange for backout */ 11712612Seric if (setjmp(TopFrame) > 0 && InChild) 11812612Seric finis(); 11912612Seric QuickAbort = FALSE; 12012612Seric HoldErrs = FALSE; 12112612Seric 1227356Seric /* setup for the read */ 1236907Seric CurEnv->e_to = NULL; 1244577Seric Errors = 0; 1257275Seric (void) fflush(stdout); 1267356Seric 1277356Seric /* read the input line */ 1287685Seric p = sfgets(inp, sizeof inp, InChannel); 1297356Seric 1307685Seric /* handle errors */ 1317356Seric if (p == NULL) 1327356Seric { 1334549Seric /* end of file, just die */ 1344558Seric message("421", "%s Lost input channel", HostName); 1354549Seric finis(); 1364549Seric } 1374549Seric 1384549Seric /* clean up end of line */ 1394558Seric fixcrlf(inp, TRUE); 1404549Seric 1414713Seric /* echo command to transcript */ 1429545Seric if (CurEnv->e_xfp != NULL) 1439545Seric fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 1444713Seric 1454549Seric /* break off command */ 1464549Seric for (p = inp; isspace(*p); p++) 1474549Seric continue; 1484549Seric cmd = p; 1494549Seric while (*++p != '\0' && !isspace(*p)) 1504549Seric continue; 1514549Seric if (*p != '\0') 1524549Seric *p++ = '\0'; 1534549Seric 1544549Seric /* decode command */ 1554549Seric for (c = CmdTab; c->cmdname != NULL; c++) 1564549Seric { 1574549Seric if (sameword(c->cmdname, cmd)) 1584549Seric break; 1594549Seric } 1604549Seric 1614549Seric /* process command */ 1624549Seric switch (c->cmdcode) 1634549Seric { 1644976Seric case CMDHELO: /* hello -- introduce yourself */ 16514877Seric if (sameword(p, HostName)) 16614877Seric { 16714877Seric /* connected to an echo server */ 16814877Seric message("553", "%s I refuse to talk to myself", 16914877Seric HostName); 17014877Seric break; 17114877Seric } 17211146Seric if (RealHostName != NULL && !sameword(p, RealHostName)) 17311146Seric { 17411146Seric char buf[MAXNAME]; 17511146Seric 17611146Seric (void) sprintf(buf, "%s (%s)", p, RealHostName); 17711146Seric define('s', newstr(buf), CurEnv); 17811146Seric } 17911146Seric else 18011146Seric define('s', newstr(p), CurEnv); 1814997Seric message("250", "%s Hello %s, pleased to meet you", 1824997Seric HostName, p); 1834976Seric break; 1844976Seric 1854549Seric case CMDMAIL: /* mail -- designate sender */ 18611151Seric /* force a sending host even if no HELO given */ 18711151Seric if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 18811151Seric define('s', RealHostName, CurEnv); 18911151Seric 1909314Seric /* check for validity of this command */ 1914558Seric if (hasmail) 1924558Seric { 1934558Seric message("503", "Sender already specified"); 1944558Seric break; 1954558Seric } 1969339Seric if (InChild) 1979339Seric { 1989339Seric syserr("Nested MAIL command"); 1999339Seric exit(0); 2009339Seric } 2019339Seric 2029339Seric /* fork a subprocess to process this command */ 2039339Seric if (runinchild("SMTP-MAIL") > 0) 2049339Seric break; 2059339Seric initsys(); 2069339Seric 2079339Seric /* child -- go do the processing */ 2084549Seric p = skipword(p, "from"); 2094549Seric if (p == NULL) 2104549Seric break; 2114549Seric setsender(p); 2124577Seric if (Errors == 0) 2134549Seric { 2144549Seric message("250", "Sender ok"); 2154549Seric hasmail = TRUE; 2164549Seric } 2179339Seric else if (InChild) 2189339Seric finis(); 2194549Seric break; 2204549Seric 2214976Seric case CMDRCPT: /* rcpt -- designate recipient */ 22212612Seric if (setjmp(TopFrame) > 0) 22314785Seric { 22414785Seric CurEnv->e_flags &= ~EF_FATALERRS; 22512612Seric break; 22614785Seric } 22712612Seric QuickAbort = TRUE; 2284549Seric p = skipword(p, "to"); 2294549Seric if (p == NULL) 2304549Seric break; 23116140Seric a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 23212612Seric if (a == NULL) 23312612Seric break; 234*16886Seric a->q_flags |= QPRIMARY; 23512612Seric a = recipient(a, &CurEnv->e_sendqueue); 23612612Seric if (Errors != 0) 23712612Seric break; 23812612Seric 23912612Seric /* no errors during parsing, but might be a duplicate */ 24012612Seric CurEnv->e_to = p; 24112612Seric if (!bitset(QBADADDR, a->q_flags)) 24212612Seric message("250", "Recipient ok"); 24312612Seric else 2444549Seric { 24512612Seric /* punt -- should keep message in ADDRESS.... */ 24612612Seric message("550", "Addressee unknown"); 2474549Seric } 24812612Seric CurEnv->e_to = NULL; 24912612Seric rcps++; 2504549Seric break; 2514549Seric 2524549Seric case CMDDATA: /* data -- text of mail */ 2534976Seric if (!hasmail) 2544549Seric { 2554976Seric message("503", "Need MAIL command"); 2564976Seric break; 2574549Seric } 2584713Seric else if (rcps <= 0) 2594549Seric { 2604976Seric message("503", "Need RCPT (recipient)"); 2614976Seric break; 2624549Seric } 2634976Seric 2644976Seric /* collect the text of the message */ 2654976Seric collect(TRUE); 2664976Seric if (Errors != 0) 2674976Seric break; 2684976Seric 2698238Seric /* 2708238Seric ** Arrange to send to everyone. 2718238Seric ** If sending to multiple people, mail back 2728238Seric ** errors rather than reporting directly. 2738238Seric ** In any case, don't mail back errors for 2748238Seric ** anything that has happened up to 2758238Seric ** now (the other end will do this). 27610197Seric ** Truncate our transcript -- the mail has gotten 27710197Seric ** to us successfully, and if we have 27810197Seric ** to mail this back, it will be easier 27910197Seric ** on the reader. 2808238Seric ** Then send to everyone. 2818238Seric ** Finally give a reply code. If an error has 2828238Seric ** already been given, don't mail a 2838238Seric ** message back. 2849339Seric ** We goose error returns by clearing error bit. 2858238Seric */ 2868238Seric 2874976Seric if (rcps != 1) 2889378Seric { 2899378Seric HoldErrs = TRUE; 290*16886Seric ErrorMode = EM_MAIL; 2919378Seric } 2929339Seric CurEnv->e_flags &= ~EF_FATALERRS; 29310197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 2944976Seric 2954976Seric /* send to all recipients */ 29614877Seric sendall(CurEnv, SM_DEFAULT); 2976907Seric CurEnv->e_to = NULL; 2984976Seric 2998238Seric /* issue success if appropriate and reset */ 3008238Seric if (Errors == 0 || HoldErrs) 3019283Seric message("250", "Ok"); 3028238Seric else 3039339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3049339Seric 3059339Seric /* if in a child, pop back to our parent */ 3069339Seric if (InChild) 3079339Seric finis(); 3084549Seric break; 3094549Seric 3104549Seric case CMDRSET: /* rset -- reset state */ 3114549Seric message("250", "Reset state"); 3129339Seric if (InChild) 3139339Seric finis(); 3149339Seric break; 3154549Seric 3164549Seric case CMDVRFY: /* vrfy -- verify address */ 3179339Seric if (runinchild("SMTP-VRFY") > 0) 3189339Seric break; 3195003Seric vrfyqueue = NULL; 3207762Seric QuickAbort = TRUE; 3219619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3227762Seric if (Errors != 0) 3239339Seric { 3249339Seric if (InChild) 3259339Seric finis(); 3267762Seric break; 3279339Seric } 3285003Seric while (vrfyqueue != NULL) 3295003Seric { 3305003Seric register ADDRESS *a = vrfyqueue->q_next; 3315003Seric char *code; 3325003Seric 3337685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3345003Seric a = a->q_next; 3355003Seric 3367685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3375003Seric { 3385003Seric if (a != NULL) 3395003Seric code = "250-"; 3405003Seric else 3415003Seric code = "250"; 3425003Seric if (vrfyqueue->q_fullname == NULL) 3435003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3445003Seric else 3455003Seric message(code, "%s <%s>", 3465003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 3475003Seric } 3485003Seric else if (a == NULL) 3495003Seric message("554", "Self destructive alias loop"); 3505003Seric vrfyqueue = a; 3515003Seric } 3529339Seric if (InChild) 3539339Seric finis(); 3544549Seric break; 3554549Seric 3564549Seric case CMDHELP: /* help -- give user info */ 3574577Seric if (*p == '\0') 3584577Seric p = "SMTP"; 3594577Seric help(p); 3604549Seric break; 3614549Seric 3624549Seric case CMDNOOP: /* noop -- do nothing */ 3634549Seric message("200", "OK"); 3644549Seric break; 3654549Seric 3664549Seric case CMDQUIT: /* quit -- leave mail */ 3674549Seric message("221", "%s closing connection", HostName); 3689339Seric if (InChild) 3699339Seric ExitStat = EX_QUIT; 3704549Seric finis(); 3714549Seric 3728544Seric case CMDVERB: /* set verbose mode */ 3738544Seric Verbose = TRUE; 3748544Seric message("200", "Verbose mode"); 3758544Seric break; 3768544Seric 3779314Seric case CMDONEX: /* doing one transaction only */ 3789378Seric OneXact = TRUE; 3799314Seric message("200", "Only one transaction"); 3809314Seric break; 3819314Seric 3825003Seric # ifdef DEBUG 3839339Seric case CMDDBGQSHOW: /* show queues */ 3846907Seric printf("Send Queue="); 3856907Seric printaddr(CurEnv->e_sendqueue, TRUE); 3865003Seric break; 3877275Seric 3887275Seric case CMDDBGDEBUG: /* set debug mode */ 3897676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 3907676Seric tTflag(p); 3917676Seric message("200", "Debug set"); 3927275Seric break; 3937275Seric 3947282Seric case CMDDBGKILL: /* kill the parent */ 3958544Seric if (!iswiz()) 3968544Seric break; 3977282Seric if (kill(MotherPid, SIGTERM) >= 0) 3987282Seric message("200", "Mother is dead"); 3997282Seric else 4007282Seric message("500", "Can't kill Mom"); 4017282Seric break; 4028544Seric 4039339Seric case CMDDBGSHELL: /* give us an interactive shell */ 4049339Seric if (!iswiz()) 4059339Seric break; 4069378Seric if (fileno(InChannel) != 0) 4079378Seric { 4089378Seric (void) close(0); 4099378Seric (void) dup(fileno(InChannel)); 41010346Seric if (fileno(InChannel) != fileno(OutChannel)) 41110346Seric (void) fclose(InChannel); 4129378Seric InChannel = stdin; 4139378Seric } 4149378Seric if (fileno(OutChannel) != 1) 4159378Seric { 4169378Seric (void) close(1); 4179378Seric (void) dup(fileno(OutChannel)); 4189378Seric (void) fclose(OutChannel); 4199378Seric OutChannel = stdout; 4209378Seric } 42110346Seric (void) close(2); 42210346Seric (void) dup(1); 4239339Seric execl("/bin/csh", "sendmail", 0); 4249339Seric execl("/bin/sh", "sendmail", 0); 4259339Seric message("500", "Can't"); 4269378Seric exit(EX_UNAVAILABLE); 4279339Seric 4288544Seric case CMDDBGWIZ: /* become a wizard */ 4298544Seric if (WizWord != NULL) 4308544Seric { 4318544Seric char seed[3]; 4328544Seric extern char *crypt(); 4338544Seric 4348544Seric strncpy(seed, WizWord, 2); 43515596Seric if (strcmp(WizWord, crypt(p, seed)) == 0) 4368544Seric { 43715596Seric IsWiz = TRUE; 43815596Seric message("200", "Please pass, oh mighty wizard"); 4398544Seric break; 4408544Seric } 4418544Seric } 44215596Seric message("500", "You are no wizard!"); 4438544Seric break; 4445003Seric # endif DEBUG 4455003Seric 4464549Seric case CMDERROR: /* unknown command */ 4474549Seric message("500", "Command unrecognized"); 4484549Seric break; 4494549Seric 4504549Seric default: 4514549Seric syserr("smtp: unknown code %d", c->cmdcode); 4524549Seric break; 4534549Seric } 4544549Seric } 4554549Seric } 4564549Seric /* 4574549Seric ** SKIPWORD -- skip a fixed word. 4584549Seric ** 4594549Seric ** Parameters: 4604549Seric ** p -- place to start looking. 4614549Seric ** w -- word to skip. 4624549Seric ** 4634549Seric ** Returns: 4644549Seric ** p following w. 4654549Seric ** NULL on error. 4664549Seric ** 4674549Seric ** Side Effects: 4684549Seric ** clobbers the p data area. 4694549Seric */ 4704549Seric 4714549Seric static char * 4724549Seric skipword(p, w) 4734549Seric register char *p; 4744549Seric char *w; 4754549Seric { 4764549Seric register char *q; 4774549Seric extern bool sameword(); 4784549Seric 4794549Seric /* find beginning of word */ 4804549Seric while (isspace(*p)) 4814549Seric p++; 4824549Seric q = p; 4834549Seric 4844549Seric /* find end of word */ 4854549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 4864549Seric p++; 4874549Seric while (isspace(*p)) 4884549Seric *p++ = '\0'; 4894549Seric if (*p != ':') 4904549Seric { 4914549Seric syntax: 4924549Seric message("501", "Syntax error"); 4934549Seric Errors++; 4944549Seric return (NULL); 4954549Seric } 4964549Seric *p++ = '\0'; 4974549Seric while (isspace(*p)) 4984549Seric p++; 4994549Seric 5004549Seric /* see if the input word matches desired word */ 5014549Seric if (!sameword(q, w)) 5024549Seric goto syntax; 5034549Seric 5044549Seric return (p); 5054549Seric } 5064577Seric /* 5074577Seric ** HELP -- implement the HELP command. 5084577Seric ** 5094577Seric ** Parameters: 5104577Seric ** topic -- the topic we want help for. 5114577Seric ** 5124577Seric ** Returns: 5134577Seric ** none. 5144577Seric ** 5154577Seric ** Side Effects: 5164577Seric ** outputs the help file to message output. 5174577Seric */ 5184577Seric 5194577Seric help(topic) 5204577Seric char *topic; 5214577Seric { 5224577Seric register FILE *hf; 5234577Seric int len; 5244577Seric char buf[MAXLINE]; 5254577Seric bool noinfo; 5264577Seric 5278269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5284577Seric { 5294577Seric /* no help */ 53011931Seric errno = 0; 5314577Seric message("502", "HELP not implemented"); 5324577Seric return; 5334577Seric } 5344577Seric 5354577Seric len = strlen(topic); 5364577Seric makelower(topic); 5374577Seric noinfo = TRUE; 5384577Seric 5394577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5404577Seric { 5414577Seric if (strncmp(buf, topic, len) == 0) 5424577Seric { 5434577Seric register char *p; 5444577Seric 5454577Seric p = index(buf, '\t'); 5464577Seric if (p == NULL) 5474577Seric p = buf; 5484577Seric else 5494577Seric p++; 5504577Seric fixcrlf(p, TRUE); 5514577Seric message("214-", p); 5524577Seric noinfo = FALSE; 5534577Seric } 5544577Seric } 5554577Seric 5564577Seric if (noinfo) 5574577Seric message("504", "HELP topic unknown"); 5584577Seric else 5594577Seric message("214", "End of HELP info"); 5604628Seric (void) fclose(hf); 5614577Seric } 5628544Seric /* 5638544Seric ** ISWIZ -- tell us if we are a wizard 5648544Seric ** 5658544Seric ** If not, print a nasty message. 5668544Seric ** 5678544Seric ** Parameters: 5688544Seric ** none. 5698544Seric ** 5708544Seric ** Returns: 5718544Seric ** TRUE if we are a wizard. 5728544Seric ** FALSE if we are not a wizard. 5738544Seric ** 5748544Seric ** Side Effects: 5758544Seric ** Prints a 500 exit stat if we are not a wizard. 5768544Seric */ 5775181Seric 5788544Seric bool 5798544Seric iswiz() 5808544Seric { 5818544Seric if (!IsWiz) 5828544Seric message("500", "Mere mortals musn't mutter that mantra"); 5838544Seric return (IsWiz); 5848544Seric } 5859339Seric /* 5869339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5879339Seric ** 5889339Seric ** Parameters: 5899339Seric ** label -- a string used in error messages 5909339Seric ** 5919339Seric ** Returns: 5929339Seric ** zero in the child 5939339Seric ** one in the parent 5949339Seric ** 5959339Seric ** Side Effects: 5969339Seric ** none. 5979339Seric */ 5988544Seric 5999339Seric runinchild(label) 6009339Seric char *label; 6019339Seric { 6029339Seric int childpid; 6039339Seric 60416158Seric if (!OneXact) 6059339Seric { 60616158Seric childpid = dofork(); 60716158Seric if (childpid < 0) 60816158Seric { 60916158Seric syserr("%s: cannot fork", label); 61016158Seric return (1); 61116158Seric } 61216158Seric if (childpid > 0) 61316158Seric { 61416158Seric auto int st; 6159339Seric 61616158Seric /* parent -- wait for child to complete */ 61716158Seric st = waitfor(childpid); 61816158Seric if (st == -1) 61916158Seric syserr("%s: lost child", label); 6209339Seric 62116158Seric /* if we exited on a QUIT command, complete the process */ 62216158Seric if (st == (EX_QUIT << 8)) 62316158Seric finis(); 6249339Seric 62516158Seric return (1); 62616158Seric } 62716158Seric else 62816158Seric { 62916158Seric /* child */ 63016158Seric InChild = TRUE; 63116158Seric } 6329339Seric } 63315256Seric 63416158Seric /* child (or ONEX command specified) */ 63516158Seric clearenvelope(CurEnv); 63615256Seric 63716158Seric /* open alias database */ 63816158Seric initaliases(AliasFile, FALSE); 63916158Seric 64016158Seric return (0); 6419339Seric } 6429339Seric 6435181Seric # endif SMTP 644