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