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