19339Seric # include <errno.h> 24549Seric # include "sendmail.h" 311728Seric # include <signal.h> 44549Seric 55181Seric # ifndef SMTP 6*15596Seric SCCSID(@(#)srvrsmtp.c 4.5 11/26/83 (no SMTP)); 75181Seric # else SMTP 84556Seric 9*15596Seric SCCSID(@(#)srvrsmtp.c 4.5 11/26/83); 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 */ 76*15596Seric 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(); 11210708Seric expand("$e", 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; 23112612Seric a = parseaddr(p, (ADDRESS *) NULL, 1); 23212612Seric if (a == NULL) 23312612Seric break; 23412612Seric a = recipient(a, &CurEnv->e_sendqueue); 23512612Seric if (Errors != 0) 23612612Seric break; 23712612Seric 23812612Seric /* no errors during parsing, but might be a duplicate */ 23912612Seric CurEnv->e_to = p; 24012612Seric if (!bitset(QBADADDR, a->q_flags)) 24112612Seric message("250", "Recipient ok"); 24212612Seric else 2434549Seric { 24412612Seric /* punt -- should keep message in ADDRESS.... */ 24512612Seric message("550", "Addressee unknown"); 2464549Seric } 24712612Seric CurEnv->e_to = NULL; 24812612Seric rcps++; 2494549Seric break; 2504549Seric 2514549Seric case CMDDATA: /* data -- text of mail */ 2524976Seric if (!hasmail) 2534549Seric { 2544976Seric message("503", "Need MAIL command"); 2554976Seric break; 2564549Seric } 2574713Seric else if (rcps <= 0) 2584549Seric { 2594976Seric message("503", "Need RCPT (recipient)"); 2604976Seric break; 2614549Seric } 2624976Seric 2634976Seric /* collect the text of the message */ 2644976Seric collect(TRUE); 2654976Seric if (Errors != 0) 2664976Seric break; 2674976Seric 2688238Seric /* 2698238Seric ** Arrange to send to everyone. 2708238Seric ** If sending to multiple people, mail back 2718238Seric ** errors rather than reporting directly. 2728238Seric ** In any case, don't mail back errors for 2738238Seric ** anything that has happened up to 2748238Seric ** now (the other end will do this). 27510197Seric ** Truncate our transcript -- the mail has gotten 27610197Seric ** to us successfully, and if we have 27710197Seric ** to mail this back, it will be easier 27810197Seric ** on the reader. 2798238Seric ** Then send to everyone. 2808238Seric ** Finally give a reply code. If an error has 2818238Seric ** already been given, don't mail a 2828238Seric ** message back. 2839339Seric ** We goose error returns by clearing error bit. 2848238Seric */ 2858238Seric 2864976Seric if (rcps != 1) 2879378Seric { 2889378Seric HoldErrs = TRUE; 2899378Seric ErrorMode == EM_MAIL; 2909378Seric } 2919339Seric CurEnv->e_flags &= ~EF_FATALERRS; 29210197Seric CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 2934976Seric 2944976Seric /* send to all recipients */ 29514877Seric sendall(CurEnv, SM_DEFAULT); 2966907Seric CurEnv->e_to = NULL; 2974976Seric 2988238Seric /* issue success if appropriate and reset */ 2998238Seric if (Errors == 0 || HoldErrs) 3009283Seric message("250", "Ok"); 3018238Seric else 3029339Seric CurEnv->e_flags &= ~EF_FATALERRS; 3039339Seric 3049339Seric /* if in a child, pop back to our parent */ 3059339Seric if (InChild) 3069339Seric finis(); 3074549Seric break; 3084549Seric 3094549Seric case CMDRSET: /* rset -- reset state */ 3104549Seric message("250", "Reset state"); 3119339Seric if (InChild) 3129339Seric finis(); 3139339Seric break; 3144549Seric 3154549Seric case CMDVRFY: /* vrfy -- verify address */ 3169339Seric if (runinchild("SMTP-VRFY") > 0) 3179339Seric break; 3185003Seric vrfyqueue = NULL; 3197762Seric QuickAbort = TRUE; 3209619Seric sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 3217762Seric if (Errors != 0) 3229339Seric { 3239339Seric if (InChild) 3249339Seric finis(); 3257762Seric break; 3269339Seric } 3275003Seric while (vrfyqueue != NULL) 3285003Seric { 3295003Seric register ADDRESS *a = vrfyqueue->q_next; 3305003Seric char *code; 3315003Seric 3327685Seric while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 3335003Seric a = a->q_next; 3345003Seric 3357685Seric if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 3365003Seric { 3375003Seric if (a != NULL) 3385003Seric code = "250-"; 3395003Seric else 3405003Seric code = "250"; 3415003Seric if (vrfyqueue->q_fullname == NULL) 3425003Seric message(code, "<%s>", vrfyqueue->q_paddr); 3435003Seric else 3445003Seric message(code, "%s <%s>", 3455003Seric vrfyqueue->q_fullname, vrfyqueue->q_paddr); 3465003Seric } 3475003Seric else if (a == NULL) 3485003Seric message("554", "Self destructive alias loop"); 3495003Seric vrfyqueue = a; 3505003Seric } 3519339Seric if (InChild) 3529339Seric finis(); 3534549Seric break; 3544549Seric 3554549Seric case CMDHELP: /* help -- give user info */ 3564577Seric if (*p == '\0') 3574577Seric p = "SMTP"; 3584577Seric help(p); 3594549Seric break; 3604549Seric 3614549Seric case CMDNOOP: /* noop -- do nothing */ 3624549Seric message("200", "OK"); 3634549Seric break; 3644549Seric 3654549Seric case CMDQUIT: /* quit -- leave mail */ 3664549Seric message("221", "%s closing connection", HostName); 3679339Seric if (InChild) 3689339Seric ExitStat = EX_QUIT; 3694549Seric finis(); 3704549Seric 3718544Seric case CMDVERB: /* set verbose mode */ 3728544Seric Verbose = TRUE; 3738544Seric message("200", "Verbose mode"); 3748544Seric break; 3758544Seric 3769314Seric case CMDONEX: /* doing one transaction only */ 3779378Seric OneXact = TRUE; 3789314Seric message("200", "Only one transaction"); 3799314Seric break; 3809314Seric 3815003Seric # ifdef DEBUG 3829339Seric case CMDDBGQSHOW: /* show queues */ 3836907Seric printf("Send Queue="); 3846907Seric printaddr(CurEnv->e_sendqueue, TRUE); 3855003Seric break; 3867275Seric 3877275Seric case CMDDBGDEBUG: /* set debug mode */ 3887676Seric tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 3897676Seric tTflag(p); 3907676Seric message("200", "Debug set"); 3917275Seric break; 3927275Seric 3937282Seric case CMDDBGKILL: /* kill the parent */ 3948544Seric if (!iswiz()) 3958544Seric break; 3967282Seric if (kill(MotherPid, SIGTERM) >= 0) 3977282Seric message("200", "Mother is dead"); 3987282Seric else 3997282Seric message("500", "Can't kill Mom"); 4007282Seric break; 4018544Seric 4029339Seric case CMDDBGSHELL: /* give us an interactive shell */ 4039339Seric if (!iswiz()) 4049339Seric break; 4059378Seric if (fileno(InChannel) != 0) 4069378Seric { 4079378Seric (void) close(0); 4089378Seric (void) dup(fileno(InChannel)); 40910346Seric if (fileno(InChannel) != fileno(OutChannel)) 41010346Seric (void) fclose(InChannel); 4119378Seric InChannel = stdin; 4129378Seric } 4139378Seric if (fileno(OutChannel) != 1) 4149378Seric { 4159378Seric (void) close(1); 4169378Seric (void) dup(fileno(OutChannel)); 4179378Seric (void) fclose(OutChannel); 4189378Seric OutChannel = stdout; 4199378Seric } 42010346Seric (void) close(2); 42110346Seric (void) dup(1); 4229339Seric execl("/bin/csh", "sendmail", 0); 4239339Seric execl("/bin/sh", "sendmail", 0); 4249339Seric message("500", "Can't"); 4259378Seric exit(EX_UNAVAILABLE); 4269339Seric 4278544Seric case CMDDBGWIZ: /* become a wizard */ 4288544Seric if (WizWord != NULL) 4298544Seric { 4308544Seric char seed[3]; 4318544Seric extern char *crypt(); 4328544Seric 4338544Seric strncpy(seed, WizWord, 2); 434*15596Seric if (strcmp(WizWord, crypt(p, seed)) == 0) 4358544Seric { 436*15596Seric IsWiz = TRUE; 437*15596Seric message("200", "Please pass, oh mighty wizard"); 4388544Seric break; 4398544Seric } 4408544Seric } 441*15596Seric message("500", "You are no wizard!"); 4428544Seric break; 4435003Seric # endif DEBUG 4445003Seric 4454549Seric case CMDERROR: /* unknown command */ 4464549Seric message("500", "Command unrecognized"); 4474549Seric break; 4484549Seric 4494549Seric default: 4504549Seric syserr("smtp: unknown code %d", c->cmdcode); 4514549Seric break; 4524549Seric } 4534549Seric } 4544549Seric } 4554549Seric /* 4564549Seric ** SKIPWORD -- skip a fixed word. 4574549Seric ** 4584549Seric ** Parameters: 4594549Seric ** p -- place to start looking. 4604549Seric ** w -- word to skip. 4614549Seric ** 4624549Seric ** Returns: 4634549Seric ** p following w. 4644549Seric ** NULL on error. 4654549Seric ** 4664549Seric ** Side Effects: 4674549Seric ** clobbers the p data area. 4684549Seric */ 4694549Seric 4704549Seric static char * 4714549Seric skipword(p, w) 4724549Seric register char *p; 4734549Seric char *w; 4744549Seric { 4754549Seric register char *q; 4764549Seric extern bool sameword(); 4774549Seric 4784549Seric /* find beginning of word */ 4794549Seric while (isspace(*p)) 4804549Seric p++; 4814549Seric q = p; 4824549Seric 4834549Seric /* find end of word */ 4844549Seric while (*p != '\0' && *p != ':' && !isspace(*p)) 4854549Seric p++; 4864549Seric while (isspace(*p)) 4874549Seric *p++ = '\0'; 4884549Seric if (*p != ':') 4894549Seric { 4904549Seric syntax: 4914549Seric message("501", "Syntax error"); 4924549Seric Errors++; 4934549Seric return (NULL); 4944549Seric } 4954549Seric *p++ = '\0'; 4964549Seric while (isspace(*p)) 4974549Seric p++; 4984549Seric 4994549Seric /* see if the input word matches desired word */ 5004549Seric if (!sameword(q, w)) 5014549Seric goto syntax; 5024549Seric 5034549Seric return (p); 5044549Seric } 5054577Seric /* 5064577Seric ** HELP -- implement the HELP command. 5074577Seric ** 5084577Seric ** Parameters: 5094577Seric ** topic -- the topic we want help for. 5104577Seric ** 5114577Seric ** Returns: 5124577Seric ** none. 5134577Seric ** 5144577Seric ** Side Effects: 5154577Seric ** outputs the help file to message output. 5164577Seric */ 5174577Seric 5184577Seric help(topic) 5194577Seric char *topic; 5204577Seric { 5214577Seric register FILE *hf; 5224577Seric int len; 5234577Seric char buf[MAXLINE]; 5244577Seric bool noinfo; 5254577Seric 5268269Seric if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 5274577Seric { 5284577Seric /* no help */ 52911931Seric errno = 0; 5304577Seric message("502", "HELP not implemented"); 5314577Seric return; 5324577Seric } 5334577Seric 5344577Seric len = strlen(topic); 5354577Seric makelower(topic); 5364577Seric noinfo = TRUE; 5374577Seric 5384577Seric while (fgets(buf, sizeof buf, hf) != NULL) 5394577Seric { 5404577Seric if (strncmp(buf, topic, len) == 0) 5414577Seric { 5424577Seric register char *p; 5434577Seric 5444577Seric p = index(buf, '\t'); 5454577Seric if (p == NULL) 5464577Seric p = buf; 5474577Seric else 5484577Seric p++; 5494577Seric fixcrlf(p, TRUE); 5504577Seric message("214-", p); 5514577Seric noinfo = FALSE; 5524577Seric } 5534577Seric } 5544577Seric 5554577Seric if (noinfo) 5564577Seric message("504", "HELP topic unknown"); 5574577Seric else 5584577Seric message("214", "End of HELP info"); 5594628Seric (void) fclose(hf); 5604577Seric } 5618544Seric /* 5628544Seric ** ISWIZ -- tell us if we are a wizard 5638544Seric ** 5648544Seric ** If not, print a nasty message. 5658544Seric ** 5668544Seric ** Parameters: 5678544Seric ** none. 5688544Seric ** 5698544Seric ** Returns: 5708544Seric ** TRUE if we are a wizard. 5718544Seric ** FALSE if we are not a wizard. 5728544Seric ** 5738544Seric ** Side Effects: 5748544Seric ** Prints a 500 exit stat if we are not a wizard. 5758544Seric */ 5765181Seric 5778544Seric bool 5788544Seric iswiz() 5798544Seric { 5808544Seric if (!IsWiz) 5818544Seric message("500", "Mere mortals musn't mutter that mantra"); 5828544Seric return (IsWiz); 5838544Seric } 5849339Seric /* 5859339Seric ** RUNINCHILD -- return twice -- once in the child, then in the parent again 5869339Seric ** 5879339Seric ** Parameters: 5889339Seric ** label -- a string used in error messages 5899339Seric ** 5909339Seric ** Returns: 5919339Seric ** zero in the child 5929339Seric ** one in the parent 5939339Seric ** 5949339Seric ** Side Effects: 5959339Seric ** none. 5969339Seric */ 5978544Seric 5989339Seric runinchild(label) 5999339Seric char *label; 6009339Seric { 6019339Seric int childpid; 6029339Seric 6039378Seric if (OneXact) 6049378Seric return (0); 6059378Seric 6069339Seric childpid = dofork(); 6079339Seric if (childpid < 0) 6089339Seric { 6099339Seric syserr("%s: cannot fork", label); 6109339Seric return (1); 6119339Seric } 6129339Seric if (childpid > 0) 6139339Seric { 6149339Seric auto int st; 6159339Seric 6169378Seric /* parent -- wait for child to complete */ 6179378Seric st = waitfor(childpid); 6189378Seric if (st == -1) 6199339Seric syserr("%s: lost child", label); 6209339Seric 6219339Seric /* if we exited on a QUIT command, complete the process */ 6229339Seric if (st == (EX_QUIT << 8)) 6239339Seric finis(); 6249339Seric 6259339Seric return (1); 6269339Seric } 6279339Seric else 6289339Seric { 6299339Seric /* child */ 6309339Seric InChild = TRUE; 6319545Seric clearenvelope(CurEnv); 63215256Seric 63315256Seric /* open alias database */ 63415256Seric initaliases(AliasFile, FALSE); 63515256Seric 6369339Seric return (0); 6379339Seric } 6389339Seric } 6399339Seric 6405181Seric # endif SMTP 641