1 /* 2 * Copyright (c) 1983 Eric P. Allman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms are permitted 7 * provided that the above copyright notice and this paragraph are 8 * duplicated in all such forms and that any documentation, 9 * advertising materials, and other materials related to such 10 * distribution and use acknowledge that the software was developed 11 * by the University of California, Berkeley. The name of the 12 * University may not be used to endorse or promote products derived 13 * from this software without specific prior written permission. 14 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 16 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 17 */ 18 19 # include "sendmail.h" 20 21 #ifndef lint 22 #ifdef SMTP 23 static char sccsid[] = "@(#)srvrsmtp.c 5.27 (Berkeley) 01/18/89 (with SMTP)"; 24 #else 25 static char sccsid[] = "@(#)srvrsmtp.c 5.27 (Berkeley) 01/18/89 (without SMTP)"; 26 #endif 27 #endif /* not lint */ 28 29 # include <errno.h> 30 # include <signal.h> 31 32 # ifdef SMTP 33 34 /* 35 ** SMTP -- run the SMTP protocol. 36 ** 37 ** Parameters: 38 ** none. 39 ** 40 ** Returns: 41 ** never. 42 ** 43 ** Side Effects: 44 ** Reads commands from the input channel and processes 45 ** them. 46 */ 47 48 struct cmd 49 { 50 char *cmdname; /* command name */ 51 int cmdcode; /* internal code, see below */ 52 }; 53 54 /* values for cmdcode */ 55 # define CMDERROR 0 /* bad command */ 56 # define CMDMAIL 1 /* mail -- designate sender */ 57 # define CMDRCPT 2 /* rcpt -- designate recipient */ 58 # define CMDDATA 3 /* data -- send message text */ 59 # define CMDRSET 4 /* rset -- reset state */ 60 # define CMDVRFY 5 /* vrfy -- verify address */ 61 # define CMDHELP 6 /* help -- give usage info */ 62 # define CMDNOOP 7 /* noop -- do nothing */ 63 # define CMDQUIT 8 /* quit -- close connection and die */ 64 # define CMDHELO 9 /* helo -- be polite */ 65 # define CMDONEX 10 /* onex -- sending one transaction only */ 66 # define CMDVERB 11 /* verb -- go into verbose mode */ 67 /* debugging-only commands, only enabled if SMTPDEBUG is defined */ 68 # define CMDDBGQSHOW 12 /* showq -- show send queue */ 69 # define CMDDBGDEBUG 13 /* debug -- set debug mode */ 70 71 static struct cmd CmdTab[] = 72 { 73 "mail", CMDMAIL, 74 "rcpt", CMDRCPT, 75 "data", CMDDATA, 76 "rset", CMDRSET, 77 "vrfy", CMDVRFY, 78 "expn", CMDVRFY, 79 "help", CMDHELP, 80 "noop", CMDNOOP, 81 "quit", CMDQUIT, 82 "helo", CMDHELO, 83 "verb", CMDVERB, 84 "onex", CMDONEX, 85 /* 86 * remaining commands are here only 87 * to trap and log attempts to use them 88 */ 89 "showq", CMDDBGQSHOW, 90 "debug", CMDDBGDEBUG, 91 NULL, CMDERROR, 92 }; 93 94 bool InChild = FALSE; /* true if running in a subprocess */ 95 bool OneXact = FALSE; /* one xaction only this run */ 96 97 #define EX_QUIT 22 /* special code for QUIT command */ 98 99 smtp() 100 { 101 register char *p; 102 register struct cmd *c; 103 char *cmd; 104 extern char *skipword(); 105 bool hasmail; /* mail command received */ 106 auto ADDRESS *vrfyqueue; 107 ADDRESS *a; 108 char *sendinghost; 109 char inp[MAXLINE]; 110 char cmdbuf[100]; 111 extern char Version[]; 112 extern char *macvalue(); 113 extern ADDRESS *recipient(); 114 extern ENVELOPE BlankEnvelope; 115 extern ENVELOPE *newenvelope(); 116 117 hasmail = FALSE; 118 if (OutChannel != stdout) 119 { 120 /* arrange for debugging output to go to remote host */ 121 (void) close(1); 122 (void) dup(fileno(OutChannel)); 123 } 124 settime(); 125 if (RealHostName != NULL) 126 { 127 CurHostName = RealHostName; 128 setproctitle("srvrsmtp %s", CurHostName); 129 } 130 else 131 { 132 /* this must be us!! */ 133 CurHostName = MyHostName; 134 } 135 expand("\001e", inp, &inp[sizeof inp], CurEnv); 136 message("220", inp); 137 SmtpPhase = "startup"; 138 sendinghost = NULL; 139 for (;;) 140 { 141 /* arrange for backout */ 142 if (setjmp(TopFrame) > 0 && InChild) 143 finis(); 144 QuickAbort = FALSE; 145 HoldErrs = FALSE; 146 147 /* setup for the read */ 148 CurEnv->e_to = NULL; 149 Errors = 0; 150 (void) fflush(stdout); 151 152 /* read the input line */ 153 p = sfgets(inp, sizeof inp, InChannel); 154 155 /* handle errors */ 156 if (p == NULL) 157 { 158 /* end of file, just die */ 159 message("421", "%s Lost input channel from %s", 160 MyHostName, CurHostName); 161 finis(); 162 } 163 164 /* clean up end of line */ 165 fixcrlf(inp, TRUE); 166 167 /* echo command to transcript */ 168 if (CurEnv->e_xfp != NULL) 169 fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 170 171 /* break off command */ 172 for (p = inp; isspace(*p); p++) 173 continue; 174 cmd = p; 175 for (cmd = cmdbuf; *p != '\0' && !isspace(*p); ) 176 *cmd++ = *p++; 177 *cmd = '\0'; 178 179 /* throw away leading whitespace */ 180 while (isspace(*p)) 181 p++; 182 183 /* decode command */ 184 for (c = CmdTab; c->cmdname != NULL; c++) 185 { 186 if (!strcasecmp(c->cmdname, cmdbuf)) 187 break; 188 } 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", "Local configuration error, hostname not recognized as local"); 203 break; 204 } 205 if (RealHostName != NULL && strcasecmp(p, RealHostName)) 206 { 207 char hostbuf[MAXNAME]; 208 209 (void) sprintf(hostbuf, "%s (%s)", p, RealHostName); 210 sendinghost = newstr(hostbuf); 211 } 212 else 213 sendinghost = newstr(p); 214 message("250", "%s Hello %s, pleased to meet you", 215 MyHostName, sendinghost); 216 break; 217 218 case CMDMAIL: /* mail -- designate sender */ 219 SmtpPhase = "MAIL"; 220 221 /* force a sending host even if no HELO given */ 222 if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 223 sendinghost = RealHostName; 224 225 /* check for validity of this command */ 226 if (hasmail) 227 { 228 message("503", "Sender already specified"); 229 break; 230 } 231 if (InChild) 232 { 233 errno = 0; 234 syserr("Nested MAIL command"); 235 exit(0); 236 } 237 238 /* fork a subprocess to process this command */ 239 if (runinchild("SMTP-MAIL") > 0) 240 break; 241 define('s', sendinghost, CurEnv); 242 define('r', "SMTP", CurEnv); 243 initsys(); 244 setproctitle("%s %s: %s", CurEnv->e_id, 245 CurHostName, inp); 246 247 /* child -- go do the processing */ 248 p = skipword(p, "from"); 249 if (p == NULL) 250 break; 251 setsender(p); 252 if (Errors == 0) 253 { 254 message("250", "Sender ok"); 255 hasmail = TRUE; 256 } 257 else if (InChild) 258 finis(); 259 break; 260 261 case CMDRCPT: /* rcpt -- designate recipient */ 262 SmtpPhase = "RCPT"; 263 setproctitle("%s %s: %s", CurEnv->e_id, 264 CurHostName, inp); 265 if (setjmp(TopFrame) > 0) 266 { 267 CurEnv->e_flags &= ~EF_FATALERRS; 268 break; 269 } 270 QuickAbort = TRUE; 271 p = skipword(p, "to"); 272 if (p == NULL) 273 break; 274 a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 275 if (a == NULL) 276 break; 277 a->q_flags |= QPRIMARY; 278 a = recipient(a, &CurEnv->e_sendqueue); 279 if (Errors != 0) 280 break; 281 282 /* no errors during parsing, but might be a duplicate */ 283 CurEnv->e_to = p; 284 if (!bitset(QBADADDR, a->q_flags)) 285 message("250", "Recipient ok"); 286 else 287 { 288 /* punt -- should keep message in ADDRESS.... */ 289 message("550", "Addressee unknown"); 290 } 291 CurEnv->e_to = NULL; 292 break; 293 294 case CMDDATA: /* data -- text of mail */ 295 SmtpPhase = "DATA"; 296 if (!hasmail) 297 { 298 message("503", "Need MAIL command"); 299 break; 300 } 301 else if (CurEnv->e_nrcpts <= 0) 302 { 303 message("503", "Need RCPT (recipient)"); 304 break; 305 } 306 307 /* collect the text of the message */ 308 SmtpPhase = "collect"; 309 setproctitle("%s %s: %s", CurEnv->e_id, 310 CurHostName, inp); 311 collect(TRUE); 312 if (Errors != 0) 313 break; 314 315 /* 316 ** Arrange to send to everyone. 317 ** If sending to multiple people, mail back 318 ** errors rather than reporting directly. 319 ** In any case, don't mail back errors for 320 ** anything that has happened up to 321 ** now (the other end will do this). 322 ** Truncate our transcript -- the mail has gotten 323 ** to us successfully, and if we have 324 ** to mail this back, it will be easier 325 ** on the reader. 326 ** Then send to everyone. 327 ** Finally give a reply code. If an error has 328 ** already been given, don't mail a 329 ** message back. 330 ** We goose error returns by clearing error bit. 331 */ 332 333 SmtpPhase = "delivery"; 334 if (CurEnv->e_nrcpts != 1) 335 { 336 HoldErrs = TRUE; 337 ErrorMode = EM_MAIL; 338 } 339 CurEnv->e_flags &= ~EF_FATALERRS; 340 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 341 342 /* send to all recipients */ 343 sendall(CurEnv, SM_DEFAULT); 344 CurEnv->e_to = NULL; 345 346 /* save statistics */ 347 markstats(CurEnv, (ADDRESS *) NULL); 348 349 /* issue success if appropriate and reset */ 350 if (Errors == 0 || HoldErrs) 351 message("250", "Ok"); 352 else 353 CurEnv->e_flags &= ~EF_FATALERRS; 354 355 /* if in a child, pop back to our parent */ 356 if (InChild) 357 finis(); 358 359 /* clean up a bit */ 360 hasmail = 0; 361 dropenvelope(CurEnv); 362 CurEnv = newenvelope(CurEnv); 363 CurEnv->e_flags = BlankEnvelope.e_flags; 364 break; 365 366 case CMDRSET: /* rset -- reset state */ 367 message("250", "Reset state"); 368 if (InChild) 369 finis(); 370 break; 371 372 case CMDVRFY: /* vrfy -- verify address */ 373 if (runinchild("SMTP-VRFY") > 0) 374 break; 375 setproctitle("%s: %s", CurHostName, inp); 376 vrfyqueue = NULL; 377 QuickAbort = TRUE; 378 sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 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 (vrfyqueue->q_fullname == NULL) 400 message(code, "<%s>", vrfyqueue->q_paddr); 401 else 402 message(code, "%s <%s>", 403 vrfyqueue->q_fullname, vrfyqueue->q_paddr); 404 } 405 else if (a == NULL) 406 message("554", "Self destructive alias loop"); 407 vrfyqueue = a; 408 } 409 if (InChild) 410 finis(); 411 break; 412 413 case CMDHELP: /* help -- give user info */ 414 if (*p == '\0') 415 p = "SMTP"; 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(CurEnv->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)\n", 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 len = strlen(topic); 556 makelower(topic); 557 noinfo = TRUE; 558 559 while (fgets(buf, sizeof buf, hf) != NULL) 560 { 561 if (strncmp(buf, topic, len) == 0) 562 { 563 register char *p; 564 565 p = index(buf, '\t'); 566 if (p == NULL) 567 p = buf; 568 else 569 p++; 570 fixcrlf(p, TRUE); 571 message("214-", p); 572 noinfo = FALSE; 573 } 574 } 575 576 if (noinfo) 577 message("504", "HELP topic unknown"); 578 else 579 message("214", "End of HELP info"); 580 (void) fclose(hf); 581 } 582 /* 583 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 584 ** 585 ** Parameters: 586 ** label -- a string used in error messages 587 ** 588 ** Returns: 589 ** zero in the child 590 ** one in the parent 591 ** 592 ** Side Effects: 593 ** none. 594 */ 595 596 runinchild(label) 597 char *label; 598 { 599 int childpid; 600 601 if (!OneXact) 602 { 603 childpid = dofork(); 604 if (childpid < 0) 605 { 606 syserr("%s: cannot fork", label); 607 return (1); 608 } 609 if (childpid > 0) 610 { 611 auto int st; 612 613 /* parent -- wait for child to complete */ 614 st = waitfor(childpid); 615 if (st == -1) 616 syserr("%s: lost child", label); 617 618 /* if we exited on a QUIT command, complete the process */ 619 if (st == (EX_QUIT << 8)) 620 finis(); 621 622 return (1); 623 } 624 else 625 { 626 /* child */ 627 InChild = TRUE; 628 QuickAbort = FALSE; 629 clearenvelope(CurEnv, FALSE); 630 } 631 } 632 633 /* open alias database */ 634 initaliases(AliasFile, FALSE); 635 636 return (0); 637 } 638 639 # endif SMTP 640