root/usr/src/lib/krb5/kadm5/clnt/logger.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * lib/kadm/logger.c
 *
 * Copyright 1995 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 *
 */

/* KADM5 wants non-syslog log files to contain syslog-like entries */
#define VERBOSE_LOGS

/*
 * logger.c     - Handle logging functions for those who want it.
 */
#include "k5-int.h"
#include "adm_proto.h"
#include "com_err.h"
#include <stdio.h>
#include <ctype.h>
#include <ctype.h>
#ifdef  HAVE_SYSLOG_H
#include <syslog.h>
#endif  /* HAVE_SYSLOG_H */
#ifdef  HAVE_STDARG_H
#include <stdarg.h>
#else   /* HAVE_STDARG_H */
#include <varargs.h>
#endif  /* HAVE_STDARG_H */
#include <libintl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define KRB5_KLOG_MAX_ERRMSG_SIZE       2048
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN  256
#endif  /* MAXHOSTNAMELEN */

#define LSPEC_PARSE_ERR_1       1
#define LSPEC_PARSE_ERR_2       2
#define LOG_FILE_ERR            3
#define LOG_DEVICE_ERR          4
#define LOG_UFO_STRING          5
#define LOG_EMERG_STRING        6
#define LOG_ALERT_STRING        7
#define LOG_CRIT_STRING         8
#define LOG_ERR_STRING          9
#define LOG_WARNING_STRING      10
#define LOG_NOTICE_STRING       11
#define LOG_INFO_STRING 12
#define LOG_DEBUG_STRING        13
/* This is to assure that we have at least one match in the syslog stuff */
/*
static const char LSPEC_PARSE_ERR_1[] = "%s: cannot parse <%s>\n";
static const char LSPEC_PARSE_ERR_2[] = "%s: warning - logging entry syntax error\n";
static const char LOG_FILE_ERR[] =      "%s: error writing to %s\n";
static const char LOG_DEVICE_ERR[] =    "%s: error writing to %s device\n";
static const char LOG_UFO_STRING[] =    "???";
static const char LOG_EMERG_STRING[] =  "EMERGENCY";
static const char LOG_ALERT_STRING[] =  "ALERT";
static const char LOG_CRIT_STRING[] =   "CRITICAL";
static const char LOG_ERR_STRING[] =    "Error";
static const char LOG_WARNING_STRING[] =        "Warning";
static const char LOG_NOTICE_STRING[] = "Notice";
static const char LOG_INFO_STRING[] =   "info";
static const char LOG_DEBUG_STRING[] =  "debug";
*/


const char *
krb5_log_error_table(long errorno) {
switch (errorno) {
        case LSPEC_PARSE_ERR_1:
                return(gettext("%s: cannot parse <%s>\n"));
        case LSPEC_PARSE_ERR_2:
                return(gettext("%s: warning - logging entry syntax error\n"));
        case LOG_FILE_ERR:
                return(gettext("%s: error writing to %s\n"));
        case LOG_DEVICE_ERR:
                return(gettext("%s: error writing to %s device\n"));
        case LOG_UFO_STRING:
                return(gettext("???"));
        case LOG_EMERG_STRING:
                return(gettext("EMERGENCY"));
        case LOG_ALERT_STRING:
                return(gettext("ALERT"));
        case LOG_CRIT_STRING:
                return(gettext("CRITICAL"));
        case LOG_ERR_STRING:
                return(gettext("Error"));
        case LOG_WARNING_STRING:
                return(gettext("Warning"));
        case LOG_NOTICE_STRING:
                return(gettext("Notice"));
        case LOG_INFO_STRING:
        case LOG_DEBUG_STRING:
        default:
                return(gettext("info"));
        }
}

/*
 * Output logging.
 *
 * Output logging is now controlled by the configuration file.  We can specify
 * the following syntaxes under the [logging]->entity specification.
 *      FILE<opentype><pathname>
 *      SYSLOG[=<severity>[:<facility>]]
 *      STDERR
 *      CONSOLE
 *      DEVICE=<device-spec>
 *
 * Where:
 *      <opentype> is ":" for open/append, "=" for open/create.
 *      <pathname> is a valid path name.
 *      <severity> is one of: (default = ERR)
 *              EMERG
 *              ALERT
 *              CRIT
 *              ERR
 *              WARNING
 *              NOTICE
 *              INFO
 *              DEBUG
 *      <facility> is one of: (default = AUTH)
 *              KERN
 *              USER
 *              MAIL
 *              DAEMON
 *              AUTH
 *              LPR
 *              NEWS
 *              UUCP
 *              AUDIT
 *              CRON
 *              LOCAL0..LOCAL7
 *      <device-spec> is a valid device specification.
 */
