mci.c revision 71348
1/* 2 * Copyright (c) 1998-2000 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 15static char id[] = "@(#)$Id: mci.c,v 8.133.10.7 2000/12/12 00:39:34 ca Exp $"; 16#endif /* ! lint */ 17 18/* $FreeBSD: head/contrib/sendmail/src/mci.c 71348 2001-01-21 22:21:43Z gshapiro $ */ 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 29static int mci_generate_persistent_path __P((const char *, char *, 30 int, bool)); 31static bool mci_load_persistent __P((MCI *)); 32static void mci_uncache __P((MCI **, bool)); 33static int mci_lock_host_statfile __P((MCI *)); 34static 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 67static 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 81void 82mci_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 132MCI ** 133mci_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 200static void 201mci_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 268void 269mci_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 288MCI * 289mci_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 382bool 383mci_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 414void 415mci_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 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 445struct mcifbits 446{ 447 int mcif_bit; /* flag bit */ 448 char *mcif_name; /* flag name */ 449}; 450static 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 471void 472mci_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: ", 483 sizeof(void *) == sizeof(u_long) ? 484 (u_long)(void *)mci : (u_long)(u_int)(void *)mci); 485 p += strlen(p); 486 if (mci == NULL) 487 { 488 snprintf(p, SPACELEFT(buf, p), "NULL"); 489 goto printit; 490 } 491 snprintf(p, SPACELEFT(buf, p), "flags=%lx", mci->mci_flags); 492 p += strlen(p); 493 if (mci->mci_flags != 0) 494 { 495 struct mcifbits *f; 496 497 *p++ = '<'; 498 for (f = MciFlags; f->mcif_bit != 0; f++) 499 { 500 if (!bitset(f->mcif_bit, mci->mci_flags)) 501 continue; 502 snprintf(p, SPACELEFT(buf, p), "%s,", f->mcif_name); 503 p += strlen(p); 504 } 505 p[-1] = '>'; 506 } 507 snprintf(p, SPACELEFT(buf, p), 508 ",%serrno=%d, herrno=%d, exitstat=%d, state=%d, pid=%d,%s", 509 sep, mci->mci_errno, mci->mci_herrno, 510 mci->mci_exitstat, mci->mci_state, (int) mci->mci_pid, sep); 511 p += strlen(p); 512 snprintf(p, SPACELEFT(buf, p), 513 "maxsize=%ld, phase=%s, mailer=%s,%s", 514 mci->mci_maxsize, 515 mci->mci_phase == NULL ? "NULL" : mci->mci_phase, 516 mci->mci_mailer == NULL ? "NULL" : mci->mci_mailer->m_name, 517 sep); 518 p += strlen(p); 519 snprintf(p, SPACELEFT(buf, p), 520 "status=%s, rstatus=%s,%s", 521 mci->mci_status == NULL ? "NULL" : mci->mci_status, 522 mci->mci_rstatus == NULL ? "NULL" : mci->mci_rstatus, 523 sep); 524 p += strlen(p); 525 snprintf(p, SPACELEFT(buf, p), 526 "host=%s, lastuse=%s", 527 mci->mci_host == NULL ? "NULL" : mci->mci_host, 528 ctime(&mci->mci_lastuse)); 529printit: 530 if (logit) 531 sm_syslog(LOG_DEBUG, CurEnv->e_id, "%.1000s", buf); 532 else 533 printf("%s\n", buf); 534} 535/* 536** MCI_DUMP_ALL -- print the entire MCI cache 537** 538** Parameters: 539** logit -- if set, log the result instead of printing 540** to stdout. 541** 542** Returns: 543** none. 544*/ 545 546void 547mci_dump_all(logit) 548 bool logit; 549{ 550 register int i; 551 552 if (MciCache == NULL) 553 return; 554 555 for (i = 0; i < MaxMciCache; i++) 556 mci_dump(MciCache[i], logit); 557} 558/* 559** MCI_LOCK_HOST -- Lock host while sending. 560** 561** If we are contacting a host, we'll need to 562** update the status information in the host status 563** file, and if we want to do that, we ought to have 564** locked it. This has the (according to some) 565** desirable effect of serializing connectivity with 566** remote hosts -- i.e.: one connection to a give 567** host at a time. 568** 569** Parameters: 570** mci -- containing the host we want to lock. 571** 572** Returns: 573** EX_OK -- got the lock. 574** EX_TEMPFAIL -- didn't get the lock. 575*/ 576 577int 578mci_lock_host(mci) 579 MCI *mci; 580{ 581 if (mci == NULL) 582 { 583 if (tTd(56, 1)) 584 dprintf("mci_lock_host: NULL mci\n"); 585 return EX_OK; 586 } 587 588 if (!SingleThreadDelivery) 589 return EX_OK; 590 591 return mci_lock_host_statfile(mci); 592} 593 594static int 595mci_lock_host_statfile(mci) 596 MCI *mci; 597{ 598 int save_errno = errno; 599 int retVal = EX_OK; 600 char fname[MAXPATHLEN + 1]; 601 602 if (HostStatDir == NULL || mci->mci_host == NULL) 603 return EX_OK; 604 605 if (tTd(56, 2)) 606 dprintf("mci_lock_host: attempting to lock %s\n", 607 mci->mci_host); 608 609 if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, TRUE) < 0) 610 { 611 /* of course this should never happen */ 612 if (tTd(56, 2)) 613 dprintf("mci_lock_host: Failed to generate host path for %s\n", 614 mci->mci_host); 615 616 retVal = EX_TEMPFAIL; 617 goto cleanup; 618 } 619 620 mci->mci_statfile = safefopen(fname, O_RDWR, FileMode, 621 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH|SFF_CREAT); 622 623 if (mci->mci_statfile == NULL) 624 { 625 syserr("mci_lock_host: cannot create host lock file %s", 626 fname); 627 goto cleanup; 628 } 629 630 if (!lockfile(fileno(mci->mci_statfile), fname, "", LOCK_EX|LOCK_NB)) 631 { 632 if (tTd(56, 2)) 633 dprintf("mci_lock_host: couldn't get lock on %s\n", 634 fname); 635 (void) fclose(mci->mci_statfile); 636 mci->mci_statfile = NULL; 637 retVal = EX_TEMPFAIL; 638 goto cleanup; 639 } 640 641 if (tTd(56, 12) && mci->mci_statfile != NULL) 642 dprintf("mci_lock_host: Sanity check -- lock is good\n"); 643 644cleanup: 645 errno = save_errno; 646 return retVal; 647} 648/* 649** MCI_UNLOCK_HOST -- unlock host 650** 651** Clean up the lock on a host, close the file, let 652** someone else use it. 653** 654** Parameters: 655** mci -- us. 656** 657** Returns: 658** nothing. 659*/ 660 661void 662mci_unlock_host(mci) 663 MCI *mci; 664{ 665 int save_errno = errno; 666 667 if (mci == NULL) 668 { 669 if (tTd(56, 1)) 670 dprintf("mci_unlock_host: NULL mci\n"); 671 return; 672 } 673 674 if (HostStatDir == NULL || mci->mci_host == NULL) 675 return; 676 677 if (!SingleThreadDelivery && mci_lock_host_statfile(mci) == EX_TEMPFAIL) 678 { 679 if (tTd(56, 1)) 680 dprintf("mci_unlock_host: stat file already locked\n"); 681 } 682 else 683 { 684 if (tTd(56, 2)) 685 dprintf("mci_unlock_host: store prior to unlock\n"); 686 687 mci_store_persistent(mci); 688 } 689 690 if (mci->mci_statfile != NULL) 691 { 692 (void) fclose(mci->mci_statfile); 693 mci->mci_statfile = NULL; 694 } 695 696 errno = save_errno; 697} 698/* 699** MCI_LOAD_PERSISTENT -- load persistent host info 700** 701** Load information about host that is kept 702** in common for all running sendmails. 703** 704** Parameters: 705** mci -- the host/connection to load persistent info 706** for. 707** 708** Returns: 709** TRUE -- lock was successful 710** FALSE -- lock failed 711*/ 712 713static bool 714mci_load_persistent(mci) 715 MCI *mci; 716{ 717 int save_errno = errno; 718 bool locked = TRUE; 719 FILE *fp; 720 char fname[MAXPATHLEN + 1]; 721 722 if (mci == NULL) 723 { 724 if (tTd(56, 1)) 725 dprintf("mci_load_persistent: NULL mci\n"); 726 return TRUE; 727 } 728 729 if (IgnoreHostStatus || HostStatDir == NULL || mci->mci_host == NULL) 730 return TRUE; 731 732 /* Already have the persistent information in memory */ 733 if (SingleThreadDelivery && mci->mci_statfile != NULL) 734 return TRUE; 735 736 if (tTd(56, 1)) 737 dprintf("mci_load_persistent: Attempting to load persistent information for %s\n", 738 mci->mci_host); 739 740 if (mci_generate_persistent_path(mci->mci_host, fname, sizeof fname, FALSE) < 0) 741 { 742 /* Not much we can do if the file isn't there... */ 743 if (tTd(56, 1)) 744 dprintf("mci_load_persistent: Couldn't generate host path\n"); 745 goto cleanup; 746 } 747 748 fp = safefopen(fname, O_RDONLY, FileMode, 749 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); 750 if (fp == NULL) 751 { 752 /* I can't think of any reason this should ever happen */ 753 if (tTd(56, 1)) 754 dprintf("mci_load_persistent: open(%s): %s\n", 755 fname, errstring(errno)); 756 goto cleanup; 757 } 758 759 FileName = fname; 760 locked = lockfile(fileno(fp), fname, "", LOCK_SH|LOCK_NB); 761 if (locked) 762 { 763 (void) mci_read_persistent(fp, mci); 764 (void) lockfile(fileno(fp), fname, "", LOCK_UN); 765 } 766 FileName = NULL; 767 (void) fclose(fp); 768 769cleanup: 770 errno = save_errno; 771 return locked; 772} 773/* 774** MCI_READ_PERSISTENT -- read persistent host status file 775** 776** Parameters: 777** fp -- the file pointer to read. 778** mci -- the pointer to fill in. 779** 780** Returns: 781** -1 -- if the file was corrupt. 782** 0 -- otherwise. 783** 784** Warning: 785** This code makes the assumption that this data 786** will be read in an atomic fashion, and that the data 787** was written in an atomic fashion. Any other functioning 788** may lead to some form of insanity. This should be 789** perfectly safe due to underlying stdio buffering. 790*/ 791 792static int 793mci_read_persistent(fp, mci) 794 FILE *fp; 795 register MCI *mci; 796{ 797 int ver; 798 register char *p; 799 int saveLineNumber = LineNumber; 800 char buf[MAXLINE]; 801 802 if (fp == NULL) 803 syserr("mci_read_persistent: NULL fp"); 804 if (mci == NULL) 805 syserr("mci_read_persistent: NULL mci"); 806 if (tTd(56, 93)) 807 { 808 dprintf("mci_read_persistent: fp=%lx, mci=", (u_long) fp); 809 mci_dump(mci, FALSE); 810 } 811 812 mci->mci_status = NULL; 813 if (mci->mci_rstatus != NULL) 814 free(mci->mci_rstatus); 815 mci->mci_rstatus = NULL; 816 817 rewind(fp); 818 ver = -1; 819 LineNumber = 0; 820 while (fgets(buf, sizeof buf, fp) != NULL) 821 { 822 LineNumber++; 823 p = strchr(buf, '\n'); 824 if (p != NULL) 825 *p = '\0'; 826 switch (buf[0]) 827 { 828 case 'V': /* version stamp */ 829 ver = atoi(&buf[1]); 830 if (ver < 0 || ver > 0) 831 syserr("Unknown host status version %d: %d max", 832 ver, 0); 833 break; 834 835 case 'E': /* UNIX error number */ 836 mci->mci_errno = atoi(&buf[1]); 837 break; 838 839 case 'H': /* DNS error number */ 840 mci->mci_herrno = atoi(&buf[1]); 841 break; 842 843 case 'S': /* UNIX exit status */ 844 mci->mci_exitstat = atoi(&buf[1]); 845 break; 846 847 case 'D': /* DSN status */ 848 mci->mci_status = newstr(&buf[1]); 849 break; 850 851 case 'R': /* SMTP status */ 852 mci->mci_rstatus = newstr(&buf[1]); 853 break; 854 855 case 'U': /* last usage time */ 856 mci->mci_lastuse = atol(&buf[1]); 857 break; 858 859 case '.': /* end of file */ 860 return 0; 861 862 default: 863 sm_syslog(LOG_CRIT, NOQID, 864 "%s: line %d: Unknown host status line \"%s\"", 865 FileName == NULL ? mci->mci_host : FileName, 866 LineNumber, buf); 867 LineNumber = saveLineNumber; 868 return -1; 869 } 870 } 871 LineNumber = saveLineNumber; 872 if (ver < 0) 873 return -1; 874 return 0; 875} 876/* 877** MCI_STORE_PERSISTENT -- Store persistent MCI information 878** 879** Store information about host that is kept 880** in common for all running sendmails. 881** 882** Parameters: 883** mci -- the host/connection to store persistent info for. 884** 885** Returns: 886** none. 887*/ 888 889void 890mci_store_persistent(mci) 891 MCI *mci; 892{ 893 int save_errno = errno; 894 895 if (mci == NULL) 896 { 897 if (tTd(56, 1)) 898 dprintf("mci_store_persistent: NULL mci\n"); 899 return; 900 } 901 902 if (HostStatDir == NULL || mci->mci_host == NULL) 903 return; 904 905 if (tTd(56, 1)) 906 dprintf("mci_store_persistent: Storing information for %s\n", 907 mci->mci_host); 908 909 if (mci->mci_statfile == NULL) 910 { 911 if (tTd(56, 1)) 912 dprintf("mci_store_persistent: no statfile\n"); 913 return; 914 } 915 916 rewind(mci->mci_statfile); 917#if !NOFTRUNCATE 918 (void) ftruncate(fileno(mci->mci_statfile), (off_t) 0); 919#endif /* !NOFTRUNCATE */ 920 921 fprintf(mci->mci_statfile, "V0\n"); 922 fprintf(mci->mci_statfile, "E%d\n", mci->mci_errno); 923 fprintf(mci->mci_statfile, "H%d\n", mci->mci_herrno); 924 fprintf(mci->mci_statfile, "S%d\n", mci->mci_exitstat); 925 if (mci->mci_status != NULL) 926 fprintf(mci->mci_statfile, "D%.80s\n", 927 denlstring(mci->mci_status, TRUE, FALSE)); 928 if (mci->mci_rstatus != NULL) 929 fprintf(mci->mci_statfile, "R%.80s\n", 930 denlstring(mci->mci_rstatus, TRUE, FALSE)); 931 fprintf(mci->mci_statfile, "U%ld\n", (long)(mci->mci_lastuse)); 932 fprintf(mci->mci_statfile, ".\n"); 933 934 (void) fflush(mci->mci_statfile); 935 936 errno = save_errno; 937 return; 938} 939/* 940** MCI_TRAVERSE_PERSISTENT -- walk persistent status tree 941** 942** Recursively find all the mci host files in `pathname'. Default to 943** main host status directory if no path is provided. 944** Call (*action)(pathname, host) for each file found. 945** 946** Note: all information is collected in a list before it is processed. 947** This may not be the best way to do it, but it seems safest, since 948** the file system would be touched while we are attempting to traverse 949** the directory tree otherwise (during purges). 950** 951** Parameters: 952** action -- function to call on each node. If returns < 0, 953** return immediately. 954** pathname -- root of tree. If null, use main host status 955** directory. 956** 957** Returns: 958** < 0 -- if any action routine returns a negative value, that 959** value is returned. 960** 0 -- if we successfully went to completion. 961** > 0 -- return status from action() 962*/ 963 964int 965mci_traverse_persistent(action, pathname) 966 int (*action)(); 967 char *pathname; 968{ 969 struct stat statbuf; 970 DIR *d; 971 int ret; 972 973 if (pathname == NULL) 974 pathname = HostStatDir; 975 if (pathname == NULL) 976 return -1; 977 978 if (tTd(56, 1)) 979 dprintf("mci_traverse: pathname is %s\n", pathname); 980 981 ret = stat(pathname, &statbuf); 982 if (ret < 0) 983 { 984 if (tTd(56, 2)) 985 dprintf("mci_traverse: Failed to stat %s: %s\n", 986 pathname, errstring(errno)); 987 return ret; 988 } 989 if (S_ISDIR(statbuf.st_mode)) 990 { 991 struct dirent *e; 992 char *newptr; 993 char newpath[MAXPATHLEN + 1]; 994 bool leftone, removedone; 995 996 if ((d = opendir(pathname)) == NULL) 997 { 998 if (tTd(56, 2)) 999 dprintf("mci_traverse: opendir %s: %s\n", 1000 pathname, errstring(errno)); 1001 return -1; 1002 } 1003 1004 if (strlen(pathname) >= sizeof newpath - MAXNAMLEN - 3) 1005 { 1006 if (tTd(56, 2)) 1007 dprintf("mci_traverse: path \"%s\" too long", 1008 pathname); 1009 return -1; 1010 } 1011 (void) strlcpy(newpath, pathname, sizeof newpath); 1012 newptr = newpath + strlen(newpath); 1013 *newptr++ = '/'; 1014 1015 /* 1016 ** repeat until no file has been removed 1017 ** this may become ugly when several files "expire" 1018 ** during these loops, but it's better than doing 1019 ** a rewinddir() inside the inner loop 1020 */ 1021 do 1022 { 1023 leftone = removedone = FALSE; 1024 while ((e = readdir(d)) != NULL) 1025 { 1026 if (e->d_name[0] == '.') 1027 continue; 1028 1029 (void) strlcpy(newptr, e->d_name, 1030 sizeof newpath - 1031 (newptr - newpath)); 1032 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 1118int 1119mci_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 (!initflag) 1134 { 1135 initflag = TRUE; 1136 printf(" -------------- Hostname --------------- How long ago ---------Results---------\n"); 1137 } 1138 1139 fp = safefopen(pathname, O_RDWR, FileMode, 1140 SFF_NOLOCK|SFF_NOLINK|SFF_OPENASROOT|SFF_REGONLY|SFF_SAFEDIRPATH); 1141 1142 if (fp == NULL) 1143 { 1144 if (tTd(56, 1)) 1145 dprintf("mci_print_persistent: cannot open %s: %s\n", 1146 pathname, errstring(errno)); 1147 return 0; 1148 } 1149 1150 FileName = pathname; 1151 memset(&mcib, '\0', sizeof mcib); 1152 if (mci_read_persistent(fp, &mcib) < 0) 1153 { 1154 syserr("%s: could not read status file", pathname); 1155 (void) fclose(fp); 1156 FileName = NULL; 1157 return 0; 1158 } 1159 1160 locked = !lockfile(fileno(fp), pathname, "", LOCK_EX|LOCK_NB); 1161 (void) fclose(fp); 1162 FileName = NULL; 1163 1164 printf("%c%-39s %12s ", 1165 locked ? '*' : ' ', hostname, 1166 pintvl(curtime() - mcib.mci_lastuse, TRUE)); 1167 if (mcib.mci_rstatus != NULL) 1168 printf("%.*s\n", width, mcib.mci_rstatus); 1169 else if (mcib.mci_exitstat == EX_TEMPFAIL && mcib.mci_errno != 0) 1170 printf("Deferred: %.*s\n", width - 10, errstring(mcib.mci_errno)); 1171 else if (mcib.mci_exitstat != 0) 1172 { 1173 int i = mcib.mci_exitstat - EX__BASE; 1174 extern int N_SysEx; 1175 extern char *SysExMsg[]; 1176 1177 if (i < 0 || i >= N_SysEx) 1178 { 1179 char buf[80]; 1180 1181 snprintf(buf, sizeof buf, "Unknown mailer error %d", 1182 mcib.mci_exitstat); 1183 printf("%.*s\n", width, buf); 1184 } 1185 else 1186 printf("%.*s\n", width, &(SysExMsg[i])[5]); 1187 } 1188 else if (mcib.mci_errno == 0) 1189 printf("OK\n"); 1190 else 1191 printf("OK: %.*s\n", width - 4, errstring(mcib.mci_errno)); 1192 1193 return 0; 1194} 1195/* 1196** MCI_PURGE_PERSISTENT -- Remove a persistence status file. 1197** 1198** Parameters: 1199** pathname -- path to the status file. 1200** hostname -- name of host corresponding to that file. 1201** NULL if this is a directory (domain). 1202** 1203** Returns: 1204** 0 -- ok 1205** 1 -- file not deleted (too young, incorrect format) 1206** < 0 -- some error occurred 1207*/ 1208 1209int 1210mci_purge_persistent(pathname, hostname) 1211 char *pathname; 1212 char *hostname; 1213{ 1214 struct stat statbuf; 1215 char *end = pathname + strlen(pathname) - 1; 1216 int ret; 1217 1218 if (tTd(56, 1)) 1219 dprintf("mci_purge_persistent: purging %s\n", pathname); 1220 1221 ret = stat(pathname, &statbuf); 1222 if (ret < 0) 1223 { 1224 if (tTd(56, 2)) 1225 dprintf("mci_purge_persistent: Failed to stat %s: %s\n", 1226 pathname, errstring(errno)); 1227 return ret; 1228 } 1229 if (curtime() - statbuf.st_mtime < MciInfoTimeout) 1230 return 1; 1231 if (hostname != NULL) 1232 { 1233 /* remove the file */ 1234 if (unlink(pathname) < 0) 1235 { 1236 if (tTd(56, 2)) 1237 dprintf("mci_purge_persistent: failed to unlink %s: %s\n", 1238 pathname, errstring(errno)); 1239 } 1240 } 1241 else 1242 { 1243 /* remove the directory */ 1244 if (*end != '.') 1245 return 1; 1246 1247 if (tTd(56, 1)) 1248 dprintf("mci_purge_persistent: dpurge %s\n", pathname); 1249 1250 if (rmdir(pathname) < 0) 1251 { 1252 if (tTd(56, 2)) 1253 dprintf("mci_purge_persistent: rmdir %s: %s\n", 1254 pathname, errstring(errno)); 1255 } 1256 1257 } 1258 1259 return 0; 1260} 1261/* 1262** MCI_GENERATE_PERSISTENT_PATH -- generate path from hostname 1263** 1264** Given `host', convert from a.b.c to $QueueDir/.hoststat/c./b./a, 1265** putting the result into `path'. if `createflag' is set, intervening 1266** directories will be created as needed. 1267** 1268** Parameters: 1269** host -- host name to convert from. 1270** path -- place to store result. 1271** pathlen -- length of path buffer. 1272** createflag -- if set, create intervening directories as 1273** needed. 1274** 1275** Returns: 1276** 0 -- success 1277** -1 -- failure 1278*/ 1279 1280static int 1281mci_generate_persistent_path(host, path, pathlen, createflag) 1282 const char *host; 1283 char *path; 1284 int pathlen; 1285 bool createflag; 1286{ 1287 char *elem, *p, *x, ch; 1288 int ret = 0; 1289 int len; 1290 char t_host[MAXHOSTNAMELEN]; 1291#if NETINET6 1292 struct in6_addr in6_addr; 1293#endif /* NETINET6 */ 1294 1295 /* 1296 ** Rationality check the arguments. 1297 */ 1298 1299 if (host == NULL) 1300 { 1301 syserr("mci_generate_persistent_path: null host"); 1302 return -1; 1303 } 1304 if (path == NULL) 1305 { 1306 syserr("mci_generate_persistent_path: null path"); 1307 return -1; 1308 } 1309 1310 if (tTd(56, 80)) 1311 dprintf("mci_generate_persistent_path(%s): ", host); 1312 1313 if (*host == '\0' || *host == '.') 1314 return -1; 1315 1316 /* make certain this is not a bracketed host number */ 1317 if (strlen(host) > sizeof t_host - 1) 1318 return -1; 1319 if (host[0] == '[') 1320 (void) strlcpy(t_host, host + 1, sizeof t_host); 1321 else 1322 (void) strlcpy(t_host, host, sizeof t_host); 1323 1324 /* 1325 ** Delete any trailing dots from the hostname. 1326 ** Leave 'elem' pointing at the \0. 1327 */ 1328 1329 elem = t_host + strlen(t_host); 1330 while (elem > t_host && 1331 (elem[-1] == '.' || (host[0] == '[' && elem[-1] == ']'))) 1332 *--elem = '\0'; 1333 1334#if NETINET || NETINET6 1335 /* check for bogus bracketed address */ 1336 if (host[0] == '[' 1337# if NETINET6 1338 && inet_pton(AF_INET6, t_host, &in6_addr) != 1 1339# endif /* NETINET6 */ 1340# if NETINET 1341 && inet_addr(t_host) == INADDR_NONE 1342# endif /* NETINET */ 1343 ) 1344 return -1; 1345#endif /* NETINET || NETINET6 */ 1346 1347 /* check for what will be the final length of the path */ 1348 len = strlen(HostStatDir) + 2; 1349 for (p = (char *) t_host; *p != '\0'; p++) 1350 { 1351 if (*p == '.') 1352 len++; 1353 len++; 1354 if (p[0] == '.' && p[1] == '.') 1355 return -1; 1356 } 1357 if (len > pathlen || len < 1) 1358 return -1; 1359 1360 (void) strlcpy(path, HostStatDir, pathlen); 1361 p = path + strlen(path); 1362 1363 while (elem > t_host) 1364 { 1365 if (!path_is_dir(path, createflag)) 1366 { 1367 ret = -1; 1368 break; 1369 } 1370 elem--; 1371 while (elem >= t_host && *elem != '.') 1372 elem--; 1373 *p++ = '/'; 1374 x = elem + 1; 1375 while ((ch = *x++) != '\0' && ch != '.') 1376 { 1377 if (isascii(ch) && isupper(ch)) 1378 ch = tolower(ch); 1379 if (ch == '/') 1380 ch = ':'; /* / -> : */ 1381 *p++ = ch; 1382 } 1383 if (elem >= t_host) 1384 *p++ = '.'; 1385 *p = '\0'; 1386 } 1387 1388 if (tTd(56, 80)) 1389 { 1390 if (ret < 0) 1391 dprintf("FAILURE %d\n", ret); 1392 else 1393 dprintf("SUCCESS %s\n", path); 1394 } 1395 1396 return ret; 1397} 1398