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