struct log_entry {
    enum log_type { K_LOG_FILE,
                        K_LOG_SYSLOG,
                        K_LOG_STDERR,
                        K_LOG_CONSOLE,
                        K_LOG_DEVICE,
                        K_LOG_NONE } log_type;
    krb5_pointer log_2free;
    union log_union {
        struct log_file {
            FILE        *lf_filep;
            char        *lf_fname;
            char        *lf_fopen_mode; /* "a+" or "w" */
#define K_LOG_DEF_FILE_ROTATE_PERIOD    -1      /* never */
#define K_LOG_DEF_FILE_ROTATE_VERSIONS  0       /* no versions */
            time_t      lf_rotate_period;
            time_t      lf_last_rotated;
            int         lf_rotate_versions;
        } log_file;
        struct log_syslog {
            int         ls_facility;
            int         ls_severity;
        } log_syslog;
        struct log_device {
            FILE        *ld_filep;
            char        *ld_devname;
        } log_device;
    } log_union;
};
#define lfu_filep       log_union.log_file.lf_filep
#define lfu_fname       log_union.log_file.lf_fname
#define lfu_fopen_mode  log_union.log_file.lf_fopen_mode
#define lfu_rotate_period       log_union.log_file.lf_rotate_period
#define lfu_last_rotated        log_union.log_file.lf_last_rotated
#define lfu_rotate_versions     log_union.log_file.lf_rotate_versions
#define lsu_facility    log_union.log_syslog.ls_facility
#define lsu_severity    log_union.log_syslog.ls_severity
#define ldu_filep       log_union.log_device.ld_filep
#define ldu_devname     log_union.log_device.ld_devname

struct log_control {
    struct log_entry    *log_entries;
    int                 log_nentries;
    char                *log_whoami;
    char                *log_hostname;
    krb5_boolean        log_opened;
};

static struct log_control log_control = {
    (struct log_entry *) NULL,
    0,
    (char *) NULL,
    (char *) NULL,
    0
};
static struct log_entry def_log_entry;

/*
 * These macros define any special processing that needs to happen for
 * devices.  For unix, of course, this is hardly anything.
 */
#define DEVICE_OPEN(d, m)       fopen(d, m)
#define CONSOLE_OPEN(m)         fopen("/dev/console", m)
#define DEVICE_PRINT(f, m)      ((fprintf(f, "%s\r\n", m) >= 0) ?       \
                                 (fflush(f), 0) :                       \
                                 -1)
#define DEVICE_CLOSE(d)         fclose(d)


/*
 * klog_rotate() - roate a log file if we have specified rotation
 * parameters in krb5.conf.
 */
static void
klog_rotate(struct log_entry *le)
{
        time_t t;
        int i;
        char *name_buf1;
        char *name_buf2;
        char *old_name;
        char *new_name;
        char *tmp;
        FILE *fp;
        int num_vers;
        mode_t old_umask;


        /*
         * By default we don't rotate.
         */
        if (le->lfu_rotate_period == K_LOG_DEF_FILE_ROTATE_PERIOD)
                return;

        t = time(0);

        if (t >= le->lfu_last_rotated + le->lfu_rotate_period) {
                /*
                 * The N log file versions will be renamed X.N-1 X.N-2, ... X.0.
                 * So the allocate file name buffers that can the version
                 * number extensions.
                 * 32 extra bytes is plenty.
                 */
                name_buf1 = malloc(strlen(le->lfu_fname) + 32);

                if (name_buf1 == NULL)
                        return;

                name_buf2 = malloc(strlen(le->lfu_fname) + 32);

                if (name_buf2 == NULL) {
                        free(name_buf1);
                        return;
                }

                old_name = name_buf1;
                new_name = name_buf2;

                /*
                 * If there N versions, then the first one has file extension
                 * of N-1.
                 */
                (void) sprintf(new_name, "%s.%d", le->lfu_fname,
                        le->lfu_rotate_versions - 1);

                /*
                 * Rename file.N-2 to file.N-1, file.N-3 to file.N-2, ...
                 * file.0 to file.1
                 */
                for (i = le->lfu_rotate_versions - 1; i > 0; i--) {
                        (void) sprintf(old_name, "%s.%d", le->lfu_fname, i - 1);
                        (void) rename(old_name, new_name);

                        /*
                         * swap old name and new name. This way,
                         * on the next iteration, new_name.X
                         * becomes new_name.X-1.
                         */
                        tmp = old_name;
                        old_name = new_name;
                        new_name = tmp;
                }
                old_name = le->lfu_fname;

                (void) rename(old_name, new_name);

                /*
                 * Even though we don't know yet if the fopen()
                 * of the log file will succeed, we mark the log
                 * as rotated. This is so we don't repeatably
                 * rotate file.N-2 to file.N-1 ... etc without
                 * waiting for the rotate period to elapse.
                 */
                le->lfu_last_rotated = t;

                /*
                 * Default log file creation mode should be read-only
                 * by owner(root), but the admin can override with
                 * chmod(1) if desired.
                 */

                old_umask = umask(077);
                fp = fopen(old_name, le->lfu_fopen_mode);

                umask(old_umask);

                if (fp != NULL) {

                        (void) fclose(le->lfu_filep);
                        le->lfu_filep = fp;

                        /*
                         * If the version parameter in krb5.conf was
                         * 0, then we take this to mean that rotating the
                         * log file will cause us to dispose of the
                         * old one, and created a new one. We have just
                         * renamed the old one to file.-1, so remove it.
                         */
                        if (le->lfu_rotate_versions <= 0)
                                (void) unlink(new_name);

                } else {
                        fprintf(stderr,
                gettext("During rotate, couldn't open log file %s: %s\n"),
                                old_name, error_message(errno));
                        /*
                         * Put it back.
                         */
                        (void) rename(new_name, old_name);
                }
                free(name_buf1);
                free(name_buf2);
        }
}

