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