diff options
Diffstat (limited to 'src/pmdas/sendmail/sendmail.c')
-rw-r--r-- | src/pmdas/sendmail/sendmail.c | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/src/pmdas/sendmail/sendmail.c b/src/pmdas/sendmail/sendmail.c new file mode 100644 index 0000000..7b2b542 --- /dev/null +++ b/src/pmdas/sendmail/sendmail.c @@ -0,0 +1,524 @@ +/* + * 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 <ctype.h> +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "domain.h" +#include <sys/stat.h> + +/* + * Sendmail PMDA + * + * This PMDA uses the statistics file that sendmail optionally maintains + * -- see "OS<file>" or "O StatusFile=<file>" 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); +} |