/*
 * klog_com_err_proc()  - Handle com_err(3) messages as specified by the
 *                        profile.
 */
static krb5_context err_context;
static void
klog_com_err_proc(const char *whoami, long code, const char *format, va_list ap)
{
    char        outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];
    int         lindex;
    const char  *actual_format;
#ifdef  HAVE_SYSLOG
    int         log_pri = -1;
#endif  /* HAVE_SYSLOG */
    char        *cp;
    char        *syslogp;

    /* Make the header */
    sprintf(outbuf, "%s: ", whoami);
    /*
     * Squirrel away address after header for syslog since syslog makes
     * a header
     */
    syslogp = &outbuf[strlen(outbuf)];

    /* If reporting an error message, separate it. */
    if (code) {
        const char *emsg;
        outbuf[sizeof(outbuf) - 1] = '\0';

        emsg = krb5_get_error_message (err_context, code);
        strncat(outbuf, emsg, sizeof(outbuf) - 1 - strlen(outbuf));
        strncat(outbuf, " - ", sizeof(outbuf) - 1 - strlen(outbuf));
        krb5_free_error_message(err_context, emsg);
    }
    cp = &outbuf[strlen(outbuf)];

    actual_format = format;
#ifdef  HAVE_SYSLOG
    /*
     * This is an unpleasant hack.  If the first character is less than
     * 8, then we assume that it is a priority.
     *
     * Since it is not guaranteed that there is a direct mapping between
     * syslog priorities (e.g. Ultrix and old BSD), we resort to this
     * intermediate representation.
     */
    if ((((unsigned char) *format) > 0) && (((unsigned char) *format) <= 8)) {
        actual_format = (format + 1);
        switch ((unsigned char) *format) {
#ifdef  LOG_EMERG
        case 1:
            log_pri = LOG_EMERG;
            break;
#endif /* LOG_EMERG */
#ifdef  LOG_ALERT
        case 2:
            log_pri = LOG_ALERT;
            break;
#endif /* LOG_ALERT */
#ifdef  LOG_CRIT
        case 3:
            log_pri = LOG_CRIT;
            break;
#endif /* LOG_CRIT */
        default:
        case 4:
            log_pri = LOG_ERR;
            break;
#ifdef  LOG_WARNING
        case 5:
            log_pri = LOG_WARNING;
            break;
#endif /* LOG_WARNING */
#ifdef  LOG_NOTICE
        case 6:
            log_pri = LOG_NOTICE;
            break;
#endif /* LOG_NOTICE */
#ifdef  LOG_INFO
        case 7:
            log_pri = LOG_INFO;
            break;
#endif /* LOG_INFO */
#ifdef  LOG_DEBUG
        case 8:
            log_pri = LOG_DEBUG;
            break;
#endif /* LOG_DEBUG */
        }
    }
#endif  /* HAVE_SYSLOG */

    /* Now format the actual message */
#if     HAVE_VSNPRINTF
    vsnprintf(cp, sizeof(outbuf) - (cp - outbuf), actual_format, ap);
#elif   HAVE_VSPRINTF
    vsprintf(cp, actual_format, ap);
#else   /* HAVE_VSPRINTF */
    sprintf(cp, actual_format, ((int *) ap)[0], ((int *) ap)[1],
            ((int *) ap)[2], ((int *) ap)[3],
            ((int *) ap)[4], ((int *) ap)[5]);
