1 /* 2 ** Sendmail 3 ** Copyright (c) 1983 Eric P. Allman 4 ** Berkeley, California 5 ** 6 ** Copyright (c) 1983 Regents of the University of California. 7 ** All rights reserved. The Berkeley software License Agreement 8 ** specifies the terms and conditions for redistribution. 9 */ 10 11 12 # include <errno.h> 13 # include "sendmail.h" 14 # include <signal.h> 15 16 # ifndef SMTP 17 # ifndef lint 18 static char SccsId[] = "@(#)srvrsmtp.c 5.7 (Berkeley) 09/03/85 (no SMTP)"; 19 # endif not lint 20 # else SMTP 21 22 # ifndef lint 23 static char SccsId[] = "@(#)srvrsmtp.c 5.7 (Berkeley) 09/03/85"; 24 # endif not lint 25 26 /* 27 ** SMTP -- run the SMTP protocol. 28 ** 29 ** Parameters: 30 ** none. 31 ** 32 ** Returns: 33 ** never. 34 ** 35 ** Side Effects: 36 ** Reads commands from the input channel and processes 37 ** them. 38 */ 39 40 struct cmd 41 { 42 char *cmdname; /* command name */ 43 int cmdcode; /* internal code, see below */ 44 }; 45 46 /* values for cmdcode */ 47 # define CMDERROR 0 /* bad command */ 48 # define CMDMAIL 1 /* mail -- designate sender */ 49 # define CMDRCPT 2 /* rcpt -- designate recipient */ 50 # define CMDDATA 3 /* data -- send message text */ 51 # define CMDRSET 4 /* rset -- reset state */ 52 # define CMDVRFY 5 /* vrfy -- verify address */ 53 # define CMDHELP 6 /* help -- give usage info */ 54 # define CMDNOOP 7 /* noop -- do nothing */ 55 # define CMDQUIT 8 /* quit -- close connection and die */ 56 # define CMDHELO 9 /* helo -- be polite */ 57 # define CMDDBGQSHOW 10 /* showq -- show send queue (DEBUG) */ 58 # define CMDDBGDEBUG 11 /* debug -- set debug mode */ 59 # define CMDVERB 12 /* verb -- go into verbose mode */ 60 # define CMDDBGKILL 13 /* kill -- kill sendmail */ 61 # define CMDDBGWIZ 14 /* wiz -- become a wizard */ 62 # define CMDONEX 15 /* onex -- sending one transaction only */ 63 64 static struct cmd CmdTab[] = 65 { 66 "mail", CMDMAIL, 67 "rcpt", CMDRCPT, 68 "data", CMDDATA, 69 "rset", CMDRSET, 70 "vrfy", CMDVRFY, 71 "expn", CMDVRFY, 72 "help", CMDHELP, 73 "noop", CMDNOOP, 74 "quit", CMDQUIT, 75 "helo", CMDHELO, 76 "verb", CMDVERB, 77 "onex", CMDONEX, 78 # ifdef DEBUG 79 "showq", CMDDBGQSHOW, 80 "debug", CMDDBGDEBUG, 81 # endif DEBUG 82 # ifdef WIZ 83 "kill", CMDDBGKILL, 84 # endif WIZ 85 "wiz", CMDDBGWIZ, 86 NULL, CMDERROR, 87 }; 88 89 bool IsWiz = FALSE; /* set if we are a wizard */ 90 char *WizWord; /* the wizard word to compare against */ 91 bool InChild = FALSE; /* true if running in a subprocess */ 92 bool OneXact = FALSE; /* one xaction only this run */ 93 char *RealHostName = NULL; /* verified hostname, set in daemon.c */ 94 95 #define EX_QUIT 22 /* special code for QUIT command */ 96 97 smtp() 98 { 99 register char *p; 100 register struct cmd *c; 101 char *cmd; 102 extern char *skipword(); 103 extern bool sameword(); 104 bool hasmail; /* mail command received */ 105 int rcps; /* number of recipients */ 106 auto ADDRESS *vrfyqueue; 107 ADDRESS *a; 108 char inp[MAXLINE]; 109 extern char Version[]; 110 extern tick(); 111 extern bool iswiz(); 112 extern char *arpadate(); 113 extern char *macvalue(); 114 extern ADDRESS *recipient(); 115 116 hasmail = FALSE; 117 rcps = 0; 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 expand("\001e", inp, &inp[sizeof inp], CurEnv); 126 message("220", inp); 127 for (;;) 128 { 129 /* arrange for backout */ 130 if (setjmp(TopFrame) > 0 && InChild) 131 finis(); 132 QuickAbort = FALSE; 133 HoldErrs = FALSE; 134 135 /* setup for the read */ 136 CurEnv->e_to = NULL; 137 Errors = 0; 138 (void) fflush(stdout); 139 140 /* read the input line */ 141 p = sfgets(inp, sizeof inp, InChannel); 142 143 /* handle errors */ 144 if (p == NULL) 145 { 146 /* end of file, just die */ 147 message("421", "%s Lost input channel", HostName); 148 finis(); 149 } 150 151 /* clean up end of line */ 152 fixcrlf(inp, TRUE); 153 154 /* echo command to transcript */ 155 if (CurEnv->e_xfp != NULL) 156 fprintf(CurEnv->e_xfp, "<<< %s\n", inp); 157 158 /* break off command */ 159 for (p = inp; isspace(*p); p++) 160 continue; 161 cmd = p; 162 while (*++p != '\0' && !isspace(*p)) 163 continue; 164 if (*p != '\0') 165 *p++ = '\0'; 166 167 /* decode command */ 168 for (c = CmdTab; c->cmdname != NULL; c++) 169 { 170 if (sameword(c->cmdname, cmd)) 171 break; 172 } 173 174 /* process command */ 175 switch (c->cmdcode) 176 { 177 case CMDHELO: /* hello -- introduce yourself */ 178 if (sameword(p, HostName)) 179 { 180 /* connected to an echo server */ 181 message("553", "%s I refuse to talk to myself", 182 HostName); 183 break; 184 } 185 if (RealHostName != NULL && !sameword(p, RealHostName)) 186 { 187 char buf[MAXNAME]; 188 189 (void) sprintf(buf, "%s (%s)", p, RealHostName); 190 define('s', newstr(buf), CurEnv); 191 } 192 else 193 define('s', newstr(p), CurEnv); 194 message("250", "%s Hello %s, pleased to meet you", 195 HostName, p); 196 break; 197 198 case CMDMAIL: /* mail -- designate sender */ 199 /* force a sending host even if no HELO given */ 200 if (RealHostName != NULL && macvalue('s', CurEnv) == NULL) 201 define('s', RealHostName, CurEnv); 202 203 /* check for validity of this command */ 204 if (hasmail) 205 { 206 message("503", "Sender already specified"); 207 break; 208 } 209 if (InChild) 210 { 211 syserr("Nested MAIL command"); 212 exit(0); 213 } 214 215 /* fork a subprocess to process this command */ 216 if (runinchild("SMTP-MAIL") > 0) 217 break; 218 initsys(); 219 220 /* child -- go do the processing */ 221 p = skipword(p, "from"); 222 if (p == NULL) 223 break; 224 setsender(p); 225 if (Errors == 0) 226 { 227 message("250", "Sender ok"); 228 hasmail = TRUE; 229 } 230 else if (InChild) 231 finis(); 232 break; 233 234 case CMDRCPT: /* rcpt -- designate recipient */ 235 if (setjmp(TopFrame) > 0) 236 { 237 CurEnv->e_flags &= ~EF_FATALERRS; 238 break; 239 } 240 QuickAbort = TRUE; 241 p = skipword(p, "to"); 242 if (p == NULL) 243 break; 244 a = parseaddr(p, (ADDRESS *) NULL, 1, '\0'); 245 if (a == NULL) 246 break; 247 a->q_flags |= QPRIMARY; 248 a = recipient(a, &CurEnv->e_sendqueue); 249 if (Errors != 0) 250 break; 251 252 /* no errors during parsing, but might be a duplicate */ 253 CurEnv->e_to = p; 254 if (!bitset(QBADADDR, a->q_flags)) 255 message("250", "Recipient ok"); 256 else 257 { 258 /* punt -- should keep message in ADDRESS.... */ 259 message("550", "Addressee unknown"); 260 } 261 CurEnv->e_to = NULL; 262 rcps++; 263 break; 264 265 case CMDDATA: /* data -- text of mail */ 266 if (!hasmail) 267 { 268 message("503", "Need MAIL command"); 269 break; 270 } 271 else if (rcps <= 0) 272 { 273 message("503", "Need RCPT (recipient)"); 274 break; 275 } 276 277 /* collect the text of the message */ 278 collect(TRUE); 279 if (Errors != 0) 280 break; 281 282 /* 283 ** Arrange to send to everyone. 284 ** If sending to multiple people, mail back 285 ** errors rather than reporting directly. 286 ** In any case, don't mail back errors for 287 ** anything that has happened up to 288 ** now (the other end will do this). 289 ** Truncate our transcript -- the mail has gotten 290 ** to us successfully, and if we have 291 ** to mail this back, it will be easier 292 ** on the reader. 293 ** Then send to everyone. 294 ** Finally give a reply code. If an error has 295 ** already been given, don't mail a 296 ** message back. 297 ** We goose error returns by clearing error bit. 298 */ 299 300 if (rcps != 1) 301 { 302 HoldErrs = TRUE; 303 ErrorMode = EM_MAIL; 304 } 305 CurEnv->e_flags &= ~EF_FATALERRS; 306 CurEnv->e_xfp = freopen(queuename(CurEnv, 'x'), "w", CurEnv->e_xfp); 307 308 /* send to all recipients */ 309 sendall(CurEnv, SM_DEFAULT); 310 CurEnv->e_to = NULL; 311 312 /* save statistics */ 313 markstats(CurEnv, (ADDRESS *) NULL); 314 315 /* issue success if appropriate and reset */ 316 if (Errors == 0 || HoldErrs) 317 message("250", "Ok"); 318 else 319 CurEnv->e_flags &= ~EF_FATALERRS; 320 321 /* if in a child, pop back to our parent */ 322 if (InChild) 323 finis(); 324 break; 325 326 case CMDRSET: /* rset -- reset state */ 327 message("250", "Reset state"); 328 if (InChild) 329 finis(); 330 break; 331 332 case CMDVRFY: /* vrfy -- verify address */ 333 if (runinchild("SMTP-VRFY") > 0) 334 break; 335 vrfyqueue = NULL; 336 QuickAbort = TRUE; 337 sendtolist(p, (ADDRESS *) NULL, &vrfyqueue); 338 if (Errors != 0) 339 { 340 if (InChild) 341 finis(); 342 break; 343 } 344 while (vrfyqueue != NULL) 345 { 346 register ADDRESS *a = vrfyqueue->q_next; 347 char *code; 348 349 while (a != NULL && bitset(QDONTSEND|QBADADDR, a->q_flags)) 350 a = a->q_next; 351 352 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 353 { 354 if (a != NULL) 355 code = "250-"; 356 else 357 code = "250"; 358 if (vrfyqueue->q_fullname == NULL) 359 message(code, "<%s>", vrfyqueue->q_paddr); 360 else 361 message(code, "%s <%s>", 362 vrfyqueue->q_fullname, vrfyqueue->q_paddr); 363 } 364 else if (a == NULL) 365 message("554", "Self destructive alias loop"); 366 vrfyqueue = a; 367 } 368 if (InChild) 369 finis(); 370 break; 371 372 case CMDHELP: /* help -- give user info */ 373 if (*p == '\0') 374 p = "SMTP"; 375 help(p); 376 break; 377 378 case CMDNOOP: /* noop -- do nothing */ 379 message("200", "OK"); 380 break; 381 382 case CMDQUIT: /* quit -- leave mail */ 383 message("221", "%s closing connection", HostName); 384 if (InChild) 385 ExitStat = EX_QUIT; 386 finis(); 387 388 case CMDVERB: /* set verbose mode */ 389 Verbose = TRUE; 390 message("200", "Verbose mode"); 391 break; 392 393 case CMDONEX: /* doing one transaction only */ 394 OneXact = TRUE; 395 message("200", "Only one transaction"); 396 break; 397 398 # ifdef DEBUG 399 case CMDDBGQSHOW: /* show queues */ 400 printf("Send Queue="); 401 printaddr(CurEnv->e_sendqueue, TRUE); 402 break; 403 404 case CMDDBGDEBUG: /* set debug mode */ 405 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 406 tTflag(p); 407 message("200", "Debug set"); 408 break; 409 # endif DEBUG 410 411 # ifdef WIZ 412 case CMDDBGKILL: /* kill the parent */ 413 if (!iswiz()) 414 break; 415 if (kill(MotherPid, SIGTERM) >= 0) 416 message("200", "Mother is dead"); 417 else 418 message("500", "Can't kill Mom"); 419 break; 420 421 case CMDDBGWIZ: /* become a wizard */ 422 if (WizWord != NULL) 423 { 424 char seed[3]; 425 extern char *crypt(); 426 427 (void) strncpy(seed, WizWord, 2); 428 if (strcmp(WizWord, crypt(p, seed)) == 0) 429 { 430 IsWiz = TRUE; 431 message("200", "Please pass, oh mighty wizard"); 432 break; 433 } 434 } 435 message("500", "You are no wizard!"); 436 break; 437 438 # else WIZ 439 case CMDDBGWIZ: /* try to become a wizard */ 440 message("500", "You wascal wabbit! Wandering wizards won't win!"); 441 break; 442 # endif WIZ 443 444 case CMDERROR: /* unknown command */ 445 message("500", "Command unrecognized"); 446 break; 447 448 default: 449 syserr("smtp: unknown code %d", c->cmdcode); 450 break; 451 } 452 } 453 } 454 /* 455 ** SKIPWORD -- skip a fixed word. 456 ** 457 ** Parameters: 458 ** p -- place to start looking. 459 ** w -- word to skip. 460 ** 461 ** Returns: 462 ** p following w. 463 ** NULL on error. 464 ** 465 ** Side Effects: 466 ** clobbers the p data area. 467 */ 468 469 static char * 470 skipword(p, w) 471 register char *p; 472 char *w; 473 { 474 register char *q; 475 extern bool sameword(); 476 477 /* find beginning of word */ 478 while (isspace(*p)) 479 p++; 480 q = p; 481 482 /* find end of word */ 483 while (*p != '\0' && *p != ':' && !isspace(*p)) 484 p++; 485 while (isspace(*p)) 486 *p++ = '\0'; 487 if (*p != ':') 488 { 489 syntax: 490 message("501", "Syntax error"); 491 Errors++; 492 return (NULL); 493 } 494 *p++ = '\0'; 495 while (isspace(*p)) 496 p++; 497 498 /* see if the input word matches desired word */ 499 if (!sameword(q, w)) 500 goto syntax; 501 502 return (p); 503 } 504 /* 505 ** HELP -- implement the HELP command. 506 ** 507 ** Parameters: 508 ** topic -- the topic we want help for. 509 ** 510 ** Returns: 511 ** none. 512 ** 513 ** Side Effects: 514 ** outputs the help file to message output. 515 */ 516 517 help(topic) 518 char *topic; 519 { 520 register FILE *hf; 521 int len; 522 char buf[MAXLINE]; 523 bool noinfo; 524 525 if (HelpFile == NULL || (hf = fopen(HelpFile, "r")) == NULL) 526 { 527 /* no help */ 528 errno = 0; 529 message("502", "HELP not implemented"); 530 return; 531 } 532 533 len = strlen(topic); 534 makelower(topic); 535 noinfo = TRUE; 536 537 while (fgets(buf, sizeof buf, hf) != NULL) 538 { 539 if (strncmp(buf, topic, len) == 0) 540 { 541 register char *p; 542 543 p = index(buf, '\t'); 544 if (p == NULL) 545 p = buf; 546 else 547 p++; 548 fixcrlf(p, TRUE); 549 message("214-", p); 550 noinfo = FALSE; 551 } 552 } 553 554 if (noinfo) 555 message("504", "HELP topic unknown"); 556 else 557 message("214", "End of HELP info"); 558 (void) fclose(hf); 559 } 560 /* 561 ** ISWIZ -- tell us if we are a wizard 562 ** 563 ** If not, print a nasty message. 564 ** 565 ** Parameters: 566 ** none. 567 ** 568 ** Returns: 569 ** TRUE if we are a wizard. 570 ** FALSE if we are not a wizard. 571 ** 572 ** Side Effects: 573 ** Prints a 500 exit stat if we are not a wizard. 574 */ 575 576 #ifdef WIZ 577 578 bool 579 iswiz() 580 { 581 if (!IsWiz) 582 message("500", "Mere mortals musn't mutter that mantra"); 583 return (IsWiz); 584 } 585 586 #endif WIZ 587 /* 588 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 589 ** 590 ** Parameters: 591 ** label -- a string used in error messages 592 ** 593 ** Returns: 594 ** zero in the child 595 ** one in the parent 596 ** 597 ** Side Effects: 598 ** none. 599 */ 600 601 runinchild(label) 602 char *label; 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 clearenvelope(CurEnv); 634 } 635 } 636 637 /* open alias database */ 638 initaliases(AliasFile, FALSE); 639 640 return (0); 641 } 642 643 # endif SMTP 644