1 /* 2 * Copyright (c) 1998 Sendmail, Inc. All rights reserved. 3 * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. 4 * Copyright (c) 1988, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * By using this file, you agree to the terms and conditions set 8 * forth in the LICENSE file which can be found at the top level of 9 * the sendmail distribution. 10 * 11 */ 12 13 # include "sendmail.h" 14 15 #ifndef lint 16 #if SMTP 17 static char sccsid[] = "@(#)srvrsmtp.c 8.187 (Berkeley) 10/23/1998 (with SMTP)"; 18 #else 19 static char sccsid[] = "@(#)srvrsmtp.c 8.187 (Berkeley) 10/23/1998 (without SMTP)"; 20 #endif 21 #endif /* not lint */ 22 23 # include <errno.h> 24 25 # if SMTP 26 27 /* 28 ** SMTP -- run the SMTP protocol. 29 ** 30 ** Parameters: 31 ** nullserver -- if non-NULL, rejection message for 32 ** all SMTP commands. 33 ** e -- the envelope. 34 ** 35 ** Returns: 36 ** never. 37 ** 38 ** Side Effects: 39 ** Reads commands from the input channel and processes 40 ** them. 41 */ 42 43 struct cmd 44 { 45 char *cmdname; /* command name */ 46 int cmdcode; /* internal code, see below */ 47 }; 48 49 /* values for cmdcode */ 50 # define CMDERROR 0 /* bad command */ 51 # define CMDMAIL 1 /* mail -- designate sender */ 52 # define CMDRCPT 2 /* rcpt -- designate recipient */ 53 # define CMDDATA 3 /* data -- send message text */ 54 # define CMDRSET 4 /* rset -- reset state */ 55 # define CMDVRFY 5 /* vrfy -- verify address */ 56 # define CMDEXPN 6 /* expn -- expand address */ 57 # define CMDNOOP 7 /* noop -- do nothing */ 58 # define CMDQUIT 8 /* quit -- close connection and die */ 59 # define CMDHELO 9 /* helo -- be polite */ 60 # define CMDHELP 10 /* help -- give usage info */ 61 # define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */ 62 # define CMDETRN 12 /* etrn -- flush queue */ 63 /* non-standard commands */ 64 # define CMDONEX 16 /* onex -- sending one transaction only */ 65 # define CMDVERB 17 /* verb -- go into verbose mode */ 66 # define CMDXUSR 18 /* xusr -- initial (user) submission */ 67 /* use this to catch and log "door handle" attempts on your system */ 68 # define CMDLOGBOGUS 23 /* bogus command that should be logged */ 69 /* debugging-only commands, only enabled if SMTPDEBUG is defined */ 70 # define CMDDBGQSHOW 24 /* showq -- show send queue */ 71 # define CMDDBGDEBUG 25 /* debug -- set debug mode */ 72 73 static struct cmd CmdTab[] = 74 { 75 { "mail", CMDMAIL }, 76 { "rcpt", CMDRCPT }, 77 { "data", CMDDATA }, 78 { "rset", CMDRSET }, 79 { "vrfy", CMDVRFY }, 80 { "expn", CMDEXPN }, 81 { "help", CMDHELP }, 82 { "noop", CMDNOOP }, 83 { "quit", CMDQUIT }, 84 { "helo", CMDHELO }, 85 { "ehlo", CMDEHLO }, 86 { "etrn", CMDETRN }, 87 { "verb", CMDVERB }, 88 { "onex", CMDONEX }, 89 { "xusr", CMDXUSR }, 90 /* remaining commands are here only to trap and log attempts to use them */ 91 { "showq", CMDDBGQSHOW }, 92 { "debug", CMDDBGDEBUG }, 93 { "wiz", CMDLOGBOGUS }, 94 95 { NULL, CMDERROR } 96 }; 97 98 bool OneXact = FALSE; /* one xaction only this run */ 99 char *CurSmtpClient; /* who's at the other end of channel */ 100 101 static char *skipword __P((char *volatile, char *)); 102 103 104 #define MAXBADCOMMANDS 25 /* maximum number of bad commands */ 105 #define MAXNOOPCOMMANDS 20 /* max "noise" commands before slowdown */ 106 #define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */ 107 #define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */ 108 #define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */ 109 110 void 111 smtp(nullserver, e) 112 char *nullserver; 113 register ENVELOPE *volatile e; 114 { 115 register char *volatile p; 116 register struct cmd *c; 117 char *cmd; 118 auto ADDRESS *vrfyqueue; 119 ADDRESS *a; 120 volatile bool gotmail; /* mail command received */ 121 volatile bool gothello; /* helo command received */ 122 bool vrfy; /* set if this is a vrfy command */ 123 char *volatile protocol; /* sending protocol */ 124 char *volatile sendinghost; /* sending hostname */ 125 char *volatile peerhostname; /* name of SMTP peer or "localhost" */ 126 auto char *delimptr; 127 char *id; 128 volatile int nrcpts = 0; /* number of RCPT commands */ 129 bool doublequeue; 130 volatile bool discard; 131 volatile int badcommands = 0; /* count of bad commands */ 132 volatile int nverifies = 0; /* count of VRFY/EXPN commands */ 133 volatile int n_etrn = 0; /* count of ETRN commands */ 134 volatile int n_noop = 0; /* count of NOOP/VERB/ONEX etc cmds */ 135 volatile int n_helo = 0; /* count of HELO/EHLO commands */ 136 bool ok; 137 volatile int lognullconnection = TRUE; 138 register char *q; 139 QUEUE_CHAR *new; 140 char inp[MAXLINE]; 141 char cmdbuf[MAXLINE]; 142 extern ENVELOPE BlankEnvelope; 143 extern void help __P((char *)); 144 extern void settime __P((ENVELOPE *)); 145 extern bool enoughdiskspace __P((long)); 146 extern int runinchild __P((char *, ENVELOPE *)); 147 extern void checksmtpattack __P((volatile int *, int, char *, ENVELOPE *)); 148 149 if (fileno(OutChannel) != fileno(stdout)) 150 { 151 /* arrange for debugging output to go to remote host */ 152 (void) dup2(fileno(OutChannel), fileno(stdout)); 153 } 154 settime(e); 155 peerhostname = RealHostName; 156 if (peerhostname == NULL) 157 peerhostname = "localhost"; 158 CurHostName = peerhostname; 159 CurSmtpClient = macvalue('_', e); 160 if (CurSmtpClient == NULL) 161 CurSmtpClient = CurHostName; 162 163 /* check_relay may have set discard bit, save for later */ 164 discard = bitset(EF_DISCARD, e->e_flags); 165 166 sm_setproctitle(TRUE, "server %s startup", CurSmtpClient); 167 #if DAEMON 168 if (LogLevel > 11) 169 { 170 /* log connection information */ 171 sm_syslog(LOG_INFO, NOQID, 172 "SMTP connect from %.100s (%.100s)", 173 CurSmtpClient, anynet_ntoa(&RealHostAddr)); 174 } 175 #endif 176 177 /* output the first line, inserting "ESMTP" as second word */ 178 expand(SmtpGreeting, inp, sizeof inp, e); 179 p = strchr(inp, '\n'); 180 if (p != NULL) 181 *p++ = '\0'; 182 id = strchr(inp, ' '); 183 if (id == NULL) 184 id = &inp[strlen(inp)]; 185 cmd = p == NULL ? "220 %.*s ESMTP%s" : "220-%.*s ESMTP%s"; 186 message(cmd, id - inp, inp, id); 187 188 /* output remaining lines */ 189 while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL) 190 { 191 *p++ = '\0'; 192 if (isascii(*id) && isspace(*id)) 193 id++; 194 message("220-%s", id); 195 } 196 if (id != NULL) 197 { 198 if (isascii(*id) && isspace(*id)) 199 id++; 200 message("220 %s", id); 201 } 202 203 protocol = NULL; 204 sendinghost = macvalue('s', e); 205 gothello = FALSE; 206 gotmail = FALSE; 207 for (;;) 208 { 209 /* arrange for backout */ 210 (void) setjmp(TopFrame); 211 QuickAbort = FALSE; 212 HoldErrs = FALSE; 213 SuprErrs = FALSE; 214 LogUsrErrs = FALSE; 215 OnlyOneError = TRUE; 216 e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS); 217 218 /* setup for the read */ 219 e->e_to = NULL; 220 Errors = 0; 221 (void) fflush(stdout); 222 223 /* read the input line */ 224 SmtpPhase = "server cmd read"; 225 sm_setproctitle(TRUE, "server %s cmd read", CurSmtpClient); 226 p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand, 227 SmtpPhase); 228 229 /* handle errors */ 230 if (p == NULL) 231 { 232 /* end of file, just die */ 233 disconnect(1, e); 234 message("421 %s Lost input channel from %s", 235 MyHostName, CurSmtpClient); 236 if (LogLevel > (gotmail ? 1 : 19)) 237 sm_syslog(LOG_NOTICE, e->e_id, 238 "lost input channel from %.100s", 239 CurSmtpClient); 240 if (lognullconnection && LogLevel > 5) 241 sm_syslog(LOG_INFO, NULL, 242 "Null connection from %.100s", 243 CurSmtpClient); 244 245 /* 246 ** If have not accepted mail (DATA), do not bounce 247 ** bad addresses back to sender. 248 */ 249 if (bitset(EF_CLRQUEUE, e->e_flags)) 250 e->e_sendqueue = NULL; 251 252 if (InChild) 253 ExitStat = EX_QUIT; 254 finis(TRUE, ExitStat); 255 } 256 257 /* clean up end of line */ 258 fixcrlf(inp, TRUE); 259 260 /* echo command to transcript */ 261 if (e->e_xfp != NULL) 262 fprintf(e->e_xfp, "<<< %s\n", inp); 263 264 if (LogLevel >= 15) 265 sm_syslog(LOG_INFO, e->e_id, 266 "<-- %s", 267 inp); 268 269 if (e->e_id == NULL) 270 sm_setproctitle(TRUE, "%s: %.80s", CurSmtpClient, inp); 271 else 272 sm_setproctitle(TRUE, "%s %s: %.80s", e->e_id, CurSmtpClient, inp); 273 274 /* break off command */ 275 for (p = inp; isascii(*p) && isspace(*p); p++) 276 continue; 277 cmd = cmdbuf; 278 while (*p != '\0' && 279 !(isascii(*p) && isspace(*p)) && 280 cmd < &cmdbuf[sizeof cmdbuf - 2]) 281 *cmd++ = *p++; 282 *cmd = '\0'; 283 284 /* throw away leading whitespace */ 285 while (isascii(*p) && isspace(*p)) 286 p++; 287 288 /* decode command */ 289 for (c = CmdTab; c->cmdname != NULL; c++) 290 { 291 if (!strcasecmp(c->cmdname, cmdbuf)) 292 break; 293 } 294 295 /* reset errors */ 296 errno = 0; 297 298 /* 299 ** Process command. 300 ** 301 ** If we are running as a null server, return 550 302 ** to everything. 303 */ 304 305 if (nullserver != NULL) 306 { 307 switch (c->cmdcode) 308 { 309 case CMDQUIT: 310 case CMDHELO: 311 case CMDEHLO: 312 case CMDNOOP: 313 /* process normally */ 314 break; 315 316 default: 317 if (++badcommands > MAXBADCOMMANDS) 318 sleep(1); 319 usrerr("550 %s", nullserver); 320 continue; 321 } 322 } 323 324 /* non-null server */ 325 switch (c->cmdcode) 326 { 327 case CMDMAIL: 328 case CMDEXPN: 329 case CMDVRFY: 330 case CMDETRN: 331 lognullconnection = FALSE; 332 } 333 334 switch (c->cmdcode) 335 { 336 case CMDHELO: /* hello -- introduce yourself */ 337 case CMDEHLO: /* extended hello */ 338 if (c->cmdcode == CMDEHLO) 339 { 340 protocol = "ESMTP"; 341 SmtpPhase = "server EHLO"; 342 } 343 else 344 { 345 protocol = "SMTP"; 346 SmtpPhase = "server HELO"; 347 } 348 349 /* avoid denial-of-service */ 350 checksmtpattack(&n_helo, MAXHELOCOMMANDS, "HELO/EHLO", e); 351 352 /* check for duplicate HELO/EHLO per RFC 1651 4.2 */ 353 if (gothello) 354 { 355 usrerr("503 %s Duplicate HELO/EHLO", 356 MyHostName); 357 break; 358 } 359 360 /* check for valid domain name (re 1123 5.2.5) */ 361 if (*p == '\0' && !AllowBogusHELO) 362 { 363 usrerr("501 %s requires domain address", 364 cmdbuf); 365 break; 366 } 367 368 /* check for long domain name (hides Received: info) */ 369 if (strlen(p) > MAXNAME) 370 { 371 usrerr("501 Invalid domain name"); 372 break; 373 } 374 375 for (q = p; *q != '\0'; q++) 376 { 377 if (!isascii(*q)) 378 break; 379 if (isalnum(*q)) 380 continue; 381 if (isspace(*q)) 382 { 383 *q = '\0'; 384 break; 385 } 386 if (strchr("[].-_#", *q) == NULL) 387 break; 388 } 389 if (*q == '\0') 390 { 391 q = "pleased to meet you"; 392 sendinghost = newstr(p); 393 } 394 else if (!AllowBogusHELO) 395 { 396 usrerr("501 Invalid domain name"); 397 break; 398 } 399 else 400 { 401 q = "accepting invalid domain name"; 402 } 403 404 gothello = TRUE; 405 406 /* print HELO response message */ 407 if (c->cmdcode != CMDEHLO || nullserver != NULL) 408 { 409 message("250 %s Hello %s, %s", 410 MyHostName, CurSmtpClient, q); 411 break; 412 } 413 414 message("250-%s Hello %s, %s", 415 MyHostName, CurSmtpClient, q); 416 417 /* print EHLO features list */ 418 if (!bitset(PRIV_NOEXPN, PrivacyFlags)) 419 { 420 message("250-EXPN"); 421 if (!bitset(PRIV_NOVERB, PrivacyFlags)) 422 message("250-VERB"); 423 } 424 #if MIME8TO7 425 message("250-8BITMIME"); 426 #endif 427 if (MaxMessageSize > 0) 428 message("250-SIZE %ld", MaxMessageSize); 429 else 430 message("250-SIZE"); 431 #if DSN 432 if (SendMIMEErrors) 433 message("250-DSN"); 434 #endif 435 message("250-ONEX"); 436 if (!bitset(PRIV_NOETRN, PrivacyFlags)) 437 message("250-ETRN"); 438 message("250-XUSR"); 439 message("250 HELP"); 440 break; 441 442 case CMDMAIL: /* mail -- designate sender */ 443 SmtpPhase = "server MAIL"; 444 445 /* check for validity of this command */ 446 if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags)) 447 { 448 usrerr("503 Polite people say HELO first"); 449 break; 450 } 451 if (gotmail) 452 { 453 usrerr("503 Sender already specified"); 454 break; 455 } 456 if (InChild) 457 { 458 errno = 0; 459 syserr("503 Nested MAIL command: MAIL %s", p); 460 finis(TRUE, ExitStat); 461 } 462 463 /* make sure we know who the sending host is */ 464 if (sendinghost == NULL) 465 sendinghost = peerhostname; 466 467 p = skipword(p, "from"); 468 if (p == NULL) 469 break; 470 471 /* fork a subprocess to process this command */ 472 if (runinchild("SMTP-MAIL", e) > 0) 473 break; 474 if (Errors > 0) 475 goto undo_subproc_no_pm; 476 if (!gothello) 477 { 478 auth_warning(e, 479 "%s didn't use HELO protocol", 480 CurSmtpClient); 481 } 482 #ifdef PICKY_HELO_CHECK 483 if (strcasecmp(sendinghost, peerhostname) != 0 && 484 (strcasecmp(peerhostname, "localhost") != 0 || 485 strcasecmp(sendinghost, MyHostName) != 0)) 486 { 487 auth_warning(e, "Host %s claimed to be %s", 488 CurSmtpClient, sendinghost); 489 } 490 #endif 491 492 if (protocol == NULL) 493 protocol = "SMTP"; 494 define('r', protocol, e); 495 define('s', sendinghost, e); 496 initsys(e); 497 if (Errors > 0) 498 goto undo_subproc_no_pm; 499 nrcpts = 0; 500 e->e_flags |= EF_LOGSENDER|EF_CLRQUEUE; 501 sm_setproctitle(TRUE, "%s %s: %.80s", e->e_id, CurSmtpClient, inp); 502 503 /* child -- go do the processing */ 504 if (setjmp(TopFrame) > 0) 505 { 506 /* this failed -- undo work */ 507 undo_subproc_no_pm: 508 e->e_flags &= ~EF_PM_NOTIFY; 509 undo_subproc: 510 if (InChild) 511 { 512 QuickAbort = FALSE; 513 SuprErrs = TRUE; 514 e->e_flags &= ~EF_FATALERRS; 515 finis(TRUE, ExitStat); 516 } 517 break; 518 } 519 QuickAbort = TRUE; 520 521 /* must parse sender first */ 522 delimptr = NULL; 523 setsender(p, e, &delimptr, ' ', FALSE); 524 if (delimptr != NULL && *delimptr != '\0') 525 *delimptr++ = '\0'; 526 if (Errors > 0) 527 goto undo_subproc_no_pm; 528 529 /* do config file checking of the sender */ 530 if (rscheck("check_mail", p, NULL, e) != EX_OK || 531 Errors > 0) 532 goto undo_subproc_no_pm; 533 534 /* check for possible spoofing */ 535 if (RealUid != 0 && OpMode == MD_SMTP && 536 !wordinclass(RealUserName, 't') && 537 !bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) && 538 strcmp(e->e_from.q_user, RealUserName) != 0) 539 { 540 auth_warning(e, "%s owned process doing -bs", 541 RealUserName); 542 } 543 544 /* now parse ESMTP arguments */ 545 e->e_msgsize = 0; 546 p = delimptr; 547 while (p != NULL && *p != '\0') 548 { 549 char *kp; 550 char *vp = NULL; 551 extern void mail_esmtp_args __P((char *, char *, ENVELOPE *)); 552 553 /* locate the beginning of the keyword */ 554 while (isascii(*p) && isspace(*p)) 555 p++; 556 if (*p == '\0') 557 break; 558 kp = p; 559 560 /* skip to the value portion */ 561 while ((isascii(*p) && isalnum(*p)) || *p == '-') 562 p++; 563 if (*p == '=') 564 { 565 *p++ = '\0'; 566 vp = p; 567 568 /* skip to the end of the value */ 569 while (*p != '\0' && *p != ' ' && 570 !(isascii(*p) && iscntrl(*p)) && 571 *p != '=') 572 p++; 573 } 574 575 if (*p != '\0') 576 *p++ = '\0'; 577 578 if (tTd(19, 1)) 579 printf("MAIL: got arg %s=\"%s\"\n", kp, 580 vp == NULL ? "<null>" : vp); 581 582 mail_esmtp_args(kp, vp, e); 583 if (Errors > 0) 584 goto undo_subproc_no_pm; 585 } 586 if (Errors > 0) 587 goto undo_subproc_no_pm; 588 589 if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) 590 { 591 usrerr("552 Message size exceeds fixed maximum message size (%ld)", 592 MaxMessageSize); 593 goto undo_subproc_no_pm; 594 } 595 596 if (!enoughdiskspace(e->e_msgsize)) 597 { 598 usrerr("452 Insufficient disk space; try again later"); 599 goto undo_subproc_no_pm; 600 } 601 if (Errors > 0) 602 goto undo_subproc_no_pm; 603 message("250 Sender ok"); 604 gotmail = TRUE; 605 break; 606 607 case CMDRCPT: /* rcpt -- designate recipient */ 608 if (!gotmail) 609 { 610 usrerr("503 Need MAIL before RCPT"); 611 break; 612 } 613 SmtpPhase = "server RCPT"; 614 if (setjmp(TopFrame) > 0) 615 { 616 e->e_flags &= ~EF_FATALERRS; 617 break; 618 } 619 QuickAbort = TRUE; 620 LogUsrErrs = TRUE; 621 622 /* limit flooding of our machine */ 623 if (MaxRcptPerMsg > 0 && nrcpts >= MaxRcptPerMsg) 624 { 625 usrerr("452 Too many recipients"); 626 break; 627 } 628 629 if (e->e_sendmode != SM_DELIVER) 630 e->e_flags |= EF_VRFYONLY; 631 632 p = skipword(p, "to"); 633 if (p == NULL) 634 break; 635 a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr, e); 636 if (Errors > 0) 637 break; 638 if (a == NULL) 639 { 640 usrerr("501 Missing recipient"); 641 break; 642 } 643 644 if (delimptr != NULL && *delimptr != '\0') 645 *delimptr++ = '\0'; 646 647 /* do config file checking of the recipient */ 648 if (rscheck("check_rcpt", p, NULL, e) != EX_OK || 649 Errors > 0) 650 break; 651 652 /* now parse ESMTP arguments */ 653 p = delimptr; 654 while (p != NULL && *p != '\0') 655 { 656 char *kp; 657 char *vp = NULL; 658 extern void rcpt_esmtp_args __P((ADDRESS *, char *, char *, ENVELOPE *)); 659 660 /* locate the beginning of the keyword */ 661 while (isascii(*p) && isspace(*p)) 662 p++; 663 if (*p == '\0') 664 break; 665 kp = p; 666 667 /* skip to the value portion */ 668 while ((isascii(*p) && isalnum(*p)) || *p == '-') 669 p++; 670 if (*p == '=') 671 { 672 *p++ = '\0'; 673 vp = p; 674 675 /* skip to the end of the value */ 676 while (*p != '\0' && *p != ' ' && 677 !(isascii(*p) && iscntrl(*p)) && 678 *p != '=') 679 p++; 680 } 681 682 if (*p != '\0') 683 *p++ = '\0'; 684 685 if (tTd(19, 1)) 686 printf("RCPT: got arg %s=\"%s\"\n", kp, 687 vp == NULL ? "<null>" : vp); 688 689 rcpt_esmtp_args(a, kp, vp, e); 690 if (Errors > 0) 691 break; 692 } 693 if (Errors > 0) 694 break; 695 696 /* save in recipient list after ESMTP mods */ 697 a = recipient(a, &e->e_sendqueue, 0, e); 698 if (Errors > 0) 699 break; 700 701 /* no errors during parsing, but might be a duplicate */ 702 e->e_to = a->q_paddr; 703 if (!bitset(QBADADDR, a->q_flags)) 704 { 705 message("250 Recipient ok%s", 706 bitset(QQUEUEUP, a->q_flags) ? 707 " (will queue)" : ""); 708 nrcpts++; 709 } 710 else 711 { 712 /* punt -- should keep message in ADDRESS.... */ 713 usrerr("550 Addressee unknown"); 714 } 715 break; 716 717 case CMDDATA: /* data -- text of mail */ 718 SmtpPhase = "server DATA"; 719 if (!gotmail) 720 { 721 usrerr("503 Need MAIL command"); 722 break; 723 } 724 else if (nrcpts <= 0) 725 { 726 usrerr("503 Need RCPT (recipient)"); 727 break; 728 } 729 730 /* put back discard bit */ 731 if (discard) 732 e->e_flags |= EF_DISCARD; 733 734 /* check to see if we need to re-expand aliases */ 735 /* also reset QBADADDR on already-diagnosted addrs */ 736 doublequeue = FALSE; 737 for (a = e->e_sendqueue; a != NULL; a = a->q_next) 738 { 739 if (bitset(QVERIFIED, a->q_flags) && 740 !bitset(EF_DISCARD, e->e_flags)) 741 { 742 /* need to re-expand aliases */ 743 doublequeue = TRUE; 744 } 745 if (bitset(QBADADDR, a->q_flags)) 746 { 747 /* make this "go away" */ 748 a->q_flags |= QDONTSEND; 749 a->q_flags &= ~QBADADDR; 750 } 751 } 752 753 /* collect the text of the message */ 754 SmtpPhase = "collect"; 755 buffer_errors(); 756 collect(InChannel, TRUE, NULL, e); 757 if (Errors > 0) 758 { 759 flush_errors(TRUE); 760 buffer_errors(); 761 goto abortmessage; 762 } 763 764 /* make sure we actually do delivery */ 765 e->e_flags &= ~EF_CLRQUEUE; 766 767 /* from now on, we have to operate silently */ 768 buffer_errors(); 769 e->e_errormode = EM_MAIL; 770 771 /* 772 ** Arrange to send to everyone. 773 ** If sending to multiple people, mail back 774 ** errors rather than reporting directly. 775 ** In any case, don't mail back errors for 776 ** anything that has happened up to 777 ** now (the other end will do this). 778 ** Truncate our transcript -- the mail has gotten 779 ** to us successfully, and if we have 780 ** to mail this back, it will be easier 781 ** on the reader. 782 ** Then send to everyone. 783 ** Finally give a reply code. If an error has 784 ** already been given, don't mail a 785 ** message back. 786 ** We goose error returns by clearing error bit. 787 */ 788 789 SmtpPhase = "delivery"; 790 e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); 791 id = e->e_id; 792 793 if (doublequeue) 794 { 795 /* make sure it is in the queue */ 796 queueup(e, FALSE); 797 } 798 else 799 { 800 /* send to all recipients */ 801 sendall(e, SM_DEFAULT); 802 } 803 e->e_to = NULL; 804 805 /* issue success message */ 806 message("250 %s Message accepted for delivery", id); 807 808 /* if we just queued, poke it */ 809 if (doublequeue && 810 e->e_sendmode != SM_QUEUE && 811 e->e_sendmode != SM_DEFER) 812 { 813 CurrentLA = getla(); 814 815 if (!shouldqueue(e->e_msgpriority, e->e_ctime)) 816 { 817 unlockqueue(e); 818 (void) dowork(id, TRUE, TRUE, e); 819 } 820 } 821 822 abortmessage: 823 /* if in a child, pop back to our parent */ 824 if (InChild) 825 finis(TRUE, ExitStat); 826 827 /* clean up a bit */ 828 gotmail = FALSE; 829 dropenvelope(e, TRUE); 830 CurEnv = e = newenvelope(e, CurEnv); 831 e->e_flags = BlankEnvelope.e_flags; 832 break; 833 834 case CMDRSET: /* rset -- reset state */ 835 if (tTd(94, 100)) 836 message("451 Test failure"); 837 else 838 message("250 Reset state"); 839 840 /* arrange to ignore any current send list */ 841 e->e_sendqueue = NULL; 842 e->e_flags |= EF_CLRQUEUE; 843 if (InChild) 844 finis(TRUE, ExitStat); 845 846 /* clean up a bit */ 847 gotmail = FALSE; 848 SuprErrs = TRUE; 849 dropenvelope(e, TRUE); 850 CurEnv = e = newenvelope(e, CurEnv); 851 break; 852 853 case CMDVRFY: /* vrfy -- verify address */ 854 case CMDEXPN: /* expn -- expand address */ 855 checksmtpattack(&nverifies, MAXVRFYCOMMANDS, 856 c->cmdcode == CMDVRFY ? "VRFY" : "EXPN", e); 857 vrfy = c->cmdcode == CMDVRFY; 858 if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, 859 PrivacyFlags)) 860 { 861 if (vrfy) 862 message("252 Cannot VRFY user; try RCPT to attempt delivery (or try finger)"); 863 else 864 message("502 Sorry, we do not allow this operation"); 865 if (LogLevel > 5) 866 sm_syslog(LOG_INFO, e->e_id, 867 "%.100s: %s [rejected]", 868 CurSmtpClient, 869 shortenstring(inp, MAXSHORTSTR)); 870 break; 871 } 872 else if (!gothello && 873 bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, 874 PrivacyFlags)) 875 { 876 usrerr("503 I demand that you introduce yourself first"); 877 break; 878 } 879 if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) 880 break; 881 if (Errors > 0) 882 goto undo_subproc; 883 if (LogLevel > 5) 884 sm_syslog(LOG_INFO, e->e_id, 885 "%.100s: %s", 886 CurSmtpClient, 887 shortenstring(inp, MAXSHORTSTR)); 888 if (setjmp(TopFrame) > 0) 889 goto undo_subproc; 890 QuickAbort = TRUE; 891 vrfyqueue = NULL; 892 if (vrfy) 893 e->e_flags |= EF_VRFYONLY; 894 while (*p != '\0' && isascii(*p) && isspace(*p)) 895 p++; 896 if (*p == '\0') 897 { 898 usrerr("501 Argument required"); 899 } 900 else 901 { 902 (void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e); 903 } 904 if (Errors > 0) 905 goto undo_subproc; 906 if (vrfyqueue == NULL) 907 { 908 usrerr("554 Nothing to %s", vrfy ? "VRFY" : "EXPN"); 909 } 910 while (vrfyqueue != NULL) 911 { 912 extern void printvrfyaddr __P((ADDRESS *, bool, bool)); 913 914 a = vrfyqueue; 915 while ((a = a->q_next) != NULL && 916 bitset(QDONTSEND|QBADADDR, a->q_flags)) 917 continue; 918 if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) 919 printvrfyaddr(vrfyqueue, a == NULL, vrfy); 920 vrfyqueue = vrfyqueue->q_next; 921 } 922 if (InChild) 923 finis(TRUE, ExitStat); 924 break; 925 926 case CMDETRN: /* etrn -- force queue flush */ 927 if (bitset(PRIV_NOETRN, PrivacyFlags)) 928 { 929 message("502 Sorry, we do not allow this operation"); 930 if (LogLevel > 5) 931 sm_syslog(LOG_INFO, e->e_id, 932 "%.100s: %s [rejected]", 933 CurSmtpClient, 934 shortenstring(inp, MAXSHORTSTR)); 935 break; 936 } 937 938 if (strlen(p) <= 0) 939 { 940 usrerr("500 Parameter required"); 941 break; 942 } 943 944 /* crude way to avoid denial-of-service attacks */ 945 checksmtpattack(&n_etrn, MAXETRNCOMMANDS, "ETRN", e); 946 947 if (LogLevel > 5) 948 sm_syslog(LOG_INFO, e->e_id, 949 "%.100s: ETRN %s", 950 CurSmtpClient, 951 shortenstring(p, MAXSHORTSTR)); 952 953 id = p; 954 if (*id == '@') 955 id++; 956 else 957 *--id = '@'; 958 959 if ((new = (QUEUE_CHAR *)malloc(sizeof(QUEUE_CHAR))) == NULL) 960 { 961 syserr("500 ETRN out of memory"); 962 break; 963 } 964 new->queue_match = id; 965 new->queue_next = NULL; 966 QueueLimitRecipient = new; 967 ok = runqueue(TRUE, TRUE); 968 free(QueueLimitRecipient); 969 QueueLimitRecipient = NULL; 970 if (ok && Errors == 0) 971 message("250 Queuing for node %s started", p); 972 break; 973 974 case CMDHELP: /* help -- give user info */ 975 help(p); 976 break; 977 978 case CMDNOOP: /* noop -- do nothing */ 979 checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "NOOP", e); 980 message("250 OK"); 981 break; 982 983 case CMDQUIT: /* quit -- leave mail */ 984 message("221 %s closing connection", MyHostName); 985 986 doquit: 987 /* arrange to ignore any current send list */ 988 e->e_sendqueue = NULL; 989 990 /* avoid future 050 messages */ 991 disconnect(1, e); 992 993 if (InChild) 994 ExitStat = EX_QUIT; 995 if (lognullconnection && LogLevel > 5) 996 sm_syslog(LOG_INFO, NULL, 997 "Null connection from %.100s", 998 CurSmtpClient); 999 finis(TRUE, ExitStat); 1000 1001 case CMDVERB: /* set verbose mode */ 1002 if (bitset(PRIV_NOEXPN, PrivacyFlags) || 1003 bitset(PRIV_NOVERB, PrivacyFlags)) 1004 { 1005 /* this would give out the same info */ 1006 message("502 Verbose unavailable"); 1007 break; 1008 } 1009 checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "VERB", e); 1010 Verbose = 1; 1011 e->e_sendmode = SM_DELIVER; 1012 message("250 Verbose mode"); 1013 break; 1014 1015 case CMDONEX: /* doing one transaction only */ 1016 checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "ONEX", e); 1017 OneXact = TRUE; 1018 message("250 Only one transaction"); 1019 break; 1020 1021 case CMDXUSR: /* initial (user) submission */ 1022 checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "XUSR", e); 1023 UserSubmission = TRUE; 1024 message("250 Initial submission"); 1025 break; 1026 1027 # if SMTPDEBUG 1028 case CMDDBGQSHOW: /* show queues */ 1029 printf("Send Queue="); 1030 printaddr(e->e_sendqueue, TRUE); 1031 break; 1032 1033 case CMDDBGDEBUG: /* set debug mode */ 1034 tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); 1035 tTflag(p); 1036 message("200 Debug set"); 1037 break; 1038 1039 # else /* not SMTPDEBUG */ 1040 case CMDDBGQSHOW: /* show queues */ 1041 case CMDDBGDEBUG: /* set debug mode */ 1042 # endif /* SMTPDEBUG */ 1043 case CMDLOGBOGUS: /* bogus command */ 1044 if (LogLevel > 0) 1045 sm_syslog(LOG_CRIT, e->e_id, 1046 "\"%s\" command from %.100s (%.100s)", 1047 c->cmdname, CurSmtpClient, 1048 anynet_ntoa(&RealHostAddr)); 1049 /* FALL THROUGH */ 1050 1051 case CMDERROR: /* unknown command */ 1052 if (++badcommands > MAXBADCOMMANDS) 1053 { 1054 message("421 %s Too many bad commands; closing connection", 1055 MyHostName); 1056 goto doquit; 1057 } 1058 1059 usrerr("500 Command unrecognized: \"%s\"", 1060 shortenstring(inp, MAXSHORTSTR)); 1061 break; 1062 1063 default: 1064 errno = 0; 1065 syserr("500 smtp: unknown code %d", c->cmdcode); 1066 break; 1067 } 1068 } 1069 } 1070 /* 1071 ** CHECKSMTPATTACK -- check for denial-of-service attack by repetition 1072 ** 1073 ** Parameters: 1074 ** pcounter -- pointer to a counter for this command. 1075 ** maxcount -- maximum value for this counter before we 1076 ** slow down. 1077 ** cname -- command name for logging. 1078 ** e -- the current envelope. 1079 ** 1080 ** Returns: 1081 ** none. 1082 ** 1083 ** Side Effects: 1084 ** Slows down if we seem to be under attack. 1085 */ 1086 1087 void 1088 checksmtpattack(pcounter, maxcount, cname, e) 1089 volatile int *pcounter; 1090 int maxcount; 1091 char *cname; 1092 ENVELOPE *e; 1093 { 1094 if (++(*pcounter) >= maxcount) 1095 { 1096 if (*pcounter == maxcount && LogLevel > 5) 1097 { 1098 sm_syslog(LOG_INFO, e->e_id, 1099 "%.100s: %.40s attack?", 1100 CurSmtpClient, cname); 1101 } 1102 sleep(*pcounter / maxcount); 1103 } 1104 } 1105 /* 1106 ** SKIPWORD -- skip a fixed word. 1107 ** 1108 ** Parameters: 1109 ** p -- place to start looking. 1110 ** w -- word to skip. 1111 ** 1112 ** Returns: 1113 ** p following w. 1114 ** NULL on error. 1115 ** 1116 ** Side Effects: 1117 ** clobbers the p data area. 1118 */ 1119 1120 static char * 1121 skipword(p, w) 1122 register char *volatile p; 1123 char *w; 1124 { 1125 register char *q; 1126 char *firstp = p; 1127 1128 /* find beginning of word */ 1129 while (isascii(*p) && isspace(*p)) 1130 p++; 1131 q = p; 1132 1133 /* find end of word */ 1134 while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p))) 1135 p++; 1136 while (isascii(*p) && isspace(*p)) 1137 *p++ = '\0'; 1138 if (*p != ':') 1139 { 1140 syntax: 1141 usrerr("501 Syntax error in parameters scanning \"%s\"", 1142 shortenstring(firstp, MAXSHORTSTR)); 1143 return (NULL); 1144 } 1145 *p++ = '\0'; 1146 while (isascii(*p) && isspace(*p)) 1147 p++; 1148 1149 if (*p == '\0') 1150 goto syntax; 1151 1152 /* see if the input word matches desired word */ 1153 if (strcasecmp(q, w)) 1154 goto syntax; 1155 1156 return (p); 1157 } 1158 /* 1159 ** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line 1160 ** 1161 ** Parameters: 1162 ** kp -- the parameter key. 1163 ** vp -- the value of that parameter. 1164 ** e -- the envelope. 1165 ** 1166 ** Returns: 1167 ** none. 1168 */ 1169 1170 void 1171 mail_esmtp_args(kp, vp, e) 1172 char *kp; 1173 char *vp; 1174 ENVELOPE *e; 1175 { 1176 if (strcasecmp(kp, "size") == 0) 1177 { 1178 if (vp == NULL) 1179 { 1180 usrerr("501 SIZE requires a value"); 1181 /* NOTREACHED */ 1182 } 1183 # if defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY) 1184 e->e_msgsize = strtoul(vp, (char **) NULL, 10); 1185 # else 1186 e->e_msgsize = strtol(vp, (char **) NULL, 10); 1187 # endif 1188 } 1189 else if (strcasecmp(kp, "body") == 0) 1190 { 1191 if (vp == NULL) 1192 { 1193 usrerr("501 BODY requires a value"); 1194 /* NOTREACHED */ 1195 } 1196 else if (strcasecmp(vp, "8bitmime") == 0) 1197 { 1198 SevenBitInput = FALSE; 1199 } 1200 else if (strcasecmp(vp, "7bit") == 0) 1201 { 1202 SevenBitInput = TRUE; 1203 } 1204 else 1205 { 1206 usrerr("501 Unknown BODY type %s", 1207 vp); 1208 /* NOTREACHED */ 1209 } 1210 e->e_bodytype = newstr(vp); 1211 } 1212 else if (strcasecmp(kp, "envid") == 0) 1213 { 1214 if (vp == NULL) 1215 { 1216 usrerr("501 ENVID requires a value"); 1217 /* NOTREACHED */ 1218 } 1219 if (!xtextok(vp)) 1220 { 1221 usrerr("501 Syntax error in ENVID parameter value"); 1222 /* NOTREACHED */ 1223 } 1224 if (e->e_envid != NULL) 1225 { 1226 usrerr("501 Duplicate ENVID parameter"); 1227 /* NOTREACHED */ 1228 } 1229 e->e_envid = newstr(vp); 1230 } 1231 else if (strcasecmp(kp, "ret") == 0) 1232 { 1233 if (vp == NULL) 1234 { 1235 usrerr("501 RET requires a value"); 1236 /* NOTREACHED */ 1237 } 1238 if (bitset(EF_RET_PARAM, e->e_flags)) 1239 { 1240 usrerr("501 Duplicate RET parameter"); 1241 /* NOTREACHED */ 1242 } 1243 e->e_flags |= EF_RET_PARAM; 1244 if (strcasecmp(vp, "hdrs") == 0) 1245 e->e_flags |= EF_NO_BODY_RETN; 1246 else if (strcasecmp(vp, "full") != 0) 1247 { 1248 usrerr("501 Bad argument \"%s\" to RET", vp); 1249 /* NOTREACHED */ 1250 } 1251 } 1252 else 1253 { 1254 usrerr("501 %s parameter unrecognized", kp); 1255 /* NOTREACHED */ 1256 } 1257 } 1258 /* 1259 ** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line 1260 ** 1261 ** Parameters: 1262 ** a -- the address corresponding to the To: parameter. 1263 ** kp -- the parameter key. 1264 ** vp -- the value of that parameter. 1265 ** e -- the envelope. 1266 ** 1267 ** Returns: 1268 ** none. 1269 */ 1270 1271 void 1272 rcpt_esmtp_args(a, kp, vp, e) 1273 ADDRESS *a; 1274 char *kp; 1275 char *vp; 1276 ENVELOPE *e; 1277 { 1278 if (strcasecmp(kp, "notify") == 0) 1279 { 1280 char *p; 1281 1282 if (vp == NULL) 1283 { 1284 usrerr("501 NOTIFY requires a value"); 1285 /* NOTREACHED */ 1286 } 1287 a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY); 1288 a->q_flags |= QHASNOTIFY; 1289 if (strcasecmp(vp, "never") == 0) 1290 return; 1291 for (p = vp; p != NULL; vp = p) 1292 { 1293 p = strchr(p, ','); 1294 if (p != NULL) 1295 *p++ = '\0'; 1296 if (strcasecmp(vp, "success") == 0) 1297 a->q_flags |= QPINGONSUCCESS; 1298 else if (strcasecmp(vp, "failure") == 0) 1299 a->q_flags |= QPINGONFAILURE; 1300 else if (strcasecmp(vp, "delay") == 0) 1301 a->q_flags |= QPINGONDELAY; 1302 else 1303 { 1304 usrerr("501 Bad argument \"%s\" to NOTIFY", 1305 vp); 1306 /* NOTREACHED */ 1307 } 1308 } 1309 } 1310 else if (strcasecmp(kp, "orcpt") == 0) 1311 { 1312 if (vp == NULL) 1313 { 1314 usrerr("501 ORCPT requires a value"); 1315 /* NOTREACHED */ 1316 } 1317 if (strchr(vp, ';') == NULL || !xtextok(vp)) 1318 { 1319 usrerr("501 Syntax error in ORCPT parameter value"); 1320 /* NOTREACHED */ 1321 } 1322 if (a->q_orcpt != NULL) 1323 { 1324 usrerr("501 Duplicate ORCPT parameter"); 1325 /* NOTREACHED */ 1326 } 1327 a->q_orcpt = newstr(vp); 1328 } 1329 else 1330 { 1331 usrerr("501 %s parameter unrecognized", kp); 1332 /* NOTREACHED */ 1333 } 1334 } 1335 /* 1336 ** PRINTVRFYADDR -- print an entry in the verify queue 1337 ** 1338 ** Parameters: 1339 ** a -- the address to print 1340 ** last -- set if this is the last one. 1341 ** vrfy -- set if this is a VRFY command. 1342 ** 1343 ** Returns: 1344 ** none. 1345 ** 1346 ** Side Effects: 1347 ** Prints the appropriate 250 codes. 1348 */ 1349 1350 void 1351 printvrfyaddr(a, last, vrfy) 1352 register ADDRESS *a; 1353 bool last; 1354 bool vrfy; 1355 { 1356 char fmtbuf[20]; 1357 1358 if (vrfy && a->q_mailer != NULL && 1359 !bitnset(M_VRFY250, a->q_mailer->m_flags)) 1360 strcpy(fmtbuf, "252"); 1361 else 1362 strcpy(fmtbuf, "250"); 1363 fmtbuf[3] = last ? ' ' : '-'; 1364 1365 if (a->q_fullname == NULL) 1366 { 1367 if (strchr(a->q_user, '@') == NULL) 1368 strcpy(&fmtbuf[4], "<%s@%s>"); 1369 else 1370 strcpy(&fmtbuf[4], "<%s>"); 1371 message(fmtbuf, a->q_user, MyHostName); 1372 } 1373 else 1374 { 1375 if (strchr(a->q_user, '@') == NULL) 1376 strcpy(&fmtbuf[4], "%s <%s@%s>"); 1377 else 1378 strcpy(&fmtbuf[4], "%s <%s>"); 1379 message(fmtbuf, a->q_fullname, a->q_user, MyHostName); 1380 } 1381 } 1382 /* 1383 ** RUNINCHILD -- return twice -- once in the child, then in the parent again 1384 ** 1385 ** Parameters: 1386 ** label -- a string used in error messages 1387 ** 1388 ** Returns: 1389 ** zero in the child 1390 ** one in the parent 1391 ** 1392 ** Side Effects: 1393 ** none. 1394 */ 1395 1396 int 1397 runinchild(label, e) 1398 char *label; 1399 register ENVELOPE *e; 1400 { 1401 pid_t childpid; 1402 1403 if (!OneXact) 1404 { 1405 /* 1406 ** Disable child process reaping, in case ETRN has preceeded 1407 ** MAIL command, and then fork. 1408 */ 1409 1410 (void) blocksignal(SIGCHLD); 1411 1412 childpid = dofork(); 1413 if (childpid < 0) 1414 { 1415 syserr("451 %s: cannot fork", label); 1416 (void) releasesignal(SIGCHLD); 1417 return (1); 1418 } 1419 if (childpid > 0) 1420 { 1421 auto int st; 1422 1423 /* parent -- wait for child to complete */ 1424 sm_setproctitle(TRUE, "server %s child wait", CurSmtpClient); 1425 st = waitfor(childpid); 1426 if (st == -1) 1427 syserr("451 %s: lost child", label); 1428 else if (!WIFEXITED(st)) 1429 syserr("451 %s: died on signal %d", 1430 label, st & 0177); 1431 1432 /* if we exited on a QUIT command, complete the process */ 1433 if (WEXITSTATUS(st) == EX_QUIT) 1434 { 1435 disconnect(1, e); 1436 finis(TRUE, ExitStat); 1437 } 1438 1439 /* restore the child signal */ 1440 (void) releasesignal(SIGCHLD); 1441 1442 return (1); 1443 } 1444 else 1445 { 1446 /* child */ 1447 InChild = TRUE; 1448 QuickAbort = FALSE; 1449 clearenvelope(e, FALSE); 1450 (void) setsignal(SIGCHLD, SIG_DFL); 1451 (void) releasesignal(SIGCHLD); 1452 } 1453 } 1454 return (0); 1455 } 1456 1457 # endif /* SMTP */ 1458 /* 1459 ** HELP -- implement the HELP command. 1460 ** 1461 ** Parameters: 1462 ** topic -- the topic we want help for. 1463 ** 1464 ** Returns: 1465 ** none. 1466 ** 1467 ** Side Effects: 1468 ** outputs the help file to message output. 1469 */ 1470 1471 void 1472 help(topic) 1473 char *topic; 1474 { 1475 register FILE *hf; 1476 int len; 1477 bool noinfo; 1478 int sff = SFF_OPENASROOT|SFF_REGONLY; 1479 char buf[MAXLINE]; 1480 extern char Version[]; 1481 1482 if (DontLockReadFiles) 1483 sff |= SFF_NOLOCK; 1484 if (!bitset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail)) 1485 sff |= SFF_SAFEDIRPATH; 1486 1487 if (HelpFile == NULL || 1488 (hf = safefopen(HelpFile, O_RDONLY, 0444, sff)) == NULL) 1489 { 1490 /* no help */ 1491 errno = 0; 1492 message("502 Sendmail %s -- HELP not implemented", Version); 1493 return; 1494 } 1495 1496 if (topic == NULL || *topic == '\0') 1497 { 1498 topic = "smtp"; 1499 message("214-This is Sendmail version %s", Version); 1500 noinfo = FALSE; 1501 } 1502 else 1503 { 1504 makelower(topic); 1505 noinfo = TRUE; 1506 } 1507 1508 len = strlen(topic); 1509 1510 while (fgets(buf, sizeof buf, hf) != NULL) 1511 { 1512 if (strncmp(buf, topic, len) == 0) 1513 { 1514 register char *p; 1515 1516 p = strchr(buf, '\t'); 1517 if (p == NULL) 1518 p = buf; 1519 else 1520 p++; 1521 fixcrlf(p, TRUE); 1522 message("214-%s", p); 1523 noinfo = FALSE; 1524 } 1525 } 1526 1527 if (noinfo) 1528 message("504 HELP topic \"%.10s\" unknown", topic); 1529 else 1530 message("214 End of HELP info"); 1531 (void) fclose(hf); 1532 } 1533