#endif  /* HAVE_VSPRINTF */

    /*
     * Now that we have the message formatted, perform the output to each
     * logging specification.
     */
    for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
        switch (log_control.log_entries[lindex].log_type) {
        case K_LOG_FILE:

            klog_rotate(&log_control.log_entries[lindex]);
            /*FALLTHRU*/
        case K_LOG_STDERR:
            /*
             * Files/standard error.
             */
            if (fprintf(log_control.log_entries[lindex].lfu_filep, "%s\n",
                        outbuf) < 0) {
                /* Attempt to report error */
                fprintf(stderr, krb5_log_error_table(LOG_FILE_ERR), whoami,
                        log_control.log_entries[lindex].lfu_fname);
            }
            else {
                fflush(log_control.log_entries[lindex].lfu_filep);
            }
            break;
        case K_LOG_CONSOLE:
        case K_LOG_DEVICE:
            /*
             * Devices (may need special handling)
             */
            if (DEVICE_PRINT(log_control.log_entries[lindex].ldu_filep,
                             outbuf) < 0) {
                /* Attempt to report error */
                fprintf(stderr, krb5_log_error_table(LOG_DEVICE_ERR), whoami,
                        log_control.log_entries[lindex].ldu_devname);
            }
            break;
#ifdef  HAVE_SYSLOG
        case K_LOG_SYSLOG:
            /*
             * System log.
             */
            /*
             * If we have specified a priority through our hackery, then
             * use it, otherwise use the default.
             */
            if (log_pri >= 0)
                log_pri |= log_control.log_entries[lindex].lsu_facility;
            else
                log_pri = log_control.log_entries[lindex].lsu_facility |
                    log_control.log_entries[lindex].lsu_severity;

            /* Log the message with our header trimmed off */
            syslog(log_pri, syslogp);
            break;
#endif /* HAVE_SYSLOG */
        default:
            break;
        }
    }
}

/*
 * krb5_klog_init()     - Initialize logging.
 *
 * This routine parses the syntax described above to specify destinations for
 * com_err(3) or krb5_klog_syslog() messages generated by the caller.
 *
 * Parameters:
 *      kcontext        - Kerberos context.
 *      ename           - Entity name as it is to appear in the profile.
 *      whoami          - Entity name as it is to appear in error output.
 *      do_com_err      - Take over com_err(3) processing.
 *
 * Implicit inputs:
 *      stderr          - This is where STDERR output goes.
 *
 * Implicit outputs:
 *      log_nentries    - Number of log entries, both valid and invalid.
 *      log_control     - List of entries (log_nentries long) which contains
 *                        data for klog_com_err_proc() to use to determine
 *                        where/how to send output.
 */
