1 # include <errno.h> 2 # include "sendmail.h" 3 # include <signal.h> 4 5 # ifndef SMTP 6 SCCSID(@(#)srvrsmtp.c 4.9 08/11/84 (no SMTP)); 7 # else SMTP 8 9 SCCSID(@(#)srvrsmtp.c 4.9 08/11/84); 10 11 /* 12 ** SMTP -- run the SMTP protocol. 13 ** 14 ** Parameters: 15 ** none. 16 ** 17 ** Returns: 18 ** never. 19 ** 20 ** Side Effects: 21 ** Reads commands from the input channel and processes 22 ** them. 23 */ 24 25 struct cmd 26 { 27 char *cmdname; /* command name */ 28 int cmdcode; /* internal code, see below */ 29 }; 30 31 /* values for cmdcode */ 32 # define CMDERROR 0 /* bad command */ 33 # define CMDMAIL 1 /* mail -- designate sender */ 34 # define CMDRCPT 2 /* rcpt -- designate recipient */ 35 # define CMDDATA 3 /* data -- send message text */ 36 # define CMDRSET 4 /* rset -- reset state */ 37 # define CMDVRFY 5 /* vrfy -- verify address */ 38 # define CMDHELP 6 /* help -- give usage info */ 39 # define CMDNOOP 7 /* noop -- do nothing */ 40 # define CMDQUIT 8 /* quit -- close connection and die */ 41 # define CMDHELO 9 /* helo -- be polite */ 42 # define CMDDBGQSHOW 10 /* showq -- show send queue (DEBUG) */ 43 # define CMDDBGDEBUG 11 /* debug -- set debug mode */ 44 # define CMDVERB 12 /* verb -- go into verbose mode */ 45 # define CMDDBGKILL 13 /* kill -- kill sendmail */ 46 # define CMDDBGWIZ 14 /* wiz -- become a wizard */ 47 # define CMDONEX 15 /* onex -- sending one transaction only */ 48 # define CMDDBGSHELL 16 /* shell -- give us a shell */ 49 50 static struct cmd CmdTab[] = 51 { 52 "mail", CMDMAIL, 53 "rcpt", CMDRCPT, 54 "data", CMDDATA, 55 "rset", CMDRSET, 56 "vrfy", CMDVRFY, 57 "expn", CMDVRFY, 58 "help", CMDHELP, 59 "noop", CMDNOOP, 60 "quit", CMDQUIT, 61 "helo", CMDHELO, 62 "verb", CMDVERB, 63 "onex", CMDONEX, 64 # ifdef DEBUG 65 "showq", CMDDBGQSHOW, 66 "debug", CMDDBGDEBUG, 67 "kill", CMDDBGKILL, 68 "wiz", CMDDBGWIZ, 69 "shell", CMDDBGSHELL, 70 # endif DEBUG 71 NULL, CMDERROR, 72 }; 73 74 # ifdef DEBUG 75 bool IsWiz = FALSE; /* set if we are a wizard */ 76 char *WizWord; /* the wizard word to compare against */ 77 # endif DEBUG 78 bool InChild = FALSE; /* true if running in a subprocess */ 79 bool OneXact = FALSE; /* one xaction only this run */ 80 char *RealHostName = NULL; /* verified hostname, set in daemon.c */ 81 82 #define EX_QUIT 22 /* special code for QUIT command */ 83 84 smtp() 85 { 86 register char *p; 87 register struct cmd *c; 88 char *cmd; 89 extern char *skipword(); 90 extern bool sameword(); 91 bool hasmail; /* mail command received */ 92 int rcps; /* number of recipients */ 93 auto ADDRESS *vrfyqueue; 94 ADDRESS *a; 95 char inp[MAXLINE]; 96 extern char Version[]; 97 extern tick(); 98 extern bool iswiz(); 99 extern char *arpadate(); 100 extern char *macvalue(); 101 extern ADDRESS *recipient(); 102 103 hasmail = FALSE; 104 rcps = 0; 105 if (OutChannel != stdout) 106 { 107 /* arrange for debugging output to go to remote host */ 108 (void) close(1); 109 (void) dup(fileno(OutChannel)); 110 } 111 settime(); 112 expand("\001e", inp, &inp[sizeof inp], CurEnv); 113 message("220", inp); 114 for (;;) 115 { 116 /* arrange for backout */ 117 if (setjmp(TopFrame) > 0 && InChild) 118 finis(); 119 QuickAbort = FALSE; 120 HoldErrs = FALSE; 121 122 /* setup for the read */ 123 CurEnv->e_to = NULL; 124 Errors = 0; 125 (void) fflush(stdout); 126 127 /* read the input line */ 128 p = sfgets(inp, sizeof inp, InChannel); 129 130 /* handle errors */ 131 if (p == NULL) 132 { 133 /* end of file, just die */ 134 message("421", "%s Lost input channel", HostName); 135 finis(); 136 } 137 138 /* clean up end of line */ 139 fixcrlf(inp, TRUE); 140 141 /* echo command to transcript */ 142 if (CurEnv->e_xfp != NULL) 143 fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 144 145 /* break off command */ 146 for (p = inp; isspace(*p); p++) 147 continue; 148 cmd = p; 149 while (*++p != '\0' && !isspace(*p)) 150 continue; 151 if (*p != '\0') 152 *p++ = '\0'; 153 154 /* decode command */ 155 for (c = CmdTab; c->cmdname != NULL; c++) 156 { 157 if (sameword(c->cmdname, cmd)) 158 break; 159 } 160 161 /* process command */ 162 switch (c->cmdcode) 163 { 164 case CMDHELO: /* hello -- introduce yourself */ 165 if (sameword(p, HostName)) 166 { 167 /* connected to an echo server */ 168 message("553", "%s I refuse to talk to myself", 169 HostName); 170 break; 171 } 172 if (RealHostName != NULL && !sameword(p, RealHostName)) 173 { 174 char buf[MAXNAME]; 175 176 (void) sprintf(buf, "%s (%s)", p, RealHostName); 177 define('s', newstr(buf), CurEnv); 178 } 179 else 180 define('s', newstr(p), CurEnv); 181 message("250", "%s Hello %s, pleased to meet you", 182 HostName, p); 183 break; 184 185 case CMDMAIL: /* mail -- designate sender */ 186 /* force a sending host even if no HELO given */ 187 if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 188 define('s', RealHostName, CurEnv); 189 190 /* check for validity of this command */ 191 if (hasmail) 192 { 193 message("503", "Sender already specified"); 194 break; 195 } 196 if (InChild) 197 { 198 syserr("Nested MAIL command"); 199 exit(0); 200 } 201 202 /* fork a subprocess to process this command */ 203 if (runinchild("SMTP-MAIL") > 0) 204 break; 205 initsys(); 206 207 /* child -- go do the processing */ 208 p = skipword(p, "from"); 209 if (p == NULL) 210 break; 211 setsender(p); 212 if (Errors == 0) 213 { 214 message("250", "Sender ok"); 215 hasmail = TRUE; 216 } 217 else if (InChild) 218 finis(); 219 break; 220 221 case CMDRCPT: /* rcpt -- designate recipient */ 222 if (setjmp(TopFrame) > 0) 223 { 224 CurEnv->e_flags &= ~EF_FATALERRS; 225 break; 226 } 227 QuickAbort = TRUE; 228 p = skipword(p, "to"); 229 if (p == NULL) 230 break; 231 a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 232 if (a == NULL) 233 break; 234 a->q_flags |= QPRIMARY; 235 a = recipient(a, &CurEnv->e_sendqueue); 236 if (Errors != 0) 237 break; 238 239 /* no errors during parsing, but might be a duplicate */ 240 CurEnv->e_to = p; 241 if (!bitset(QBADADDR, a->q_flags)) 242 message("250", "Recipient ok"); 243 else 244 { 245 /* punt -- should keep message in ADDRESS.... */ 246 message("550", "Addressee unknown"); 247 } 248 CurEnv->e_to = NULL; 249 rcps++; 250 break; 251 252 case CMDDATA: /* data -- text of mail */ 253 if (!hasmail) 254 { 255 message("503", "Need MAIL command"); 256 break; 257 } 258 else if (rcps <= 0) 259 { 260 message("503", "Need RCPT (recipient)"); 261 break; 262 } 263 264 /* collect the text of the message */ 265 collect(TRUE); 266 if (Errors != 0) 267 break; 268 269 /* 270 ** Arrange to send to everyone. 271 ** If sending to multiple people, mail back 272 ** errors rather than reporting directly. 273 ** In any case, don't mail back errors for 274 ** anything that has happened up to 275 ** now (the other end will do this). 276 ** Truncate our transcript -- the mail has gotten 277 ** to us successfully, and if we have 278 ** to mail this back, it will be easier 279 ** on the reader. 280 ** Then send to everyone. 281 ** Finally give a reply code. If an error has 282 ** already been given, don't mail a 283 ** message back. 284 ** We goose error returns by clearing error bit. 285 */ 286 287 if (rcps != 1) 288 { 289 HoldErrs = TRUE; 290 ErrorMode = EM_MAIL; 291 } 292 CurEnv->e_flags &= ~EF_FATALERRS; 293 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 294 295 /* send to all recipients */ 296 sendall(CurEnv, SM_DEFAULT); 297 CurEnv->e_to = NULL; 298 299 /* issue success if appropriate and reset */ 300 if (Errors == 0 || HoldErrs) 301 message("250", "Ok"); 302 else 303 CurEnv->e_flags &= ~EF_FATALERRS; 304 305 /* if in a child, pop back to our parent */ 306 if (InChild) 307 finis(); 308 break; 309 310 case CMDRSET: /* rset -- reset state */ 311 message("250", "Reset state"); 312 if (InChild) 313 finis(); 314 break; 315 316 case CMDVRFY: /* vrfy -- verify address */ 317 if (runinchild("SMTP-VRFY") > 0) 318 break; 319 vrfyqueue = NULL; 320 QuickAbort = TRUE; 321 sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 322 if (Errors != 0) 323 { 324 if (InChild) 325 finis(); 326 break; 327 } 328 while (vrfyqueue != NULL) 329 { 330 register ADDRESS *a = vrfyqueue->q_next; 331 char *code; 332 333 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 334 a = a->q_next; 335 336 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 337 { 338 if (a != NULL) 339 code = "250-"; 340 else 341 code = "250"; 342 if (vrfyqueue->q_fullname == NULL) 343 message(code, "<%s>", vrfyqueue->q_paddr); 344 else 345 message(code, "%s <%s>", 346 vrfyqueue->q_fullname, vrfyqueue->q_paddr); 347 } 348 else if (a == NULL) 349 message("554", "Self destructive alias loop"); 350 vrfyqueue = a; 351 } 352 if (InChild) 353 finis(); 354 break; 355 356 case CMDHELP: /* help -- give user info */ 357 if (*p == '\0') 358 p = "SMTP"; 359 help(p); 360 break; 361 362 case CMDNOOP: /* noop -- do nothing */ 363 message("200", "OK"); 364 break; 365 366 case CMDQUIT: /* quit -- leave mail */ 367 message("221", "%s closing connection", HostName); 368 if (InChild) 369 ExitStat = EX_QUIT; 370 finis(); 371 372 case CMDVERB: /* set verbose mode */ 373 Verbose = TRUE; 374 message("200", "Verbose mode"); 375 break; 376 377 case CMDONEX: /* doing one transaction only */ 378 OneXact = TRUE; 379 message("200", "Only one transaction"); 380 break; 381 382 # ifdef DEBUG 383 case CMDDBGQSHOW: /* show queues */ 384 printf("Send Queue="); 385 printaddr(CurEnv->e_sendqueue, TRUE); 386 break; 387 388 case CMDDBGDEBUG: /* set debug mode */ 389 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 390 tTflag(p); 391 message("200", "Debug set"); 392 break; 393 394 case CMDDBGKILL: /* kill the parent */ 395 if (!iswiz()) 396 break; 397 if (kill(MotherPid, SIGTERM) >= 0) 398 message("200", "Mother is dead"); 399 else 400 message("500", "Can't kill Mom"); 401 break; 402 403 case CMDDBGSHELL: /* give us an interactive shell */ 404 if (!iswiz()) 405 break; 406 if (fileno(InChannel) != 0) 407 { 408 (void) close(0); 409 (void) dup(fileno(InChannel)); 410 if (fileno(InChannel) != fileno(OutChannel)) 411 (void) fclose(InChannel); 412 InChannel = stdin; 413 } 414 if (fileno(OutChannel) != 1) 415 { 416 (void) close(1); 417 (void) dup(fileno(OutChannel)); 418 (void) fclose(OutChannel); 419 OutChannel = stdout; 420 } 421 (void) close(2); 422 (void) dup(1); 423 execl("/bin/csh", "sendmail", 0); 424 execl("/bin/sh", "sendmail", 0); 425 message("500", "Can't"); 426 exit(EX_UNAVAILABLE); 427 428 case CMDDBGWIZ: /* become a wizard */ 429 if (WizWord != NULL) 430 { 431 char seed[3]; 432 extern char *crypt(); 433 434 strncpy(seed, WizWord, 2); 435 if (strcmp(WizWord, crypt(p, seed)) == 0) 436 { 437 IsWiz = TRUE; 438 message("200", "Please pass, oh mighty wizard"); 439 break; 440 } 441 } 442 message("500", "You are no wizard!"); 443 break; 444 # endif DEBUG 445 446 case CMDERROR: /* unknown command */ 447 message("500", "Command unrecognized"); 448 break; 449 450 default: 451 syserr("smtp: unknown code %d", c->cmdcode); 452 break; 453 } 454 } 455 } 456 /* 457 ** SKIPWORD -- skip a fixed word. 458 ** 459 ** Parameters: 460 ** p -- place to start looking. 461 ** w -- word to skip. 462 ** 463 ** Returns: 464 ** p following w. 465 ** NULL on error. 466 ** 467 ** Side Effects: 468 ** clobbers the p data area. 469 */ 470 471 static char * 472 skipword(p, w) 473 register char *p; 474 char *w; 475 { 476 register char *q; 477 extern bool sameword(); 478 479 /* find beginning of word */ 480 while (isspace(*p)) 481 p++; 482 q = p; 483 484 /* find end of word */ 485 while (*p != '\0' && *p != ':' && !isspace(*p)) 486 p++; 487 while (isspace(*p)) 488 *p++ = '\0'; 489 if (*p != ':') 490 { 491 syntax: 492 message("501", "Syntax error"); 493 Errors++; 494 return (NULL); 495 } 496 *p++ = '\0'; 497 while (isspace(*p)) 498 p++; 499 500 /* see if the input word matches desired word */ 501 if (!sameword(q, w)) 502 goto syntax; 503 504 return (p); 505 } 506 /* 507 ** HELP -- implement the HELP command. 508 ** 509 ** Parameters: 510 ** topic -- the topic we want help for. 511 ** 512 ** Returns: 513 ** none. 514 ** 515 ** Side Effects: 516 ** outputs the help file to message output. 517 */ 518 519 help(topic) 520 char *topic; 521 { 522 register FILE *hf; 523 int len; 524 char buf[MAXLINE]; 525 bool noinfo; 526 527 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 528 { 529 /* no help */ 530 errno = 0; 531 message("502", "HELP not implemented"); 532 return; 533 } 534 535 len = strlen(topic); 536 makelower(topic); 537 noinfo = TRUE; 538 539 while (fgets(buf, sizeof buf, hf) != NULL) 540 { 541 if (strncmp(buf, topic, len) == 0) 542 { 543 register char *p; 544 545 p = index(buf, '\t'); 546 if (p == NULL) 547 p = buf; 548 else 549 p++; 550 fixcrlf(p, TRUE); 551 message("214-", p); 552 noinfo = FALSE; 553 } 554 } 555 556 if (noinfo) 557 message("504", "HELP topic unknown"); 558 else 559 message("214", "End of HELP info"); 560 (void) fclose(hf); 561 } 562 /* 563 ** ISWIZ -- tell us if we are a wizard 564 ** 565 ** If not, print a nasty message. 566 ** 567 ** Parameters: 568 ** none. 569 ** 570 ** Returns: 571 ** TRUE if we are a wizard. 572 ** FALSE if we are not a wizard. 573 ** 574 ** Side Effects: 575 ** Prints a 500 exit stat if we are not a wizard. 576 */ 577 578 bool 579 iswiz() 580 { 581 if (!IsWiz) 582 message("500", "Mere mortals musn't mutter that mantra"); 583 return (IsWiz); 584 } 585 /* 586 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 587 ** 588 ** Parameters: 589 ** label -- a string used in error messages 590 ** 591 ** Returns: 592 ** zero in the child 593 ** one in the parent 594 ** 595 ** Side Effects: 596 ** none. 597 */ 598 599 runinchild(label) 600 char *label; 601 { 602 int childpid; 603 604 if (!OneXact) 605 { 606 childpid = dofork(); 607 if (childpid < 0) 608 { 609 syserr("%s: cannot fork", label); 610 return (1); 611 } 612 if (childpid > 0) 613 { 614 auto int st; 615 616 /* parent -- wait for child to complete */ 617 st = waitfor(childpid); 618 if (st == -1) 619 syserr("%s: lost child", label); 620 621 /* if we exited on a QUIT command, complete the process */ 622 if (st == (EX_QUIT << 8)) 623 finis(); 624 625 return (1); 626 } 627 else 628 { 629 /* child */ 630 InChild = TRUE; 631 } 632 } 633 634 /* child (or ONEX command specified) */ 635 clearenvelope(CurEnv); 636 637 /* open alias database */ 638 initaliases(AliasFile, FALSE); 639 640 return (0); 641 } 642 643 # endif SMTP 644