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