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