krb5_error_code
krb5_klog_init(krb5_context kcontext, char *ename, char *whoami, krb5_boolean do_com_err)
{
    const char  *logging_profent[3];
    const char  *logging_defent[3];
    char        **logging_specs;
    int         i, ngood;
    char        *cp, *cp2;
    char        savec = '\0';
    int         error;
    int         do_openlog, log_facility;
    FILE        *f;
    mode_t      old_umask;

    /* Initialize */
    do_openlog = 0;
    log_facility = 0;

    err_context = kcontext;

    /*
     * Look up [logging]-><ename> in the profile.  If that doesn't
     * succeed, then look for [logging]->default.
     */
    logging_profent[0] = "logging";
    logging_profent[1] = ename;
    logging_profent[2] = (char *) NULL;
    logging_defent[0] = "logging";
    logging_defent[1] = "default";
    logging_defent[2] = (char *) NULL;
    logging_specs = (char **) NULL;
    ngood = 0;
    log_control.log_nentries = 0;
    if (!profile_get_values(kcontext->profile,
                            logging_profent,
                            &logging_specs) ||
        !profile_get_values(kcontext->profile,
                            logging_defent,
                            &logging_specs)) {
        /*
         * We have a match, so we first count the number of elements
         */
        for (log_control.log_nentries = 0;
             logging_specs[log_control.log_nentries];
             log_control.log_nentries++);

        /*
         * Now allocate our structure.
         */
        log_control.log_entries = (struct log_entry *)
            malloc(log_control.log_nentries * sizeof(struct log_entry));
        if (log_control.log_entries) {
            /*
             * Scan through the list.
             */
            for (i=0; i<log_control.log_nentries; i++) {
                log_control.log_entries[i].log_type = K_LOG_NONE;
                log_control.log_entries[i].log_2free = logging_specs[i];
                /*
                 * The format is:
                 *      <whitespace><data><whitespace>
                 * so, trim off the leading and trailing whitespace here.
                 */
                for (cp = logging_specs[i]; isspace((int) *cp); cp++);
                for (cp2 = &logging_specs[i][strlen(logging_specs[i])-1];
                     isspace((int) *cp2); cp2--);
                cp2++;
                *cp2 = '\0';
                /*
                 * Is this a file?
                 */
                if (!strncasecmp(cp, "FILE", 4)) {
                    /*
                     * Check for append/overwrite, then open the file.
                     */
                    if (cp[4] == ':' || cp[4] == '=') {
                        log_control.log_entries[i].lfu_fopen_mode =
                                (cp[4] == ':') ? "a+F" : "wF";
                        old_umask = umask(077);
                        f = fopen(&cp[5],
                                log_control.log_entries[i].lfu_fopen_mode);
                        umask(old_umask);
                        if (f) {
                            char rotate_kw[128];

                            log_control.log_entries[i].lfu_filep = f;
                            log_control.log_entries[i].log_type = K_LOG_FILE;
                            log_control.log_entries[i].lfu_fname = &cp[5];
                            log_control.log_entries[i].lfu_rotate_period =
                                K_LOG_DEF_FILE_ROTATE_PERIOD;
                            log_control.log_entries[i].lfu_rotate_versions =
                                K_LOG_DEF_FILE_ROTATE_VERSIONS;
                            log_control.log_entries[i].lfu_last_rotated =
                                time(0);

                        /*
                         * Now parse for ename_"rotate" = {
                         *      period = XXX
                         *      versions = 10
                         * }
                         */
                            if (strlen(ename) + strlen("_rotate") <
                                sizeof (rotate_kw)) {

                                    char *time;
                                    krb5_deltat dt;
                                    int vers;

                                    strcpy(rotate_kw, ename);
                                    strcat(rotate_kw, "_rotate");

                                    if (!profile_get_string(kcontext->profile,
                                        "logging", rotate_kw, "period",
                                        NULL, &time)) {

                                        if (time != NULL) {
                                            if (!krb5_string_to_deltat(time,
                                                &dt)) {
                        log_control.log_entries[i].lfu_rotate_period =
                                                        (time_t) dt;
                                            }
                                            free(time);
                                        }
                                    }

                                    if (!profile_get_integer(
                                        kcontext->profile, "logging",
                                        rotate_kw, "versions",
                                        K_LOG_DEF_FILE_ROTATE_VERSIONS,
                                        &vers)) {
                        log_control.log_entries[i].lfu_rotate_versions = vers;
                                    }

                           }
                        } else {
                            fprintf(stderr, gettext("Couldn't open log file %s: %s\n"),
                                    &cp[5], error_message(errno));
                            continue;
                        }
                    }
                }
#ifdef  HAVE_SYSLOG
                /*
                 * Is this a syslog?
                 */
                else if (!strncasecmp(cp, "SYSLOG", 6)) {
                    error = 0;
                    log_control.log_entries[i].lsu_facility = LOG_AUTH;
                    log_control.log_entries[i].lsu_severity = LOG_ERR;
                    /*
                     * Is there a severify specified?
                     */
                    if (cp[6] == ':') {
                        /*
                         * Find the end of the severity.
                         */
                        cp2 = strchr(&cp[7], ':');
                        if (cp2) {
                            savec = *cp2;
                            *cp2 = '\0';
                            cp2++;
                        }

                        /*
                         * Match a severity.
                         */
                        if (!strcasecmp(&cp[7], "ERR")) {
                            log_control.log_entries[i].lsu_severity = LOG_ERR;
                        }
#ifdef  LOG_EMERG
                        else if (!strcasecmp(&cp[7], "EMERG")) {
                            log_control.log_entries[i].lsu_severity =
                                LOG_EMERG;
                        }
#endif  /* LOG_EMERG */
#ifdef  LOG_ALERT
                        else if (!strcasecmp(&cp[7], "ALERT")) {
                            log_control.log_entries[i].lsu_severity =
                                LOG_ALERT;
                        }
#endif  /* LOG_ALERT */
#ifdef  LOG_CRIT
                        else if (!strcasecmp(&cp[7], "CRIT")) {
                            log_control.log_entries[i].lsu_severity = LOG_CRIT;
                        }
#endif  /* LOG_CRIT */
#ifdef  LOG_WARNING
                        else if (!strcasecmp(&cp[7], "WARNING")) {
                            log_control.log_entries[i].lsu_severity =
                                LOG_WARNING;
                        }
#endif  /* LOG_WARNING */
#ifdef  LOG_NOTICE
                        else if (!strcasecmp(&cp[7], "NOTICE")) {
                            log_control.log_entries[i].lsu_severity =
                                LOG_NOTICE;
                        }
#endif  /* LOG_NOTICE */
#ifdef  LOG_INFO
                        else if (!strcasecmp(&cp[7], "INFO")) {
                            log_control.log_entries[i].lsu_severity = LOG_INFO;
                        }
#endif  /* LOG_INFO */
#ifdef  LOG_DEBUG
                        else if (!strcasecmp(&cp[7], "DEBUG")) {
                            log_control.log_entries[i].lsu_severity =
                                LOG_DEBUG;
                        }
#endif  /* LOG_DEBUG */
                        else
                            error = 1;

                        /*
                         * If there is a facility present, then parse that.
                         */
                        if (cp2) {
                            if (!strcasecmp(cp2, "AUTH")) {
                                log_control.log_entries[i].lsu_facility = LOG_AUTH;
                            }
                            else if (!strcasecmp(cp2, "KERN")) {
                                log_control.log_entries[i].lsu_facility = LOG_KERN;
                            }
                            else if (!strcasecmp(cp2, "USER")) {
                                log_control.log_entries[i].lsu_facility = LOG_USER;
                            }
                            else if (!strcasecmp(cp2, "MAIL")) {
                                log_control.log_entries[i].lsu_facility = LOG_MAIL;
                            }
                            else if (!strcasecmp(cp2, "DAEMON")) {
                                log_control.log_entries[i].lsu_facility = LOG_DAEMON;
                            }
                            else if (!strcasecmp(cp2, "LPR")) {
                                log_control.log_entries[i].lsu_facility = LOG_LPR;
                            }
                            else if (!strcasecmp(cp2, "NEWS")) {
                                log_control.log_entries[i].lsu_facility = LOG_NEWS;
                            }
                            else if (!strcasecmp(cp2, "UUCP")) {
                                log_control.log_entries[i].lsu_facility = LOG_UUCP;
                            }
                            else if (!strcasecmp(cp2, "CRON")) {
                                log_control.log_entries[i].lsu_facility = LOG_CRON;
                            }
                            else if (!strcasecmp(cp2, "AUDIT")) {
                                log_control.log_entries[i].lsu_facility = LOG_AUDIT;
                            }
                            else if (!strcasecmp(cp2, "LOCAL0")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL0;
                            }
                            else if (!strcasecmp(cp2, "LOCAL1")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL1;
                            }
                            else if (!strcasecmp(cp2, "LOCAL2")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL2;
                            }
                            else if (!strcasecmp(cp2, "LOCAL3")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL3;
                            }
                            else if (!strcasecmp(cp2, "LOCAL4")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL4;
                            }
                            else if (!strcasecmp(cp2, "LOCAL5")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL5;
                            }
                            else if (!strcasecmp(cp2, "LOCAL6")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL6;
                            }
                            else if (!strcasecmp(cp2, "LOCAL7")) {
                                log_control.log_entries[i].lsu_facility = LOG_LOCAL7;
                            }
                            cp2--;
                            *cp2 = savec;
                        }
                    }
                    if (!error) {
                        log_control.log_entries[i].log_type = K_LOG_SYSLOG;
                        do_openlog = 1;
                        log_facility = log_control.log_entries[i].lsu_facility;
                    }
                }
#endif  /* HAVE_SYSLOG */
                /*
                 * Is this a standard error specification?
                 */
                else if (!strcasecmp(cp, "STDERR")) {
                    log_control.log_entries[i].lfu_filep =
                        fdopen(fileno(stderr), "a+F");
                    if (log_control.log_entries[i].lfu_filep) {
                        log_control.log_entries[i].log_type = K_LOG_STDERR;
                        log_control.log_entries[i].lfu_fname =
                            "standard error";
                    }
                }
                /*
                 * Is this a specification of the console?
                 */
                else if (!strcasecmp(cp, "CONSOLE")) {
                    log_control.log_entries[i].ldu_filep =
                        CONSOLE_OPEN("a+F");
                    if (log_control.log_entries[i].ldu_filep) {
                        log_control.log_entries[i].log_type = K_LOG_CONSOLE;
                        log_control.log_entries[i].ldu_devname = "console";
                    }
                }
                /*
                 * Is this a specification of a device?
                 */
                else if (!strncasecmp(cp, "DEVICE", 6)) {
                    /*
                     * We handle devices very similarly to files.
                     */
                    if (cp[6] == '=') {
                        log_control.log_entries[i].ldu_filep =
                            DEVICE_OPEN(&cp[7], "wF");
                        if (log_control.log_entries[i].ldu_filep) {
                            log_control.log_entries[i].log_type = K_LOG_DEVICE;
                            log_control.log_entries[i].ldu_devname = &cp[7];
                        }
                    }
                }
                /*
                 * See if we successfully parsed this specification.
                 */
                if (log_control.log_entries[i].log_type == K_LOG_NONE) {
                    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_1), whoami, cp);
                    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_2), whoami);
                }
                else
                    ngood++;
            }
        }
        /*
         * If we didn't find anything, then free our lists.
         */
        if (ngood == 0) {
            for (i=0; i<log_control.log_nentries; i++)
                free(logging_specs[i]);
        }
        free(logging_specs);
    }
    /*
     * If we didn't find anything, go for the default which is to log to
     * the system log.
     */
    if (ngood == 0) {
        if (log_control.log_entries)
            free(log_control.log_entries);
        log_control.log_entries = &def_log_entry;
        log_control.log_entries->log_type = K_LOG_SYSLOG;
        log_control.log_entries->log_2free = (krb5_pointer) NULL;
        log_facility = log_control.log_entries->lsu_facility = LOG_AUTH;
        log_control.log_entries->lsu_severity = LOG_ERR;
        do_openlog = 1;
        log_control.log_nentries = 1;
    }
    if (log_control.log_nentries) {
        log_control.log_whoami = (char *) malloc(strlen(whoami)+1);
        if (log_control.log_whoami)
            strcpy(log_control.log_whoami, whoami);

        log_control.log_hostname = (char *) malloc(MAXHOSTNAMELEN + 1);
        if (log_control.log_hostname) {
            gethostname(log_control.log_hostname, MAXHOSTNAMELEN);
            log_control.log_hostname[MAXHOSTNAMELEN] = '\0';
        }
#ifdef  HAVE_OPENLOG
        if (do_openlog) {
            openlog(whoami, LOG_NDELAY|LOG_PID, log_facility);
            log_control.log_opened = 1;
        }
#endif /* HAVE_OPENLOG */
        if (do_com_err)
            (void) set_com_err_hook(klog_com_err_proc);
    }
    return((log_control.log_nentries) ? 0 : ENOENT);
}

