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