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