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