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