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