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