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