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