/* * Sendmail PMDA * * Copyright (c) 1995-2000,2003 Silicon Graphics, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include #include "pmapi.h" #include "impl.h" #include "pmda.h" #include "domain.h" #include /* * Sendmail PMDA * * This PMDA uses the statistics file that sendmail optionally maintains * -- see "OS" or "O StatusFile=" in sendmail.cf and sendmail(1) * * This file (defaults to /var/sendmail.st) must be created before sendmail * will update any statistics. */ /* * list of instances */ static pmdaIndom indomtab[] = { #define MAILER_INDOM 0 { MAILER_INDOM, 0, NULL }, }; static char *statsfile = "/var/sendmail.st"; static char *username; static int nmailer; static void *ptr; static struct stat laststatbuf; static time_t *start_date; static __uint32_t *msgs_from; static __uint32_t *kbytes_from; static __uint32_t *msgs_to; static __uint32_t *kbytes_to; static pmdaMetric metrictab[] = { /* start_date */ { NULL, { PMDA_PMID(0,0), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,0,0,0,0) }, }, /* permailer.msgs_from */ { NULL, { PMDA_PMID(1,0), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* permailer.bytes_from */ { NULL, { PMDA_PMID(1,1), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, /* permailer.msgs_to */ { NULL, { PMDA_PMID(1,2), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* permailer.bytes_to */ { NULL, { PMDA_PMID(1,3), PM_TYPE_U32, MAILER_INDOM, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, /* total.msgs_from */ { NULL, { PMDA_PMID(2,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* total.bytes_from */ { NULL, { PMDA_PMID(2,1), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, /* total.msgs_to */ { NULL, { PMDA_PMID(2,2), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* total.bytes_to */ { NULL, { PMDA_PMID(2,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER, PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) }, }, }; static void map_stats(void) { struct stat statbuf; static int fd; static int notified = 0; #define MAPSTATS_NULL 0x01 #define MAPSTATS_NOTV2STRUCT 0x02 #define MAPSTATS_MAPFAIL 0x04 /* From mailstats.h in sendmail(1) 8.x... */ struct smstat_s { #define MAXMAILERS 25 #define STAT_VERSION 2 #define STAT_MAGIC 0x1B1DE int stat_magic; /* magic number */ int stat_version; /* stat file version */ time_t stat_itime; /* file initialization time */ short stat_size; /* size of this structure */ long stat_nf[MAXMAILERS]; /* # msgs from each mailer */ long stat_bf[MAXMAILERS]; /* kbytes from each mailer */ long stat_nt[MAXMAILERS]; /* # msgs to each mailer */ long stat_bt[MAXMAILERS]; /* kbytes to each mailer */ long stat_nr[MAXMAILERS]; /* # rejects by each mailer */ long stat_nd[MAXMAILERS]; /* # discards by each mailer */ } *smstat; #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: Entering:\n", pmProgname); fprintf(stderr, "%s: map_stats: Check: ptr = " PRINTF_P_PFX "%p\n", pmProgname, ptr); fprintf(stderr, "%s: map_stats: Check: statsfile = " PRINTF_P_PFX "%p\n", pmProgname, statsfile); if (statsfile != NULL) fprintf(stderr, "%s: map_stats: = %s\n", pmProgname, statsfile); } #endif if (statsfile == NULL || stat(statsfile, &statbuf) < 0) { /* if sendmail not collecting stats this is expected */ if (ptr != NULL) { /* must have gone away */ __pmMemoryUnmap(ptr, laststatbuf.st_size); close(fd); ptr = NULL; notified &= ~MAPSTATS_NOTV2STRUCT; #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: (Maybe) stat() < 0; pmunmap() called\n", pmProgname); } #endif } return; } #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: Check: statbuf.st_ino = %lu\n", pmProgname, (unsigned long)statbuf.st_ino); fprintf(stderr, "%s: map_stats: Check: statbuf.st_dev = %lu\n", pmProgname, (unsigned long)statbuf.st_dev); fprintf(stderr, "%s: map_stats: Check: laststatbuf.st_ino = %lu\n", pmProgname, (unsigned long)laststatbuf.st_ino); fprintf(stderr, "%s: map_stats: Check: laststatbuf.st_dev = %lu\n", pmProgname, (unsigned long)laststatbuf.st_dev); } #endif if (statbuf.st_ino != laststatbuf.st_ino || statbuf.st_dev != laststatbuf.st_dev || ptr == NULL) { /* * Not the same as the file we saw last time, or statsfile is * not mapped into memory (because it was zero length). * * The file can change due to rotation or restarting sendmail... * note the times (st_atim, st_mtim and st_ctim) are all expected * to change as sendmail updates the file, hence we must use dev * and ino. * * ino is guaranteed to change for different instances of the * sendmail stats file, since a mmap()'d file is never closed * until after it's munmap()'d. */ if (ptr != NULL) { __pmMemoryUnmap(ptr, laststatbuf.st_size); close(fd); ptr = NULL; notified &= ~MAPSTATS_NOTV2STRUCT; #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: statbuf.st_[dev|ido] changed; pmunmap() called\n", pmProgname); } #endif } if ((fd = open(statsfile, O_RDONLY)) < 0) { __pmNotifyErr(LOG_WARNING, "%s: map_stats: cannot open(\"%s\",...): %s", pmProgname, statsfile, osstrerror()); return; } ptr = __pmMemoryMap(fd, statbuf.st_size, 0); if (ptr == NULL) { if (!(notified & MAPSTATS_MAPFAIL)) { __pmNotifyErr(LOG_ERR, "%s: map_stats: memmap of %s failed: %s", pmProgname, statsfile, osstrerror()); } close(fd); ptr = NULL; notified |= MAPSTATS_MAPFAIL; return; } laststatbuf = statbuf; /* struct assignment */ notified &= ~(MAPSTATS_NULL | MAPSTATS_MAPFAIL); #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: mmap() called, succeeded\n", pmProgname); } #endif /* Check for a statistics file from sendmail(1) 8.x: */ smstat = (struct smstat_s *)ptr; if (smstat->stat_magic != STAT_MAGIC || smstat->stat_version != STAT_VERSION) { if (! (notified & MAPSTATS_NOTV2STRUCT)) { __pmNotifyErr(LOG_WARNING, "%s: map_stats: cannot find magic number in file %s; assuming version 1 format", pmProgname, statsfile); #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) { fprintf(stderr, "%s: map_stats: smstat_s contents:\n", pmProgname); fprintf(stderr, "%s: map_stats: Version 2 format:\n", pmProgname); fprintf(stderr, "%s: map_stats: Check: stat_magic = 0x%x\n", pmProgname, smstat->stat_magic); fprintf(stderr, "%s: map_stats: Check: stat_version = 0x%x\n", pmProgname, smstat->stat_version); fprintf(stderr, "%s: map_stats: Check: stat_itime = %s", pmProgname, ctime(&(smstat->stat_itime))); fprintf(stderr, "%s: map_stats: Check: stat_size = %d\n", pmProgname, smstat->stat_size); /* We're being difficult here... using smstat_s the wrong way! */ fprintf(stderr, "%s: map_stats: Version 1 format:\n", pmProgname); fprintf(stderr, "%s: map_stats: Check: stat_itime = %s", pmProgname, ctime((time_t *)&(smstat->stat_magic))); fprintf(stderr, "%s: map_stats: Check: stat_size = %d\n", pmProgname, *((short *)&(smstat->stat_version))); } #endif notified |= MAPSTATS_NOTV2STRUCT; } /* Could be older version of stats file... here is the original code that dealt with that case */ /* * format of [older version] sendmail stats file: * word[0] time_t file first created * word[1] N/A * word[2] .. word[K+2] msgs_from mailers 0 .. K * word[K+3] .. word[2*K+3] kbytes_from mailers 0 .. K * word[2*K+3] .. word[3*K+4] msgs_to mailers 0 .. K * word[3*K+4] .. word[4*K+5] kbytes_to mailers 0 .. K */ nmailer = (statbuf.st_size - sizeof(__int32_t) - sizeof(__int32_t)) / (4 * sizeof(__uint32_t)); msgs_from = &((__uint32_t *)ptr)[2]; kbytes_from = &msgs_from[nmailer]; msgs_to = &kbytes_from[nmailer]; kbytes_to = &msgs_to[nmailer]; start_date = (time_t *)ptr; } else { /* Assign pointers to point to parts of the v2 struct */ nmailer = ((char *)smstat->stat_bf - (char *)smstat->stat_nf) / sizeof(long); msgs_from = (__uint32_t *)&(smstat->stat_nf); kbytes_from = (__uint32_t *)&(smstat->stat_bf); msgs_to = (__uint32_t *)&(smstat->stat_nt); kbytes_to = (__uint32_t *)&(smstat->stat_bt); start_date = &(smstat->stat_itime); } } } /* * logic here is similar to that used by mailstats(1) */ static void do_sendmail_cf(void) { FILE *fp; char buf[MAXPATHLEN+20]; char *bp; int i; int lineno = 0; if ((fp = fopen("/etc/sendmail.cf", "r")) == NULL) { if ((fp = fopen("/etc/mail/sendmail.cf", "r")) == NULL) { /* this is pretty serious! */ nmailer = 0; statsfile = NULL; #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) fprintf(stderr, "Warning: cannot find sendmail.cf, so no stats!\n"); #endif return; } } nmailer = 3; indomtab[MAILER_INDOM].it_set = (pmdaInstid *)malloc(nmailer * sizeof(pmdaInstid)); indomtab[MAILER_INDOM].it_set[0].i_inst = 0; indomtab[MAILER_INDOM].it_set[0].i_name = "prog"; indomtab[MAILER_INDOM].it_set[1].i_inst = 1; indomtab[MAILER_INDOM].it_set[1].i_name = "*file*"; indomtab[MAILER_INDOM].it_set[2].i_inst = 2; indomtab[MAILER_INDOM].it_set[2].i_name = "*include*"; while (fgets(buf, sizeof(buf), fp) != NULL) { lineno++; bp = buf; if (*bp == 'M') { /* mailer definition */ bp++; while (*bp != ',' && !isspace((int)*bp) && *bp != '\0') bp++; *bp = '\0'; for (i = 0; i < nmailer; i++) { if (strcmp(&buf[1], indomtab[MAILER_INDOM].it_set[i].i_name) == 0) break; } if (i == nmailer) { indomtab[MAILER_INDOM].it_set = (pmdaInstid *)realloc(indomtab[MAILER_INDOM].it_set, (nmailer+1) * sizeof(pmdaInstid)); indomtab[MAILER_INDOM].it_set[nmailer].i_name = strdup(&buf[1]); indomtab[MAILER_INDOM].it_set[nmailer].i_inst = nmailer; #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) fprintf(stderr, "sendmail.cf[%d]: mailer \"%s\" inst=%d\n", lineno, &buf[1], nmailer); #endif nmailer++; } } else if (*bp == 'O') { char *tp; if (strncasecmp(++bp, " StatusFile", 11) == 0 && !isalnum((int)bp[11])) { bp = strchr(bp, '='); if (bp == NULL) continue; while (isspace((int)*++bp)) continue; } else if (*bp == 'S') bp++; else continue; tp = bp++; while (*bp && !isspace((int)*bp) && *bp != '#') bp++; *bp = '\0'; statsfile = strdup(tp); #ifdef PCP_DEBUG if (pmDebug & DBG_TRACE_APPL0) fprintf(stderr, "sendmail.cf[%d]: statsfile \"%s\"\n", lineno, tp); #endif } } fclose(fp); indomtab[MAILER_INDOM].it_numinst = nmailer; } /* * callback provided to pmdaFetch */ static int sendmail_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom) { __pmID_int *idp = (__pmID_int *)&(mdesc->m_desc.pmid); if (ptr == NULL) return 0; if (idp->cluster == 0) { if (idp->item == 0) { /* sendmail.start_date */ atom->cp = ctime(start_date); atom->cp[24] = '\0'; /* no newline */ return 1; } } else if (idp->cluster == 1) { if (inst >= nmailer) return 0; if (msgs_from[inst] == 0 && msgs_to[inst] == 0) { return 0; } switch (idp->item) { case 0: /* sendmail.permailer.msgs_from */ atom->ul = msgs_from[inst]; break; case 1: /* sendmail.permailer.bytes_from */ atom->ul = kbytes_from[inst]; break; case 2: /* sendmail.permailer.msgs_to */ atom->ul = msgs_to[inst]; break; case 3: /* sendmail.permailer.bytes_to */ atom->ul = kbytes_to[inst]; break; default: return PM_ERR_PMID; } return 1; } else if (idp->cluster == 2) { int i; atom->ul = 0; switch (idp->item) { case 0: /* sendmail.total.msgs_from */ for (i = 0; i < nmailer; i++) atom->ul += msgs_from[i]; break; case 1: /* sendmail.total.bytes_from */ for (i = 0; i < nmailer; i++) atom->ul += kbytes_from[i]; break; case 2: /* sendmail.total.msgs_to */ for (i = 0; i < nmailer; i++) atom->ul += msgs_to[i]; break; case 3: /* sendmail.total.bytes_to */ for (i = 0; i < nmailer; i++) atom->ul += kbytes_to[i]; break; default: return PM_ERR_PMID; } return 1; } return PM_ERR_PMID; } static int sendmail_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda) { map_stats(); return pmdaFetch(numpmid, pmidlist, resp, pmda); } /* * Initialise the agent */ void __PMDA_INIT_CALL sendmail_init(pmdaInterface *dp) { if (dp->status != 0) return; if (username) __pmSetProcessIdentity(username); do_sendmail_cf(); map_stats(); dp->version.two.fetch = sendmail_fetch; pmdaSetFetchCallBack(dp, sendmail_fetchCallBack); pmdaInit(dp, indomtab, sizeof(indomtab)/sizeof(indomtab[0]), metrictab, sizeof(metrictab)/sizeof(metrictab[0])); } pmLongOptions longopts[] = { PMDA_OPTIONS_HEADER("Options"), PMOPT_DEBUG, PMDAOPT_DOMAIN, PMDAOPT_LOGFILE, PMDAOPT_USERNAME, PMOPT_HELP, PMDA_OPTIONS_END }; pmdaOptions opts = { .short_options = "D:d:l:U:?", .long_options = longopts, }; /* * Set up the agent if running as a daemon. */ int main(int argc, char **argv) { int sep = __pmPathSeparator(); pmdaInterface dispatch; char mypath[MAXPATHLEN]; __pmSetProgname(argv[0]); __pmGetUsername(&username); snprintf(mypath, sizeof(mypath), "%s%c" "sendmail" "%c" "help", pmGetConfig("PCP_PMDAS_DIR"), sep, sep); pmdaDaemon(&dispatch, PMDA_INTERFACE_3, pmProgname, SENDMAIL, "sendmail.log", mypath); pmdaGetOptions(argc, argv, &opts, &dispatch); if (opts.errors) { pmdaUsageMessage(&opts); exit(1); } if (opts.username) username = opts.username; pmdaOpenLog(&dispatch); sendmail_init(&dispatch); pmdaConnect(&dispatch); pmdaMain(&dispatch); exit(0); }