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