1 /* 2 * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1995-1997 Eric P. Allman. All rights reserved. 5 * Copyright (c) 1988, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * By using this file, you agree to the terms and conditions set 9 * forth in the LICENSE file which can be found at the top level of 10 * the sendmail distribution. 11 * 12 */ 13 14 #ifndef lint 15 static char id[] = "@(#)$Id: mci.c,v 8.133.10.8 2001/05/03 17:24:10 gshapiro Exp $"; 16 #endif /* ! lint */ 17 18 #include <sendmail.h> 19 20 21 #if NETINET || NETINET6 22 # include <arpa/inet.h> 23 #endif /* NETINET || NETINET6 */ 24 25 #include <dirent.h> 26 27 static int mci_generate_persistent_path __P((const char *, char *, 28 int, bool)); 29 static bool mci_load_persistent __P((MCI *)); 30 static void mci_uncache __P((MCI **, bool)); 31 static int mci_lock_host_statfile __P((MCI *)); 32 static int mci_read_persistent __P((FILE *, MCI *)); 33 34 /* 35 ** Mail Connection Information (MCI) Caching Module. 36 ** 37 ** There are actually two separate things cached. The first is 38 ** the set of all open connections -- these are stored in a 39 ** (small) list. The second is stored in the symbol table; it 40 ** has the overall status for all hosts, whether or not there 41 ** is a connection open currently. 42 ** 43 ** There should never be too many connections open (since this 44 ** could flood the socket table), nor should a connection be 45 ** allowed to sit idly for too long. 46 ** 47 ** MaxMciCache is the maximum number of open connections that 48 ** will be supported. 49 ** 50 ** MciCacheTimeout is the time (in seconds) that a connection 51 ** is permitted to survive without activity. 52 ** 53 ** We actually try any cached connections by sending a NOOP 54 ** before we use them; if the NOOP fails we close down the 55 ** connection and reopen it. Note that this means that a 56 ** server SMTP that doesn't support NOOP will hose the 57 ** algorithm -- but that doesn't seem too likely. 58 ** 59 ** The persistent MCI code is donated by Mark Lovell and Paul 60 ** Vixie. It is based on the long term host status code in KJS 61 ** written by Paul but has been adapted by Mark to fit into the 62 ** MCI structure. 63 */ 64 65 static MCI **MciCache; /* the open connection cache */ 66 67 /* 68 ** MCI_CACHE -- enter a connection structure into the open connection cache 69 ** 70 ** This may cause something else to be flushed. 71 ** 72 ** Parameters: 73 ** mci -- the connection to cache. 74 ** 75 ** Returns: 76 ** none. 77 */ 78 79 void 80 mci_cache(mci) 81 register MCI *mci; 82 { 83 register MCI **mcislot; 84 85 /* 86 ** Find the best slot. This may cause expired connections 87 ** to be closed. 88 */ 89 90 mcislot = mci_scan(mci); 91 if (mcislot == NULL) 92 { 93 /* we don't support caching */ 94 return; 95 } 96 97 if (mci->mci_host == NULL) 98 return; 99 100 /* if this is already cached, we are done */ 101 if (bitset(MCIF_CACHED, mci->mci_flags)) 102 return; 103 104 /* otherwise we may have to clear the slot */ 105 if (*mcislot != NULL) 106 mci_uncache(mcislot, TRUE); 107 108 if (tTd(42, 5)) 109 dprintf("mci_cache: caching %lx (%s) in slot %d\n", 110 (u_long) mci, mci->mci_host, 111 (int)(mcislot - MciCache)); 112 if (tTd(91, 100)) 113 sm_syslog(LOG_DEBUG, CurEnv->e_id, 114 "mci_cache: caching %lx (%.100s) in slot %d", 115 (u_long) mci, mci->mci_host, mcislot - MciCache); 116 117 *mcislot = mci; 118 mci->mci_flags |= MCIF_CACHED; 119 } 120 /* 121 ** MCI_SCAN -- scan the cache, flush junk, and return best slot 122 ** 123 ** Parameters: 124 ** savemci -- never flush this one. Can be null. 125 ** 126 ** Returns: 127 ** The LRU (or empty) slot. 128 */ 129 130 MCI ** 131 mci_scan(savemci) 132 MCI *savemci; 133 { 134 time_t now; 135 register MCI **bestmci; 136 register MCI *mci; 137 register int i; 138 139 if (MaxMciCache <= 0) 140 { 141 /* we don't support caching */ 142 return NULL; 143 } 144 145 if (MciCache == NULL) 146 { 147 /* first call */ 148 MciCache = (MCI **) xalloc(MaxMciCache * sizeof *MciCache); 149 memset((char *) MciCache, '\0', MaxMciCache * sizeof *MciCache); 150 return &MciCache[0]; 151 } 152 153 now = curtime(); 154 bestmci = &MciCache[0]; 155 for (i = 0; i < MaxMciCache; i++) 156 { 157 mci = MciCache[i]; 158 if (mci == NULL || mci->mci_state == MCIS_CLOSED) 159 { 160 bestmci = &MciCache[i]; 161 continue; 162 } 163 if ((mci->mci_lastuse + MciCacheTimeout < now || 164 (mci->mci_mailer != NULL && 165 mci->mci_mailer->m_maxdeliveries > 0 && 166 mci->mci_deliveries + 1 >= mci->mci_mailer->m_maxdeliveries))&& 167 mci != savemci) 168 { 169 /* connection idle too long or too many deliveries */ 170 bestmci = &MciCache[i]; 171 172 /* close it */ 173 mci_uncache(bestmci, TRUE); 174 continue; 175 } 176 if (*bestmci == NULL) 177 continue; 178 if (mci->mci_lastuse < (*bestmci)->mci_lastuse) 179 bestmci = &MciCache[i]; 180 } 181 return bestmci; 182 } 183 /* 184 ** MCI_UNCACHE -- remove a connection from a slot. 185 ** 186 ** May close a connection. 187 ** 188 ** Parameters: 189 ** mcislot -- the slot to empty. 190 ** doquit -- if TRUE, send QUIT protocol on this connection. 191 ** if FALSE, we are assumed to be in a forked child; 192 ** all we want to do is close the file(s). 193 ** 194 ** Returns: 195 ** none. 196 */ 197 198 static void 199 mci_uncache(mcislot, doquit) 200 register MCI **mcislot; 201 bool doquit; 202 { 203 register MCI *mci; 204 extern ENVELOPE BlankEnvelope; 205 206 mci = *mcislot; 207 if (mci == NULL) 208 return; 209 *mcislot = NULL; 210 if (mci->mci_host == NULL) 211 return; 212 213 mci_unlock_host(mci); 214 215 if (tTd(42, 5)) 216 dprintf("mci_uncache: uncaching %lx (%s) from slot %d (%d)\n", 217 (u_long) mci, mci->mci_host, 218 (int)(mcislot - MciCache), doquit); 219 if (tTd(91, 100)) 220 sm_syslog(LOG_DEBUG, CurEnv->e_id, 221 "mci_uncache: uncaching %lx (%.100s) from slot %d (%d)", 222 (u_long) mci, mci->mci_host, 223 mcislot - MciCache, doquit); 224 225 mci->mci_deliveries = 0; 226 #if SMTP 227 if (doquit) 228 { 229 message("Closing connection to %s", mci->mci_host); 230 231 mci->mci_flags &= ~MCIF_CACHED; 232 233 /* only uses the envelope to flush the transcript file */ 234 if (mci->mci_state != MCIS_CLOSED) 235 smtpquit(mci->mci_mailer, mci, &BlankEnvelope); 236 # ifdef XLA 237 xla_host_end(mci->mci_host); 238 # endif /* XLA */ 239 } 240 else 241 #endif /* SMTP */ 242 { 243 if (mci->mci_in != NULL) 244 (void) fclose(mci->mci_in); 245 if (mci->mci_out != NULL) 246 (void) fclose(mci->mci_out); 247 mci->mci_in = mci->mci_out = NULL; 248 mci->mci_state = MCIS_CLOSED; 249 mci->mci_exitstat = EX_OK; 250 mci->mci_errno = 0; 251 mci->mci_flags = 0; 252 } 253 } 254 /* 255 ** MCI_FLUSH -- flush the entire cache 256 ** 257 ** Parameters: 258 ** doquit -- if TRUE, send QUIT protocol. 259 ** if FALSE, just close the connection. 260 ** allbut -- but leave this one open. 261 ** 262 ** Returns: 263 ** none. 264 */ 265 266 void 267 mci_flush(doquit, allbut) 268 bool doquit; 269 MCI *allbut; 270 { 271 register int i; 272 273 if (MciCache == NULL) 274 return; 275 276 for (i = 0; i < MaxMciCache; i++) 277 { 278 if (allbut != MciCache[i]) 279 mci_uncache(&MciCache[i], doquit); 280 } 281 } 282 /* 283 ** MCI_GET -- get information about a particular host 284 */ 285 286 MCI * 287 mci_get(host, m) 288 char *host; 289 MAILER *m; 290 { 291 register MCI *mci; 292 register STAB *s; 293 294 #if DAEMON 295 extern SOCKADDR CurHostAddr; 296 297 /* clear CurHostAddr so we don't get a bogus address with this name */ 298 memset(&CurHostAddr, '\0', sizeof CurHostAddr); 299 #endif /* DAEMON */ 300 301 /* clear out any expired connections */ 302 (void) mci_scan(NULL); 303 304 if (m->m_mno < 0) 305 syserr("!negative mno %d (%s)", m->m_mno, m->m_name); 306 307 s = stab(host, ST_MCI + m->m_mno, ST_ENTER); 308 mci = &s->s_mci; 309 310 /* 311 ** We don't need to load the peristent data if we have data 312 ** already loaded in the cache. 313 */ 314 315 if (mci->mci_host == NULL && 316 (mci->mci_host = s->s_name) != NULL && 317 !mci_load_persistent(mci)) 318 { 319 if (tTd(42, 2)) 320 dprintf("mci_get(%s %s): lock failed\n", 321 host, m->m_name); 322 mci->mci_exitstat = EX_TEMPFAIL; 323 mci->mci_state = MCIS_CLOSED; 324 mci->mci_statfile = NULL; 325 return mci; 326 } 327 328 if (tTd(42, 2)) 329 { 330 dprintf("mci_get(%s %s): mci_state=%d, _flags=%lx, _exitstat=%d, _errno=%d\n", 331 host, m->m_name, mci->mci_state, mci->mci_flags, 332 mci->mci_exitstat, mci->mci_errno); 333 } 334 335 #if SMTP 336 if (mci->mci_state == MCIS_OPEN) 337 { 338 /* poke the connection to see if it's still alive */ 339 (void) smtpprobe(mci); 340 341 /* reset the stored state in the event of a timeout */ 342 if (mci->mci_state != MCIS_OPEN) 343 { 344 mci->mci_errno = 0; 345 mci->mci_exitstat = EX_OK; 346 mci->mci_state = MCIS_CLOSED; 347 } 348 # if DAEMON 349 else 350 { 351 /* get peer host address for logging reasons only */ 352 /* (this should really be in the mci struct) */ 353 SOCKADDR_LEN_T socklen = sizeof CurHostAddr; 354 355 (void) getpeername(fileno(mci->mci_in), 356 (struct sockaddr *) &CurHostAddr, &socklen); 357 } 358 # endif /* DAEMON */ 359 } 360 #endif /* SMTP */ 361 if (mci->mci_state == MCIS_CLOSED) 362 { 363 time_t now = curtime(); 364 365 /* if this info is stale, ignore it */ 366 if (now > mci->mci_lastuse + MciInfoTimeout) 367 { 368 mci->mci_lastuse = now; 369 mci->mci_errno = 0; 370 mci->mci_exitstat = EX_OK; 371 } 372 } 373 374 return mci; 375 } 376 /* 377 ** MCI_MATCH -- check connection cache for a particular host 378 */ 379 380 bool 381 mci_match(host, m) 382 char *host; 383 MAILER *m; 384 { 385 register MCI *mci; 386 register STAB *s; 387 388 if (m->m_mno < 0 || m->m_mno > MAXMAILERS) 389 return FALSE; 390 s = stab(host, ST_MCI + m->m_mno, ST_FIND); 391 if (s == NULL) 392 return FALSE; 393 394 mci = &s->s_mci; 395 if (mci->mci_state == MCIS_OPEN) 396 return TRUE; 397 return FALSE; 398 } 399 /* 400 ** MCI_SETSTAT -- set status codes in MCI structure. 401 ** 402 ** Parameters: 403 ** mci -- the MCI structure to set. 404 ** xstat -- the exit status code. 405 ** dstat -- the DSN status code. 406 ** rstat -- the SMTP status code. 407 ** 408 ** Returns: 409 ** none. 410 */ 411 412 void 413 mci_setstat(mci, xstat, dstat, rstat) 414 MCI *mci; 415 int xstat; 416 char *dstat; 417 char *rstat; 418 { 419 /* protocol errors should never be interpreted as sticky */ 420 if (xstat != EX_NOTSTICKY && xstat != EX_PROTOCOL) 421 mci->mci_exitstat = xstat; 422 423 mci->mci_status = dstat; 424 if (mci->mci_rstatus != NULL) 425 sm_free(mci->mci_rstatus); 426 if (rstat != NULL) 427 rstat = newstr(rstat); 428 mci->mci_rstatus = rstat; 429 } 430 /* 431 ** MCI_DUMP -- dump the contents of an MCI structure. 432 ** 433 ** Parameters: 434 ** mci -- the MCI structure to dump. 435 ** 436 ** Returns: 437 ** none. 438 ** 439 ** Side Effects: 440 ** none. 441 */ 442 443 struct mcifbits 444 { 445 int mcif_bit; /* flag bit */ 446 char *mcif_name; /* flag name */ 447 }; 448 static struct mcifbits MciFlags[] = 449 { 450 { MCIF_VALID, "VALID" }, 451 { MCIF_TEMP, "TEMP" }, 452 { MCIF_CACHED, "CACHED" }, 453 { MCIF_ESMTP, "ESMTP" }, 454 { MCIF_EXPN, "EXPN" }, 455 { MCIF_SIZE, "SIZE" }, 456 { MCIF_8BITMIME, "8BITMIME" }, 457 { MCIF_7BIT, "7BIT" }, 458 { MCIF_MULTSTAT, "MULTSTAT" }, 459 { MCIF_INHEADER, "INHEADER" }, 460 { MCIF_CVT8TO7, "CVT8TO7" }, 461 { MCIF_DSN, "DSN" }, 462 { MCIF_8BITOK, "8BITOK" }, 463 { MCIF_CVT7TO8, "CVT7TO8" }, 464 { MCIF_INMIME, "INMIME" }, 465 { 0, NULL } 466 }; 467 468 469 void 470 mci_dump(mci, logit) 471 register MCI *mci; 472 bool logit; 473 { 474 register char *p; 475 char *sep; 476 char buf[4000]; 477 478 sep = logit ? " " : "\n\t"; 479 p = buf; 480 snprintf(p, SPACELEFT(buf, p), "MCI@%lx: ", (u_long) mci); 481 p += strlen(p); 482 if (mci == NULL) 483 { 484 snprintf(p, SPACELEFT(buf, p), "NULL"); 485 goto printit; 486 } 487 snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags); 488 p += strlen(p); 489 if (mci->mci_flags != 0) 490 { 491 struct mcifbits *f; 492 493 *p++ = '<'; 494 for (f = MciFlags; f->mcif_bit != 0; f++) 495 { 496 if (!bitset(f->mcif_bit, mci->mci_flags)) 497 continue; 498 snprintf(p, SPACELEFT(buf, p), "%s,", f->mcif_name); 499 p += strlen(p); 500 } 501 p[-1] = '>'; 502 } 503 snprintf(p, SPACELEFT(buf, p), 504 ",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s", 505 sep, mci->mci_errno, mci->mci_herrno, 506 mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep); 507 p += strlen(p); 508 snprintf(p, SPACELEFT(buf, p), 509 "maxsize=%ld, phase=%s, mailer=%s,%s", 510 mci->mci_maxsize, 511 mci->mci_phase == NULL ? "NULL" : mci->mci_phase, 512 mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name, 513 sep); 514 p += strlen(p); 515 snprintf(p, SPACELEFT(buf, p), 516 "status=%s, rstatus=%s,%s", 517 mci->mci_status == NULL ? "NULL" : mci->mci_status, 518 mci->mci_rstatus == NULL ? "NULL" : mci->mci_rstatus, 519 sep); 520 p += strlen(p); 521 snprintf(p, SPACELEFT(buf, p), 522 "host=%s, lastuse=%s", 523 mci->mci_host == NULL ? "NULL" : mci->mci_host, 524 ctime(&mci->mci_lastuse)); 525 printit: 526 if (logit) 527 sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf); 528 else 529 printf("%s\n", buf); 530 } 531 /* 532 ** MCI_DUMP_ALL -- print the entire MCI cache 533 ** 534 ** Parameters: 535 ** logit -- if set, log the result instead of printing 536 ** to stdout. 537 ** 538 ** Returns: 539 ** none. 540 */ 541 542 void 543 mci_dump_all(logit) 544 bool logit; 545 { 546 register int i; 547 548 if (MciCache == NULL) 549 return; 550 551 for (i = 0; i < MaxMciCache; i++) 552 mci_dump(MciCache[i], logit); 553 } 554 /* 555 ** MCI_LOCK_HOST -- Lock host while sending. 556 ** 557 ** If we are contacting a host, we'll need to 558 ** update the status information in the host status 559 ** file, and if we want to do that, we ought to have 560 ** locked it. This has the (according to some) 561 ** desirable effect of serializing connectivity with 562 ** remote hosts -- i.e.: one connection to a give 563 ** host at a time. 564 ** 565 ** Parameters: 566 ** mci -- containing the host we want to lock. 567 ** 568 ** Returns: 569 ** EX_OK -- got the lock. 570 ** EX_TEMPFAIL -- didn't get the lock. 571 */ 572 573 int 574 mci_lock_host(mci) 575 MCI *mci; 576 { 577 if (mci == NULL) 578 { 579 if (tTd(56, 1)) 580 dprintf("mci_lock_host: NULL mci\n"); 581 return EX_OK; 582 } 583 584 if (!SingleThreadDelivery) 585 return EX_OK; 586 587 return mci_lock_host_statfile(mci); 588 } 589 590 static int 591 mci_lock_host_statfile(mci) 592 MCI *mci; 593 { 594 int save_errno = errno; 595 int retVal = EX_OK; 596 char fname[MAXPATHLEN + 1]; 597 598 if (HostStatDir == NULL || mci->mci_host == NULL) 599 return EX_OK; 600 601 if (tTd(56, 2)) 602 dprintf("mci_lock_host: attempting to lock %s\n", 603 mci->mci_host); 604 605 if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, TRUE) < 0) 606 { 607 /* of course this should never happen */ 608 if (tTd(56, 2)) 609 dprintf("mci_lock_host: Failed to generate host path for %s\n", 610 mci->mci_host); 611 612 retVal = EX_TEMPFAIL; 613 goto cleanup; 614 } 615 616 mci->mci_statfile = safefopen(fname, O_RDWR, FileMode, 617 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT); 618 619 if (mci->mci_statfile == NULL) 620 { 621 syserr("mci_lock_host: cannot create host lock file %s", 622 fname); 623 goto cleanup; 624 } 625 626 if (!lockfile(fileno(mci->mci_statfile), fname, "", LOCK_EX|LOCK_NB)) 627 { 628 if (tTd(56, 2)) 629 dprintf("mci_lock_host: couldn't get lock on %s\n", 630 fname); 631 (void) fclose(mci->mci_statfile); 632 mci->mci_statfile = NULL; 633 retVal = EX_TEMPFAIL; 634 goto cleanup; 635 } 636 637 if (tTd(56, 12) && mci->mci_statfile != NULL) 638 dprintf("mci_lock_host: Sanity check -- lock is good\n"); 639 640 cleanup: 641 errno = save_errno; 642 return retVal; 643 } 644 /* 645 ** MCI_UNLOCK_HOST -- unlock host 646 ** 647 ** Clean up the lock on a host, close the file, let 648 ** someone else use it. 649 ** 650 ** Parameters: 651 ** mci -- us. 652 ** 653 ** Returns: 654 ** nothing. 655 */ 656 657 void 658 mci_unlock_host(mci) 659 MCI *mci; 660 { 661 int save_errno = errno; 662 663 if (mci == NULL) 664 { 665 if (tTd(56, 1)) 666 dprintf("mci_unlock_host: NULL mci\n"); 667 return; 668 } 669 670 if (HostStatDir == NULL || mci->mci_host == NULL) 671 return; 672 673 if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL) 674 { 675 if (tTd(56, 1)) 676 dprintf("mci_unlock_host: stat file already locked\n"); 677 } 678 else 679 { 680 if (tTd(56, 2)) 681 dprintf("mci_unlock_host: store prior to unlock\n"); 682 683 mci_store_persistent(mci); 684 } 685 686 if (mci->mci_statfile != NULL) 687 { 688 (void) fclose(mci->mci_statfile); 689 mci->mci_statfile = NULL; 690 } 691 692 errno = save_errno; 693 } 694 /* 695 ** MCI_LOAD_PERSISTENT -- load persistent host info 696 ** 697 ** Load information about host that is kept 698 ** in common for all running sendmails. 699 ** 700 ** Parameters: 701 ** mci -- the host/connection to load persistent info 702 ** for. 703 ** 704 ** Returns: 705 ** TRUE -- lock was successful 706 ** FALSE -- lock failed 707 */ 708 709 static bool 710 mci_load_persistent(mci) 711 MCI *mci; 712 { 713 int save_errno = errno; 714 bool locked = TRUE; 715 FILE *fp; 716 char fname[MAXPATHLEN + 1]; 717 718 if (mci == NULL) 719 { 720 if (tTd(56, 1)) 721 dprintf("mci_load_persistent: NULL mci\n"); 722 return TRUE; 723 } 724 725 if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL) 726 return TRUE; 727 728 /* Already have the persistent information in memory */ 729 if (SingleThreadDelivery && mci->mci_statfile != NULL) 730 return TRUE; 731 732 if (tTd(56, 1)) 733 dprintf("mci_load_persistent: Attempting to load persistent information for %s\n", 734 mci->mci_host); 735 736 if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, FALSE) < 0) 737 { 738 /* Not much we can do if the file isn't there... */ 739 if (tTd(56, 1)) 740 dprintf("mci_load_persistent: Couldn't generate host path\n"); 741 goto cleanup; 742 } 743 744 fp = safefopen(fname, O_RDONLY, FileMode, 745 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); 746 if (fp == NULL) 747 { 748 /* I can't think of any reason this should ever happen */ 749 if (tTd(56, 1)) 750 dprintf("mci_load_persistent: open(%s): %s\n", 751 fname, errstring(errno)); 752 goto cleanup; 753 } 754 755 FileName = fname; 756 locked = lockfile(fileno(fp), fname, "", LOCK_SH|LOCK_NB); 757 if (locked) 758 { 759 (void) mci_read_persistent(fp, mci); 760 (void) lockfile(fileno(fp), fname, "", LOCK_UN); 761 } 762 FileName = NULL; 763 (void) fclose(fp); 764 765 cleanup: 766 errno = save_errno; 767 return locked; 768 } 769 /* 770 ** MCI_READ_PERSISTENT -- read persistent host status file 771 ** 772 ** Parameters: 773 ** fp -- the file pointer to read. 774 ** mci -- the pointer to fill in. 775 ** 776 ** Returns: 777 ** -1 -- if the file was corrupt. 778 ** 0 -- otherwise. 779 ** 780 ** Warning: 781 ** This code makes the assumption that this data 782 ** will be read in an atomic fashion, and that the data 783 ** was written in an atomic fashion. Any other functioning 784 ** may lead to some form of insanity. This should be 785 ** perfectly safe due to underlying stdio buffering. 786 */ 787 788 static int 789 mci_read_persistent(fp, mci) 790 FILE *fp; 791 register MCI *mci; 792 { 793 int ver; 794 register char *p; 795 int saveLineNumber = LineNumber; 796 char buf[MAXLINE]; 797 798 if (fp == NULL) 799 syserr("mci_read_persistent: NULL fp"); 800 if (mci == NULL) 801 syserr("mci_read_persistent: NULL mci"); 802 if (tTd(56, 93)) 803 { 804 dprintf("mci_read_persistent: fp=%lx, mci=", (u_long) fp); 805 mci_dump(mci, FALSE); 806 } 807 808 mci->mci_status = NULL; 809 if (mci->mci_rstatus != NULL) 810 sm_free(mci->mci_rstatus); 811 mci->mci_rstatus = NULL; 812 813 rewind(fp); 814 ver = -1; 815 LineNumber = 0; 816 while (fgets(buf, sizeof buf, fp) != NULL) 817 { 818 LineNumber++; 819 p = strchr(buf, '\n'); 820 if (p != NULL) 821 *p = '\0'; 822 switch (buf[0]) 823 { 824 case 'V': /* version stamp */ 825 ver = atoi(&buf[1]); 826 if (ver < 0 || ver > 0) 827 syserr("Unknown host status version %d: %d max", 828 ver, 0); 829 break; 830 831 case 'E': /* UNIX error number */ 832 mci->mci_errno = atoi(&buf[1]); 833 break; 834 835 case 'H': /* DNS error number */ 836 mci->mci_herrno = atoi(&buf[1]); 837 break; 838 839 case 'S': /* UNIX exit status */ 840 mci->mci_exitstat = atoi(&buf[1]); 841 break; 842 843 case 'D': /* DSN status */ 844 mci->mci_status = newstr(&buf[1]); 845 break; 846 847 case 'R': /* SMTP status */ 848 mci->mci_rstatus = newstr(&buf[1]); 849 break; 850 851 case 'U': /* last usage time */ 852 mci->mci_lastuse = atol(&buf[1]); 853 break; 854 855 case '.': /* end of file */ 856 return 0; 857 858 default: 859 sm_syslog(LOG_CRIT, NOQID, 860 "%s: line %d: Unknown host status line \"%s\"", 861 FileName == NULL ? mci->mci_host : FileName, 862 LineNumber, buf); 863 LineNumber = saveLineNumber; 864 return -1; 865 } 866 } 867 LineNumber = saveLineNumber; 868 if (ver < 0) 869 return -1; 870 return 0; 871 } 872 /* 873 ** MCI_STORE_PERSISTENT -- Store persistent MCI information 874 ** 875 ** Store information about host that is kept 876 ** in common for all running sendmails. 877 ** 878 ** Parameters: 879 ** mci -- the host/connection to store persistent info for. 880 ** 881 ** Returns: 882 ** none. 883 */ 884 885 void 886 mci_store_persistent(mci) 887 MCI *mci; 888 { 889 int save_errno = errno; 890 891 if (mci == NULL) 892 { 893 if (tTd(56, 1)) 894 dprintf("mci_store_persistent: NULL mci\n"); 895 return; 896 } 897 898 if (HostStatDir == NULL || mci->mci_host == NULL) 899 return; 900 901 if (tTd(56, 1)) 902 dprintf("mci_store_persistent: Storing information for %s\n", 903 mci->mci_host); 904 905 if (mci->mci_statfile == NULL) 906 { 907 if (tTd(56, 1)) 908 dprintf("mci_store_persistent: no statfile\n"); 909 return; 910 } 911 912 rewind(mci->mci_statfile); 913 #if !NOFTRUNCATE 914 (void) ftruncate(fileno(mci->mci_statfile), (off_t) 0); 915 #endif /* !NOFTRUNCATE */ 916 917 fprintf(mci->mci_statfile, "V0\n"); 918 fprintf(mci->mci_statfile, "E%d\n", mci->mci_errno); 919 fprintf(mci->mci_statfile, "H%d\n", mci->mci_herrno); 920 fprintf(mci->mci_statfile, "S%d\n", mci->mci_exitstat); 921 if (mci->mci_status != NULL) 922 fprintf(mci->mci_statfile, "D%.80s\n", 923 denlstring(mci->mci_status, TRUE, FALSE)); 924 if (mci->mci_rstatus != NULL) 925 fprintf(mci->mci_statfile, "R%.80s\n", 926 denlstring(mci->mci_rstatus, TRUE, FALSE)); 927 fprintf(mci->mci_statfile, "U%ld\n", (long)(mci->mci_lastuse)); 928 fprintf(mci->mci_statfile, ".\n"); 929 930 (void) fflush(mci->mci_statfile); 931 932 errno = save_errno; 933 return; 934 } 935 /* 936 ** MCI_TRAVERSE_PERSISTENT -- walk persistent status tree 937 ** 938 ** Recursively find all the mci host files in `pathname'. Default to 939 ** main host status directory if no path is provided. 940 ** Call (*action)(pathname, host) for each file found. 941 ** 942 ** Note: all information is collected in a list before it is processed. 943 ** This may not be the best way to do it, but it seems safest, since 944 ** the file system would be touched while we are attempting to traverse 945 ** the directory tree otherwise (during purges). 946 ** 947 ** Parameters: 948 ** action -- function to call on each node. If returns < 0, 949 ** return immediately. 950 ** pathname -- root of tree. If null, use main host status 951 ** directory. 952 ** 953 ** Returns: 954 ** < 0 -- if any action routine returns a negative value, that 955 ** value is returned. 956 ** 0 -- if we successfully went to completion. 957 ** > 0 -- return status from action() 958 */ 959 960 int 961 mci_traverse_persistent(action, pathname) 962 int (*action)(); 963 char *pathname; 964 { 965 struct stat statbuf; 966 DIR *d; 967 int ret; 968 969 if (pathname == NULL) 970 pathname = HostStatDir; 971 if (pathname == NULL) 972 return -1; 973 974 if (tTd(56, 1)) 975 dprintf("mci_traverse: pathname is %s\n", pathname); 976 977 ret = stat(pathname, &statbuf); 978 if (ret < 0) 979 { 980 if (tTd(56, 2)) 981 dprintf("mci_traverse: Failed to stat %s: %s\n", 982 pathname, errstring(errno)); 983 return ret; 984 } 985 if (S_ISDIR(statbuf.st_mode)) 986 { 987 struct dirent *e; 988 char *newptr; 989 char newpath[MAXPATHLEN + 1]; 990 bool leftone, removedone; 991 992 if ((d = opendir(pathname)) == NULL) 993 { 994 if (tTd(56, 2)) 995 dprintf("mci_traverse: opendir %s: %s\n", 996 pathname, errstring(errno)); 997 return -1; 998 } 999 1000 if (strlen(pathname) >= sizeof newpath - MAXNAMLEN - 3) 1001 { 1002 if (tTd(56, 2)) 1003 dprintf("mci_traverse: path \"%s\" too long", 1004 pathname); 1005 return -1; 1006 } 1007 (void) strlcpy(newpath, pathname, sizeof newpath); 1008 newptr = newpath + strlen(newpath); 1009 *newptr++ = '/'; 1010 1011 /* 1012 ** repeat until no file has been removed 1013 ** this may become ugly when several files "expire" 1014 ** during these loops, but it's better than doing 1015 ** a rewinddir() inside the inner loop 1016 */ 1017 do 1018 { 1019 leftone = removedone = FALSE; 1020 while ((e = readdir(d)) != NULL) 1021 { 1022 if (e->d_name[0] == '.') 1023 continue; 1024 1025 (void) strlcpy(newptr, e->d_name, 1026 sizeof newpath - 1027 (newptr - newpath)); 1028 1029 if (StopRequest) 1030 stop_sendmail(); 1031 ret = mci_traverse_persistent(action, newpath); 1032 if (ret < 0) 1033 break; 1034 if (ret == 1) 1035 leftone = TRUE; 1036 if (!removedone && ret == 0 && 1037 action == mci_purge_persistent) 1038 removedone = TRUE; 1039 } 1040 if (ret < 0) 1041 break; 1042 /* 1043 ** The following appears to be 1044 ** necessary during purges, since 1045 ** we modify the directory structure 1046 */ 1047 if (removedone) 1048 rewinddir(d); 1049 if (tTd(56, 40)) 1050 dprintf("mci_traverse: path %s: ret %d removed %d left %d\n", 1051 pathname, ret, removedone, leftone); 1052 } while (removedone); 1053 1054 /* purge (or whatever) the directory proper */ 1055 if (!leftone) 1056 { 1057 *--newptr = '\0'; 1058 ret = (*action)(newpath, NULL); 1059 } 1060 (void) closedir(d); 1061 } 1062 else if (S_ISREG(statbuf.st_mode)) 1063 { 1064 char *end = pathname + strlen(pathname) - 1; 1065 char *start; 1066 char *scan; 1067 char host[MAXHOSTNAMELEN]; 1068 char *hostptr = host; 1069 1070 /* 1071 ** Reconstruct the host name from the path to the 1072 ** persistent information. 1073 */ 1074 1075 do 1076 { 1077 if (hostptr != host) 1078 *(hostptr++) = '.'; 1079 start = end; 1080 while (*(start - 1) != '/') 1081 start--; 1082 1083 if (*end == '.') 1084 end--; 1085 1086 for (scan = start; scan <= end; scan++) 1087 *(hostptr++) = *scan; 1088 1089 end = start - 2; 1090 } while (*end == '.'); 1091 1092 *hostptr = '\0'; 1093 1094 /* 1095 ** Do something with the file containing the persistent 1096 ** information. 1097 */ 1098 ret = (*action)(pathname, host); 1099 } 1100 1101 return ret; 1102 } 1103 /* 1104 ** MCI_PRINT_PERSISTENT -- print persistent info 1105 ** 1106 ** Dump the persistent information in the file 'pathname' 1107 ** 1108 ** Parameters: 1109 ** pathname -- the pathname to the status file. 1110 ** hostname -- the corresponding host name. 1111 ** 1112 ** Returns: 1113 ** 0 1114 */ 1115 1116 int 1117 mci_print_persistent(pathname, hostname) 1118 char *pathname; 1119 char *hostname; 1120 { 1121 static int initflag = FALSE; 1122 FILE *fp; 1123 int width = Verbose ? 78 : 25; 1124 bool locked; 1125 MCI mcib; 1126 1127 /* skip directories */ 1128 if (hostname == NULL) 1129 return 0; 1130 1131 if (StopRequest) 1132 stop_sendmail(); 1133 1134 if (!initflag) 1135 { 1136 initflag = TRUE; 1137 printf(" -------------- Hostname --------------- How long ago ---------Results---------\n"); 1138 } 1139 1140 fp = safefopen(pathname, O_RDWR, FileMode, 1141 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); 1142 1143 if (fp == NULL) 1144 { 1145 if (tTd(56, 1)) 1146 dprintf("mci_print_persistent: cannot open %s: %s\n", 1147 pathname, errstring(errno)); 1148 return 0; 1149 } 1150 1151 FileName = pathname; 1152 memset(&mcib, '\0', sizeof mcib); 1153 if (mci_read_persistent(fp, &mcib) < 0) 1154 { 1155 syserr("%s: could not read status file", pathname); 1156 (void) fclose(fp); 1157 FileName = NULL; 1158 return 0; 1159 } 1160 1161 locked = !lockfile(fileno(fp), pathname, "", LOCK_EX|LOCK_NB); 1162 (void) fclose(fp); 1163 FileName = NULL; 1164 1165 printf("%c%-39s %12s ", 1166 locked ? '*' : ' ', hostname, 1167 pintvl(curtime() - mcib.mci_lastuse, TRUE)); 1168 if (mcib.mci_rstatus != NULL) 1169 printf("%.*s\n", width, mcib.mci_rstatus); 1170 else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0) 1171 printf("Deferred: %.*s\n", width - 10, errstring(mcib.mci_errno)); 1172 else if (mcib.mci_exitstat != 0) 1173 { 1174 int i = mcib.mci_exitstat - EX__BASE; 1175 extern int N_SysEx; 1176 extern char *SysExMsg[]; 1177 1178 if (i < 0 || i >= N_SysEx) 1179 { 1180 char buf[80]; 1181 1182 snprintf(buf, sizeof buf, "Unknown mailer error %d", 1183 mcib.mci_exitstat); 1184 printf("%.*s\n", width, buf); 1185 } 1186 else 1187 printf("%.*s\n", width, &(SysExMsg[i])[5]); 1188 } 1189 else if (mcib.mci_errno == 0) 1190 printf("OK\n"); 1191 else 1192 printf("OK: %.*s\n", width - 4, errstring(mcib.mci_errno)); 1193 1194 return 0; 1195 } 1196 /* 1197 ** MCI_PURGE_PERSISTENT -- Remove a persistence status file. 1198 ** 1199 ** Parameters: 1200 ** pathname -- path to the status file. 1201 ** hostname -- name of host corresponding to that file. 1202 ** NULL if this is a directory (domain). 1203 ** 1204 ** Returns: 1205 ** 0 -- ok 1206 ** 1 -- file not deleted (too young, incorrect format) 1207 ** < 0 -- some error occurred 1208 */ 1209 1210 int 1211 mci_purge_persistent(pathname, hostname) 1212 char *pathname; 1213 char *hostname; 1214 { 1215 struct stat statbuf; 1216 char *end = pathname + strlen(pathname) - 1; 1217 int ret; 1218 1219 if (tTd(56, 1)) 1220 dprintf("mci_purge_persistent: purging %s\n", pathname); 1221 1222 ret = stat(pathname, &statbuf); 1223 if (ret < 0) 1224 { 1225 if (tTd(56, 2)) 1226 dprintf("mci_purge_persistent: Failed to stat %s: %s\n", 1227 pathname, errstring(errno)); 1228 return ret; 1229 } 1230 if (curtime() - statbuf.st_mtime < MciInfoTimeout) 1231 return 1; 1232 if (hostname != NULL) 1233 { 1234 /* remove the file */ 1235 if (unlink(pathname) < 0) 1236 { 1237 if (tTd(56, 2)) 1238 dprintf("mci_purge_persistent: failed to unlink %s: %s\n", 1239 pathname, errstring(errno)); 1240 } 1241 } 1242 else 1243 { 1244 /* remove the directory */ 1245 if (*end != '.') 1246 return 1; 1247 1248 if (tTd(56, 1)) 1249 dprintf("mci_purge_persistent: dpurge %s\n", pathname); 1250 1251 if (rmdir(pathname) < 0) 1252 { 1253 if (tTd(56, 2)) 1254 dprintf("mci_purge_persistent: rmdir %s: %s\n", 1255 pathname, errstring(errno)); 1256 } 1257 1258 } 1259 1260 return 0; 1261 } 1262 /* 1263 ** MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname 1264 ** 1265 ** Given `host', convert from a.b.c to $QueueDir/.hoststat/c./b./a, 1266 ** putting the result into `path'. if `createflag' is set, intervening 1267 ** directories will be created as needed. 1268 ** 1269 ** Parameters: 1270 ** host -- host name to convert from. 1271 ** path -- place to store result. 1272 ** pathlen -- length of path buffer. 1273 ** createflag -- if set, create intervening directories as 1274 ** needed. 1275 ** 1276 ** Returns: 1277 ** 0 -- success 1278 ** -1 -- failure 1279 */ 1280 1281 static int 1282 mci_generate_persistent_path(host, path, pathlen, createflag) 1283 const char *host; 1284 char *path; 1285 int pathlen; 1286 bool createflag; 1287 { 1288 char *elem, *p, *x, ch; 1289 int ret = 0; 1290 int len; 1291 char t_host[MAXHOSTNAMELEN]; 1292 #if NETINET6 1293 struct in6_addr in6_addr; 1294 #endif /* NETINET6 */ 1295 1296 /* 1297 ** Rationality check the arguments. 1298 */ 1299 1300 if (host == NULL) 1301 { 1302 syserr("mci_generate_persistent_path: null host"); 1303 return -1; 1304 } 1305 if (path == NULL) 1306 { 1307 syserr("mci_generate_persistent_path: null path"); 1308 return -1; 1309 } 1310 1311 if (tTd(56, 80)) 1312 dprintf("mci_generate_persistent_path(%s): ", host); 1313 1314 if (*host == '\0' || *host == '.') 1315 return -1; 1316 1317 /* make certain this is not a bracketed host number */ 1318 if (strlen(host) > sizeof t_host - 1) 1319 return -1; 1320 if (host[0] == '[') 1321 (void) strlcpy(t_host, host + 1, sizeof t_host); 1322 else 1323 (void) strlcpy(t_host, host, sizeof t_host); 1324 1325 /* 1326 ** Delete any trailing dots from the hostname. 1327 ** Leave 'elem' pointing at the \0. 1328 */ 1329 1330 elem = t_host + strlen(t_host); 1331 while (elem > t_host && 1332 (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']'))) 1333 *--elem = '\0'; 1334 1335 #if NETINET || NETINET6 1336 /* check for bogus bracketed address */ 1337 if (host[0] == '[' 1338 # if NETINET6 1339 && inet_pton(AF_INET6, t_host, &in6_addr) != 1 1340 # endif /* NETINET6 */ 1341 # if NETINET 1342 && inet_addr(t_host) == INADDR_NONE 1343 # endif /* NETINET */ 1344 ) 1345 return -1; 1346 #endif /* NETINET || NETINET6 */ 1347 1348 /* check for what will be the final length of the path */ 1349 len = strlen(HostStatDir) + 2; 1350 for (p = (char *) t_host; *p != '\0'; p++) 1351 { 1352 if (*p == '.') 1353 len++; 1354 len++; 1355 if (p[0] == '.' && p[1] == '.') 1356 return -1; 1357 } 1358 if (len > pathlen || len < 1) 1359 return -1; 1360 1361 (void) strlcpy(path, HostStatDir, pathlen); 1362 p = path + strlen(path); 1363 1364 while (elem > t_host) 1365 { 1366 if (!path_is_dir(path, createflag)) 1367 { 1368 ret = -1; 1369 break; 1370 } 1371 elem--; 1372 while (elem >= t_host && *elem != '.') 1373 elem--; 1374 *p++ = '/'; 1375 x = elem + 1; 1376 while ((ch = *x++) != '\0' && ch != '.') 1377 { 1378 if (isascii(ch) && isupper(ch)) 1379 ch = tolower(ch); 1380 if (ch == '/') 1381 ch = ':'; /* / -> : */ 1382 *p++ = ch; 1383 } 1384 if (elem >= t_host) 1385 *p++ = '.'; 1386 *p = '\0'; 1387 } 1388 1389 if (tTd(56, 80)) 1390 { 1391 if (ret < 0) 1392 dprintf("FAILURE %d\n", ret); 1393 else 1394 dprintf("SUCCESS %s\n", path); 1395 } 1396 1397 return ret; 1398 } 1399