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