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.12 (Berkeley) 02/20/93 (with SMTP)"; 14 #else 15 static char sccsid[] = "@(#)srvrsmtp.c 6.12 (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 p = skipword(p, "to"); 291 if (p == NULL) 292 break; 293 a = parseaddr(p, (ADDRESS *) NULL, 1, '\0', e); 294 if (a == NULL) 295 break; 296 a->q_flags |= QPRIMARY; 297 a = recipient(a, &e->e_sendqueue, e); 298 if (Errors != 0) 299 break; 300 301 /* no errors during parsing, but might be a duplicate */ 302 e->e_to = p; 303 if (!bitset(QBADADDR, a->q_flags)) 304 message("250", "Recipient ok"); 305 else 306 { 307 /* punt -- should keep message in ADDRESS.... */ 308 message("550", "Addressee unknown"); 309 } 310 e->e_to = NULL; 311 break; 312 313 case CMDDATA: /* data -- text of mail */ 314 SmtpPhase = "DATA"; 315 if (!hasmail) 316 { 317 message("503", "Need MAIL command"); 318 break; 319 } 320 else if (e->e_nrcpts <= 0) 321 { 322 message("503", "Need RCPT (recipient)"); 323 break; 324 } 325 326 /* collect the text of the message */ 327 SmtpPhase = "collect"; 328 setproctitle("%s %s: %s", e->e_id, CurHostName, inp); 329 collect(TRUE, e); 330 if (Errors != 0) 331 break; 332 333 /* 334 ** Arrange to send to everyone. 335 ** If sending to multiple people, mail back 336 ** errors rather than reporting directly. 337 ** In any case, don't mail back errors for 338 ** anything that has happened up to 339 ** now (the other end will do this). 340 ** Truncate our transcript -- the mail has gotten 341 ** to us successfully, and if we have 342 ** to mail this back, it will be easier 343 ** on the reader. 344 ** Then send to everyone. 345 ** Finally give a reply code. If an error has 346 ** already been given, don't mail a 347 ** message back. 348 ** We goose error returns by clearing error bit. 349 */ 350 351 SmtpPhase = "delivery"; 352 if (e->e_nrcpts != 1) 353 { 354 HoldErrs = TRUE; 355 ErrorMode = EM_MAIL; 356 } 357 e->e_flags &= ~EF_FATALERRS; 358 e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); 359 360 /* send to all recipients */ 361 sendall(e, SM_DEFAULT); 362 e->e_to = NULL; 363 364 /* save statistics */ 365 markstats(e, (ADDRESS *) NULL); 366 367 /* issue success if appropriate and reset */ 368 if (Errors == 0 || HoldErrs) 369 message("250", "Ok"); 370 else 371 e->e_flags &= ~EF_FATALERRS; 372 373 /* if in a child, pop back to our parent */ 374 if (InChild) 375 finis(); 376 377 /* clean up a bit */ 378 hasmail = FALSE; 379 dropenvelope(e); 380 CurEnv = e = newenvelope(e); 381 e->e_flags = BlankEnvelope.e_flags; 382 break; 383 384 case CMDRSET: /* rset -- reset state */ 385 message("250", "Reset state"); 386 if (InChild) 387 finis(); 388 break; 389 390 case CMDVRFY: /* vrfy -- verify address */ 391 case CMDEXPN: /* expn -- expand address */ 392 vrfy = c->cmdcode == CMDVRFY; 393 if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, 394 PrivacyFlags)) 395 { 396 message("502", "That's none of your business"); 397 break; 398 } 399 else if (!gothello && 400 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, 401 PrivacyFlags)) 402 { 403 message("503", "I demand that you introduce yourself first"); 404 break; 405 } 406 if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) 407 break; 408 setproctitle("%s: %s", CurHostName, inp); 409 #ifdef LOG 410 if (LogLevel > 5) 411 syslog(LOG_INFO, "%s: %s", CurHostName, inp); 412 #endif 413 vrfyqueue = NULL; 414 QuickAbort = TRUE; 415 if (vrfy) 416 e->e_flags |= EF_VRFYONLY; 417 (void) sendtolist(p, (ADDRESS *) NULL, &vrfyqueue, e); 418 if (Errors != 0) 419 { 420 if (InChild) 421 finis(); 422 break; 423 } 424 while (vrfyqueue != NULL) 425 { 426 register ADDRESS *a = vrfyqueue->q_next; 427 char *code; 428 429 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 430 a = a->q_next; 431 432 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 433 { 434 if (a != NULL) 435 code = "250-"; 436 else 437 code = "250"; 438 if (strchr(vrfyqueue->q_paddr, '<') != NULL) 439 message(code, "%s", vrfyqueue->q_paddr); 440 else if (vrfyqueue->q_fullname == NULL) 441 message(code, "<%s>", vrfyqueue->q_paddr); 442 else 443 message(code, "%s <%s>", 444 vrfyqueue->q_fullname, vrfyqueue->q_paddr); 445 } 446 else if (a == NULL) 447 message("554", "Self destructive alias loop"); 448 vrfyqueue = a; 449 } 450 if (InChild) 451 finis(); 452 break; 453 454 case CMDHELP: /* help -- give user info */ 455 help(p); 456 break; 457 458 case CMDNOOP: /* noop -- do nothing */ 459 message("200", "OK"); 460 break; 461 462 case CMDQUIT: /* quit -- leave mail */ 463 message("221", "%s closing connection", MyHostName); 464 if (InChild) 465 ExitStat = EX_QUIT; 466 finis(); 467 468 case CMDVERB: /* set verbose mode */ 469 Verbose = TRUE; 470 SendMode = SM_DELIVER; 471 message("200", "Verbose mode"); 472 break; 473 474 case CMDONEX: /* doing one transaction only */ 475 OneXact = TRUE; 476 message("200", "Only one transaction"); 477 break; 478 479 # ifdef SMTPDEBUG 480 case CMDDBGQSHOW: /* show queues */ 481 printf("Send Queue="); 482 printaddr(e->e_sendqueue, TRUE); 483 break; 484 485 case CMDDBGDEBUG: /* set debug mode */ 486 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 487 tTflag(p); 488 message("200", "Debug set"); 489 break; 490 491 # else /* not SMTPDEBUG */ 492 493 case CMDDBGQSHOW: /* show queues */ 494 case CMDDBGDEBUG: /* set debug mode */ 495 # ifdef LOG 496 if (RealHostName != NULL && LogLevel > 0) 497 syslog(LOG_NOTICE, 498 "\"%s\" command from %s (%s)", 499 c->cmdname, RealHostName, 500 inet_ntoa(RealHostAddr.sin_addr)); 501 # endif 502 /* FALL THROUGH */ 503 # endif /* SMTPDEBUG */ 504 505 case CMDERROR: /* unknown command */ 506 message("500", "Command unrecognized"); 507 break; 508 509 default: 510 errno = 0; 511 syserr("smtp: unknown code %d", c->cmdcode); 512 break; 513 } 514 } 515 } 516 /* 517 ** SKIPWORD -- skip a fixed word. 518 ** 519 ** Parameters: 520 ** p -- place to start looking. 521 ** w -- word to skip. 522 ** 523 ** Returns: 524 ** p following w. 525 ** NULL on error. 526 ** 527 ** Side Effects: 528 ** clobbers the p data area. 529 */ 530 531 static char * 532 skipword(p, w) 533 register char *p; 534 char *w; 535 { 536 register char *q; 537 538 /* find beginning of word */ 539 while (isascii(*p) && isspace(*p)) 540 p++; 541 q = p; 542 543 /* find end of word */ 544 while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p))) 545 p++; 546 while (isascii(*p) && isspace(*p)) 547 *p++ = '\0'; 548 if (*p != ':') 549 { 550 syntax: 551 message("501", "Syntax error"); 552 Errors++; 553 return (NULL); 554 } 555 *p++ = '\0'; 556 while (isascii(*p) && isspace(*p)) 557 p++; 558 559 /* see if the input word matches desired word */ 560 if (strcasecmp(q, w)) 561 goto syntax; 562 563 return (p); 564 } 565 /* 566 ** HELP -- implement the HELP command. 567 ** 568 ** Parameters: 569 ** topic -- the topic we want help for. 570 ** 571 ** Returns: 572 ** none. 573 ** 574 ** Side Effects: 575 ** outputs the help file to message output. 576 */ 577 578 help(topic) 579 char *topic; 580 { 581 register FILE *hf; 582 int len; 583 char buf[MAXLINE]; 584 bool noinfo; 585 586 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 587 { 588 /* no help */ 589 errno = 0; 590 message("502", "HELP not implemented"); 591 return; 592 } 593 594 if (topic == NULL || *topic == '\0') 595 topic = "smtp"; 596 else 597 makelower(topic); 598 599 len = strlen(topic); 600 noinfo = TRUE; 601 602 while (fgets(buf, sizeof buf, hf) != NULL) 603 { 604 if (strncmp(buf, topic, len) == 0) 605 { 606 register char *p; 607 608 p = strchr(buf, '\t'); 609 if (p == NULL) 610 p = buf; 611 else 612 p++; 613 fixcrlf(p, TRUE); 614 message("214-", p); 615 noinfo = FALSE; 616 } 617 } 618 619 if (noinfo) 620 message("504", "HELP topic unknown"); 621 else 622 message("214", "End of HELP info"); 623 (void) fclose(hf); 624 } 625 /* 626 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 627 ** 628 ** Parameters: 629 ** label -- a string used in error messages 630 ** 631 ** Returns: 632 ** zero in the child 633 ** one in the parent 634 ** 635 ** Side Effects: 636 ** none. 637 */ 638 639 runinchild(label, e) 640 char *label; 641 register ENVELOPE *e; 642 { 643 int childpid; 644 645 if (!OneXact) 646 { 647 childpid = dofork(); 648 if (childpid < 0) 649 { 650 syserr("%s: cannot fork", label); 651 return (1); 652 } 653 if (childpid > 0) 654 { 655 auto int st; 656 657 /* parent -- wait for child to complete */ 658 st = waitfor(childpid); 659 if (st == -1) 660 syserr("%s: lost child", label); 661 662 /* if we exited on a QUIT command, complete the process */ 663 if (st == (EX_QUIT << 8)) 664 finis(); 665 666 return (1); 667 } 668 else 669 { 670 /* child */ 671 InChild = TRUE; 672 QuickAbort = FALSE; 673 clearenvelope(e, FALSE); 674 } 675 } 676 677 /* open alias database */ 678 initaliases(AliasFile, FALSE, e); 679 680 return (0); 681 } 682 683 # endif /* SMTP */ 684