/*
 * krb5_klog_close()    - Close the logging context and free all data.
 */
void
krb5_klog_close(krb5_context kcontext)
{
    int lindex;
    (void) reset_com_err_hook();
    for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
        switch (log_control.log_entries[lindex].log_type) {
        case K_LOG_FILE:
        case K_LOG_STDERR:
            /*
             * Files/standard error.
             */
            fclose(log_control.log_entries[lindex].lfu_filep);
            break;
        case K_LOG_CONSOLE:
        case K_LOG_DEVICE:
            /*
             * Devices (may need special handling)
             */
            DEVICE_CLOSE(log_control.log_entries[lindex].ldu_filep);
            break;
#ifdef  HAVE_SYSLOG
        case K_LOG_SYSLOG:
            /*
             * System log.
             */
            break;
#endif  /* HAVE_SYSLOG */
        default:
            break;
        }
        if (log_control.log_entries[lindex].log_2free)
            free(log_control.log_entries[lindex].log_2free);
    }
    if (log_control.log_entries != &def_log_entry)
        free(log_control.log_entries);
    log_control.log_entries = (struct log_entry *) NULL;
    log_control.log_nentries = 0;
    if (log_control.log_whoami)
        free(log_control.log_whoami);
    log_control.log_whoami = (char *) NULL;
    if (log_control.log_hostname)
        free(log_control.log_hostname);
    log_control.log_hostname = (char *) NULL;
