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