1 # include <errno.h> 2 # include "sendmail.h" 3 # include <signal.h> 4 5 # ifndef SMTP 6 SCCSID(@(#)srvrsmtp.c 4.4 10/16/83 (no SMTP)); 7 # else SMTP 8 9 SCCSID(@(#)srvrsmtp.c 4.4 10/16/83); 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 = NULL; /* 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("$e", 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); 232 if (a == NULL) 233 break; 234 a = recipient(a, &CurEnv->e_sendqueue); 235 if (Errors != 0) 236 break; 237 238 /* no errors during parsing, but might be a duplicate */ 239 CurEnv->e_to = p; 240 if (!bitset(QBADADDR, a->q_flags)) 241 message("250", "Recipient ok"); 242 else 243 { 244 /* punt -- should keep message in ADDRESS.... */ 245 message("550", "Addressee unknown"); 246 } 247 CurEnv->e_to = NULL; 248 rcps++; 249 break; 250 251 case CMDDATA: /* data -- text of mail */ 252 if (!hasmail) 253 { 254 message("503", "Need MAIL command"); 255 break; 256 } 257 else if (rcps <= 0) 258 { 259 message("503", "Need RCPT (recipient)"); 260 break; 261 } 262 263 /* collect the text of the message */ 264 collect(TRUE); 265 if (Errors != 0) 266 break; 267 268 /* 269 ** Arrange to send to everyone. 270 ** If sending to multiple people, mail back 271 ** errors rather than reporting directly. 272 ** In any case, don't mail back errors for 273 ** anything that has happened up to 274 ** now (the other end will do this). 275 ** Truncate our transcript -- the mail has gotten 276 ** to us successfully, and if we have 277 ** to mail this back, it will be easier 278 ** on the reader. 279 ** Then send to everyone. 280 ** Finally give a reply code. If an error has 281 ** already been given, don't mail a 282 ** message back. 283 ** We goose error returns by clearing error bit. 284 */ 285 286 if (rcps != 1) 287 { 288 HoldErrs = TRUE; 289 ErrorMode == EM_MAIL; 290 } 291 CurEnv->e_flags &= ~EF_FATALERRS; 292 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 293 294 /* send to all recipients */ 295 sendall(CurEnv, SM_DEFAULT); 296 CurEnv->e_to = NULL; 297 298 /* issue success if appropriate and reset */ 299 if (Errors == 0 || HoldErrs) 300 message("250", "Ok"); 301 else 302 CurEnv->e_flags &= ~EF_FATALERRS; 303 304 /* if in a child, pop back to our parent */ 305 if (InChild) 306 finis(); 307 break; 308 309 case CMDRSET: /* rset -- reset state */ 310 message("250", "Reset state"); 311 if (InChild) 312 finis(); 313 break; 314 315 case CMDVRFY: /* vrfy -- verify address */ 316 if (runinchild("SMTP-VRFY") > 0) 317 break; 318 vrfyqueue = NULL; 319 QuickAbort = TRUE; 320 sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 321 if (Errors != 0) 322 { 323 if (InChild) 324 finis(); 325 break; 326 } 327 while (vrfyqueue != NULL) 328 { 329 register ADDRESS *a = vrfyqueue->q_next; 330 char *code; 331 332 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 333 a = a->q_next; 334 335 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 336 { 337 if (a != NULL) 338 code = "250-"; 339 else 340 code = "250"; 341 if (vrfyqueue->q_fullname == NULL) 342 message(code, "<%s>", vrfyqueue->q_paddr); 343 else 344 message(code, "%s <%s>", 345 vrfyqueue->q_fullname, vrfyqueue->q_paddr); 346 } 347 else if (a == NULL) 348 message("554", "Self destructive alias loop"); 349 vrfyqueue = a; 350 } 351 if (InChild) 352 finis(); 353 break; 354 355 case CMDHELP: /* help -- give user info */ 356 if (*p == '\0') 357 p = "SMTP"; 358 help(p); 359 break; 360 361 case CMDNOOP: /* noop -- do nothing */ 362 message("200", "OK"); 363 break; 364 365 case CMDQUIT: /* quit -- leave mail */ 366 message("221", "%s closing connection", HostName); 367 if (InChild) 368 ExitStat = EX_QUIT; 369 finis(); 370 371 case CMDVERB: /* set verbose mode */ 372 Verbose = TRUE; 373 message("200", "Verbose mode"); 374 break; 375 376 case CMDONEX: /* doing one transaction only */ 377 OneXact = TRUE; 378 message("200", "Only one transaction"); 379 break; 380 381 # ifdef DEBUG 382 case CMDDBGQSHOW: /* show queues */ 383 printf("Send Queue="); 384 printaddr(CurEnv->e_sendqueue, TRUE); 385 break; 386 387 case CMDDBGDEBUG: /* set debug mode */ 388 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 389 tTflag(p); 390 message("200", "Debug set"); 391 break; 392 393 case CMDDBGKILL: /* kill the parent */ 394 if (!iswiz()) 395 break; 396 if (kill(MotherPid, SIGTERM) >= 0) 397 message("200", "Mother is dead"); 398 else 399 message("500", "Can't kill Mom"); 400 break; 401 402 case CMDDBGSHELL: /* give us an interactive shell */ 403 if (!iswiz()) 404 break; 405 if (fileno(InChannel) != 0) 406 { 407 (void) close(0); 408 (void) dup(fileno(InChannel)); 409 if (fileno(InChannel) != fileno(OutChannel)) 410 (void) fclose(InChannel); 411 InChannel = stdin; 412 } 413 if (fileno(OutChannel) != 1) 414 { 415 (void) close(1); 416 (void) dup(fileno(OutChannel)); 417 (void) fclose(OutChannel); 418 OutChannel = stdout; 419 } 420 (void) close(2); 421 (void) dup(1); 422 execl("/bin/csh", "sendmail", 0); 423 execl("/bin/sh", "sendmail", 0); 424 message("500", "Can't"); 425 exit(EX_UNAVAILABLE); 426 427 case CMDDBGWIZ: /* become a wizard */ 428 if (WizWord != NULL) 429 { 430 char seed[3]; 431 extern char *crypt(); 432 433 strncpy(seed, WizWord, 2); 434 if (strcmp(WizWord, crypt(p, seed)) != 0) 435 { 436 message("500", "You are no wizard!"); 437 break; 438 } 439 } 440 IsWiz = TRUE; 441 message("200", "Please pass, oh mighty wizard"); 442 break; 443 # endif DEBUG 444 445 case CMDERROR: /* unknown command */ 446 message("500", "Command unrecognized"); 447 break; 448 449 default: 450 syserr("smtp: unknown code %d", c->cmdcode); 451 break; 452 } 453 } 454 } 455 /* 456 ** SKIPWORD -- skip a fixed word. 457 ** 458 ** Parameters: 459 ** p -- place to start looking. 460 ** w -- word to skip. 461 ** 462 ** Returns: 463 ** p following w. 464 ** NULL on error. 465 ** 466 ** Side Effects: 467 ** clobbers the p data area. 468 */ 469 470 static char * 471 skipword(p, w) 472 register char *p; 473 char *w; 474 { 475 register char *q; 476 extern bool sameword(); 477 478 /* find beginning of word */ 479 while (isspace(*p)) 480 p++; 481 q = p; 482 483 /* find end of word */ 484 while (*p != '\0' && *p != ':' && !isspace(*p)) 485 p++; 486 while (isspace(*p)) 487 *p++ = '\0'; 488 if (*p != ':') 489 { 490 syntax: 491 message("501", "Syntax error"); 492 Errors++; 493 return (NULL); 494 } 495 *p++ = '\0'; 496 while (isspace(*p)) 497 p++; 498 499 /* see if the input word matches desired word */ 500 if (!sameword(q, w)) 501 goto syntax; 502 503 return (p); 504 } 505 /* 506 ** HELP -- implement the HELP command. 507 ** 508 ** Parameters: 509 ** topic -- the topic we want help for. 510 ** 511 ** Returns: 512 ** none. 513 ** 514 ** Side Effects: 515 ** outputs the help file to message output. 516 */ 517 518 help(topic) 519 char *topic; 520 { 521 register FILE *hf; 522 int len; 523 char buf[MAXLINE]; 524 bool noinfo; 525 526 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 527 { 528 /* no help */ 529 errno = 0; 530 message("502", "HELP not implemented"); 531 return; 532 } 533 534 len = strlen(topic); 535 makelower(topic); 536 noinfo = TRUE; 537 538 while (fgets(buf, sizeof buf, hf) != NULL) 539 { 540 if (strncmp(buf, topic, len) == 0) 541 { 542 register char *p; 543 544 p = index(buf, '\t'); 545 if (p == NULL) 546 p = buf; 547 else 548 p++; 549 fixcrlf(p, TRUE); 550 message("214-", p); 551 noinfo = FALSE; 552 } 553 } 554 555 if (noinfo) 556 message("504", "HELP topic unknown"); 557 else 558 message("214", "End of HELP info"); 559 (void) fclose(hf); 560 } 561 /* 562 ** ISWIZ -- tell us if we are a wizard 563 ** 564 ** If not, print a nasty message. 565 ** 566 ** Parameters: 567 ** none. 568 ** 569 ** Returns: 570 ** TRUE if we are a wizard. 571 ** FALSE if we are not a wizard. 572 ** 573 ** Side Effects: 574 ** Prints a 500 exit stat if we are not a wizard. 575 */ 576 577 bool 578 iswiz() 579 { 580 if (!IsWiz) 581 message("500", "Mere mortals musn't mutter that mantra"); 582 return (IsWiz); 583 } 584 /* 585 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 586 ** 587 ** Parameters: 588 ** label -- a string used in error messages 589 ** 590 ** Returns: 591 ** zero in the child 592 ** one in the parent 593 ** 594 ** Side Effects: 595 ** none. 596 */ 597 598 runinchild(label) 599 char *label; 600 { 601 int childpid; 602 603 if (OneXact) 604 return (0); 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 clearenvelope(CurEnv); 632 633 /* open alias database */ 634 initaliases(AliasFile, FALSE); 635 636 return (0); 637 } 638 } 639 640 # endif SMTP 641