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