#ifdef  HAVE_CLOSELOG
    if (log_control.log_opened)
        closelog();
#endif  /* HAVE_CLOSELOG */
}

/*
 * severity2string()    - Convert a severity to a string.
 */
static const char *
severity2string(int severity)
{
    int s;
    const char *ss;

    s = severity & LOG_PRIMASK;
    ss = krb5_log_error_table(LOG_UFO_STRING);
    switch (s) {
#ifdef  LOG_EMERG
    case LOG_EMERG:
        ss = krb5_log_error_table(LOG_EMERG_STRING);
        break;
#endif  /* LOG_EMERG */
#ifdef  LOG_ALERT
    case LOG_ALERT:
        ss = krb5_log_error_table(LOG_ALERT_STRING);
        break;
#endif  /* LOG_ALERT */
#ifdef  LOG_CRIT
    case LOG_CRIT:
        ss = krb5_log_error_table(LOG_CRIT_STRING);
        break;
#endif  /* LOG_CRIT */
    case LOG_ERR:
        ss = krb5_log_error_table(LOG_ERR_STRING);
        break;
#ifdef  LOG_WARNING
    case LOG_WARNING:
        ss = krb5_log_error_table(LOG_WARNING_STRING);
        break;
#endif  /* LOG_WARNING */
#ifdef  LOG_NOTICE
    case LOG_NOTICE:
        ss = krb5_log_error_table(LOG_NOTICE_STRING);
        break;
#endif  /* LOG_NOTICE */
#ifdef  LOG_INFO
    case LOG_INFO:
        ss = krb5_log_error_table(LOG_INFO_STRING);
        break;
#endif  /* LOG_INFO */
#ifdef  LOG_DEBUG
    case LOG_DEBUG:
        ss = krb5_log_error_table(LOG_DEBUG_STRING);
        break;
#endif  /* LOG_DEBUG */
    }
    return((char *) ss);
}

