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