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