/*
 * krb5_klog_syslog()   - Simulate the calling sequence of syslog(3), while
 *                        also performing the logging redirection as specified
 *                        by krb5_klog_init().
 */
static int
klog_vsyslog(int priority, const char *format, va_list arglist)
{
    char        outbuf[KRB5_KLOG_MAX_ERRMSG_SIZE];
    int         lindex;
    char        *syslogp;
    char        *cp;
    time_t      now;
#ifdef  HAVE_STRFTIME
    size_t      soff;
#endif  /* HAVE_STRFTIME */

    /*
     * Format a syslog-esque message of the format:
     *
     * (verbose form)
     *          <date> <hostname> <id>[<pid>](<priority>): <message>
     *
     * (short form)
     *          <date> <message>
     */
    cp = outbuf;
    (void) time(&now);
#ifdef  HAVE_STRFTIME
    /*
     * Format the date: mon dd hh:mm:ss
     */
    soff = strftime(outbuf, sizeof(outbuf), "%b %d %H:%M:%S", localtime(&now));
    if (soff > 0)
        cp += soff;
    else
        return(-1);
#else   /* HAVE_STRFTIME */
    /*
     * Format the date:
     * We ASSUME here that the output of ctime is of the format:
     *  dow mon dd hh:mm:ss tzs yyyy\n
     *  012345678901234567890123456789
     */
    strncpy(outbuf, ctime(&now) + 4, 15);
    cp += 15;
#endif  /* HAVE_STRFTIME */
#ifdef VERBOSE_LOGS
    sprintf(cp, " %s %s[%ld](%s): ",
            log_control.log_hostname, log_control.log_whoami, (long) getpid(),
            severity2string(priority));
#else
    sprintf(cp, " ");
#endif
    syslogp = &outbuf[strlen(outbuf)];

    /* Now format the actual message */
#ifdef  HAVE_VSNPRINTF
    vsnprintf(syslogp, sizeof(outbuf) - (syslogp - outbuf), format, arglist);
#elif   HAVE_VSPRINTF
    vsprintf(syslogp, format, arglist);
#else   /* HAVE_VSPRINTF */
    sprintf(syslogp, format, ((int *) arglist)[0], ((int *) arglist)[1],
            ((int *) arglist)[2], ((int *) arglist)[3],
            ((int *) arglist)[4], ((int *) arglist)[5]);
#endif  /* HAVE_VSPRINTF */

    /*
     * If the user did not use krb5_klog_init() instead of dropping
     * the request on the floor, syslog it - if it exists
     */
#ifdef HAVE_SYSLOG
    if (log_control.log_nentries == 0) {
        /* Log the message with our header trimmed off */
        syslog(priority, "%s", syslogp);
    }
#endif

    /*
     * Now that we have the message formatted, perform the output to each
     * logging specification.
     */
    for (lindex = 0; lindex < log_control.log_nentries; lindex++) {
        switch (log_control.log_entries[lindex].log_type) {
        case K_LOG_FILE:

            klog_rotate(&log_control.log_entries[lindex]);
            /*FALLTHRU*/
        case K_LOG_STDERR:
            /*
             * Files/standard error.
             */
            if (fprintf(log_control.log_entries[lindex].lfu_filep, "%s\n",
                        outbuf) < 0) {
                /* Attempt to report error */
                fprintf(stderr, krb5_log_error_table(LOG_FILE_ERR),
                        log_control.log_whoami,
                        log_control.log_entries[lindex].lfu_fname);
            }
            else {
                fflush(log_control.log_entries[lindex].lfu_filep);
            }
            break;
        case K_LOG_CONSOLE:
        case K_LOG_DEVICE:
            /*
             * Devices (may need special handling)
             */
            if (DEVICE_PRINT(log_control.log_entries[lindex].ldu_filep,
                             outbuf) < 0) {
                /* Attempt to report error */
                fprintf(stderr, krb5_log_error_table(LOG_DEVICE_ERR),
                        log_control.log_whoami,
                        log_control.log_entries[lindex].ldu_devname);
            }
            break;
#ifdef  HAVE_SYSLOG
        case K_LOG_SYSLOG:
            /*
             * System log.
             */

            /* Log the message with our header trimmed off */
            syslog(priority, "%s", syslogp);
            break;
#endif /* HAVE_SYSLOG */
        default:
            break;
        }
    }
    return(0);
}

int
krb5_klog_syslog(int priority, const char *format, ...)
{
    int         retval;
    va_list     pvar;

    va_start(pvar, format);
    retval = klog_vsyslog(priority, format, pvar);
    va_end(pvar);
    return(retval);
}