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