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