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