/*
 * logging.c - generic logging for snmp-agent
 * * Contributed by Ragnar Kjørstad, ucd@ragnark.vestdata.no 1999-06-26 
 */
/* Portions of this file are subject to the following copyright(s).  See
 * the Net-SNMP's COPYING file for more details and other copyrights
 * that may apply:
 */
/*
 * Portions of this file are copyrighted by:
 * Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 *
 * Portions of this file are copyrighted by:
 * Copyright (c) 2016 VMware, Inc. All rights reserved.
 * Use is subject to license terms specified in the COPYING file
 * distributed with the Net-SNMP package.
 */
/** @defgroup snmp_logging generic logging for net-snmp 
 *  @ingroup library
 * 
 *  @{
 */
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <stdio.h>
#if HAVE_MALLOC_H
#include <malloc.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#if HAVE_SYSLOG_H
#include <syslog.h>
#ifndef LOG_CONS                /* Interesting Ultrix feature */
#include <sys/syslog.h>
#endif
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#include <stdarg.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <net-snmp/types.h>
#include <net-snmp/output_api.h>
#include <net-snmp/library/snmp_logging.h>      /* For this file's "internal" definitions */
#include <net-snmp/config_api.h>
#include <net-snmp/utilities.h>

#include <net-snmp/library/callback.h>

#include "snmp_syslog.h"

#ifdef va_copy
#define NEED_VA_END_AFTER_VA_COPY
#else
#ifdef __vacopy
#define vacopy __vacopy
#define NEED_VA_END_AFTER_VA_COPY
#else
#define va_copy(dest, src) memcpy (&dest, &src, sizeof (va_list))
#endif
#endif
#ifndef HAVE_VSNPRINTF
#include "snprintf.h"
#endif

netsnmp_feature_child_of(logging_all, libnetsnmp);

netsnmp_feature_child_of(logging_outputs, logging_all);
netsnmp_feature_child_of(logging_file, logging_outputs);
netsnmp_feature_child_of(logging_stdio, logging_outputs);
netsnmp_feature_child_of(logging_syslog, logging_outputs);
netsnmp_feature_child_of(logging_external, logging_all);

netsnmp_feature_child_of(enable_stderrlog, logging_all);

netsnmp_feature_child_of(logging_enable_calllog, netsnmp_unused);
netsnmp_feature_child_of(logging_enable_loghandler, netsnmp_unused);

/* default to the file/stdio/syslog set */
netsnmp_feature_want(logging_outputs);

/*
 * logh_head:  A list of all log handlers, in increasing order of priority
 * logh_priorities:  'Indexes' into this list, by priority
 */
netsnmp_log_handler *logh_head = NULL;
netsnmp_log_handler *logh_priorities[LOG_DEBUG+1];
static int  logh_enabled = 0;

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
static char syslogname[64] = DEFAULT_LOG_ID;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
netsnmp_log_handler *
netsnmp_register_stdio_loghandler(int is_stdout, int priority, int priority_max,
                                  const char *tok);
#endif
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
netsnmp_log_handler *
netsnmp_register_filelog_handler(const char* logfilename, int priority,
                                 int priority_max, int dont_zero_log);
#endif

void
netsnmp_disable_this_loghandler(netsnmp_log_handler *logh)
{
    if (!logh || (0 == logh->enabled))
        return;
    logh->enabled = 0;
    --logh_enabled;
    netsnmp_assert(logh_enabled >= 0);
}

void
netsnmp_enable_this_loghandler(netsnmp_log_handler *logh)
{
    if (!logh || (0 != logh->enabled))
        return;
    logh->enabled = 1;
    ++logh_enabled;
}

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
void
netsnmp_enable_filelog(netsnmp_log_handler *logh, int dont_zero_log);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */

void
parse_config_logOption(const char *token, char *cptr)
{
  int my_argc = 0 ;
  char **my_argv = NULL;

  snmp_log_options( cptr, my_argc, my_argv );
}

void
init_snmp_logging(void)
{
    netsnmp_ds_register_premib(ASN_BOOLEAN, "snmp", "logTimestamp", 
			 NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_LOG_TIMESTAMP);
    register_prenetsnmp_mib_handler("snmp", "logOption",
                                    parse_config_logOption, NULL, "string");

}

void
shutdown_snmp_logging(void)
{
   snmp_disable_log();
   while(NULL != logh_head)
      netsnmp_remove_loghandler( logh_head );
}

/* Set line buffering mode for a stream. */
void
netsnmp_set_line_buffering(FILE *stream)
{
#if defined(WIN32)
    /*
     * According to MSDN, the Microsoft Visual Studio C runtime library does
     * not support line buffering, so turn off buffering completely.
     * See also http://msdn.microsoft.com/en-us/library/86cebhfs(VS.71).aspx.
     */
    setvbuf(stream, NULL, _IONBF, BUFSIZ);
#elif defined(HAVE_SETLINEBUF)
    /* setlinefunction() is a function from the BSD Unix API. */
    setlinebuf(stream);
#else
    /* See also the C89 or C99 standard for more information about setvbuf(). */
    setvbuf(stream, NULL, _IOLBF, BUFSIZ);
#endif
}

/*
 * Decodes log priority.
 * @param optarg - IN - priority to decode, "0" or "0-7"
 *                 OUT - points to last character after the decoded priority
 * @param pri_max - OUT - maximum priority (i.e. 0x7 from "0-7")
 */
static int
decode_priority( char **optarg, int *pri_max )
{
    int pri_low = LOG_DEBUG;

    if (*optarg == NULL)
        return -1;

    switch (**optarg) {
        case '0': 
        case '!': 
            pri_low = LOG_EMERG;
            break;
        case '1': 
        case 'a': 
        case 'A': 
            pri_low = LOG_ALERT;
            break;
        case '2': 
        case 'c': 
        case 'C': 
            pri_low = LOG_CRIT;
            break;
        case '3': 
        case 'e': 
        case 'E': 
            pri_low = LOG_ERR;
            break;
        case '4': 
        case 'w': 
        case 'W': 
            pri_low = LOG_WARNING;
            break;
        case '5': 
        case 'n': 
        case 'N': 
            pri_low = LOG_NOTICE;
            break;
        case '6': 
        case 'i': 
        case 'I': 
            pri_low = LOG_INFO;
            break;
        case '7': 
        case 'd': 
        case 'D': 
            pri_low = LOG_DEBUG;
            break;
        default: 
            fprintf(stderr, "invalid priority: %c\n",**optarg);
            return -1;
    }
    *optarg = *optarg+1;

    if (pri_max && **optarg=='-') {
        *optarg = *optarg + 1; /* skip '-' */
        *pri_max = decode_priority( optarg, NULL );
        if (*pri_max == -1) return -1;
        if (pri_low < *pri_max) { 
            int tmp = pri_low; 
            pri_low = *pri_max; 
            *pri_max = tmp; 
        }

    }
    return pri_low;
}

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
static int
decode_facility( char *optarg )
{
    if (optarg == NULL)
        return -1;

    switch (*optarg) {
    case 'd':
    case 'D':
        return LOG_DAEMON;
    case 'u':
    case 'U':
        return LOG_USER;
    case '0':
        return LOG_LOCAL0;
    case '1':
        return LOG_LOCAL1;
    case '2':
        return LOG_LOCAL2;
    case '3':
        return LOG_LOCAL3;
    case '4':
        return LOG_LOCAL4;
    case '5':
        return LOG_LOCAL5;
    case '6':
        return LOG_LOCAL6;
    case '7':
        return LOG_LOCAL7;
    default:
        fprintf(stderr, "invalid syslog facility: %c\n",*optarg);
        return -1;
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

int
snmp_log_options(char *optarg, int argc, char *const *argv)
{
    char           *cp = optarg;
        /*
	 * Hmmm... this doesn't seem to work.
	 * The main agent 'getopt' handling assumes
	 *   that the -L option takes an argument,
	 *   and objects if this is missing.
	 * Trying to differentiate between
	 *   new-style "-Lx", and old-style "-L xx"
	 *   is likely to be a major headache.
	 */
    char            missing_opt = 'e';	/* old -L is new -Le */
    int             priority = LOG_DEBUG;
    int             pri_max  = LOG_EMERG;
    int             inc_optind = 0;
    netsnmp_log_handler *logh;

    DEBUGMSGT(("logging:options", "optarg: '%s', argc %d, argv '%s'\n",
               optarg, argc, argv ? argv[0] : "NULL"));
    optarg++;
    if (!*cp)
        cp = &missing_opt;

    /*
     * Support '... -Lx=value ....' syntax
     */
    if (*optarg == '=') {
        optarg++;
    }
    /*
     * and '.... "-Lx value" ....'  (*with* the quotes)
     */
    while (*optarg && isspace((unsigned char)(*optarg))) {
        optarg++;
    }
    /*
     * Finally, handle ".... -Lx value ...." syntax
     *   (*without* surrounding quotes)
     */
    if ((!*optarg) && (NULL != argv)) {
        /*
         * We've run off the end of the argument
         *  so move on to the next.
         * But we might not actually need it, so don't
	 *  increment optind just yet!
         */
        optarg = argv[optind];
        inc_optind = 1;
    }

    DEBUGMSGT(("logging:options", "*cp: '%c'\n", *cp));
    switch (*cp) {

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
    /*
     * Log to Standard Error
     */
    case 'E':
        priority = decode_priority( &optarg, &pri_max );
        if (priority == -1)  return -1;
        if (inc_optind)
            optind++;
        /* Fallthrough */
    case 'e':
        logh = netsnmp_register_stdio_loghandler(0, priority, pri_max, "stderr");
        break;

    /*
     * Log to Standard Output
     */
    case 'O':
        priority = decode_priority( &optarg, &pri_max );
        if (priority == -1)  return -1;
        if (inc_optind)
            optind++;
        /* Fallthrough */
    case 'o':
        logh = netsnmp_register_stdio_loghandler( 1, priority, pri_max, "stdout" );
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */

    /*
     * Log to a named file
     */
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
    case 'F':
        priority = decode_priority( &optarg, &pri_max );
        if (priority == -1) return -1;
        while (*optarg == ' ') optarg++;
        if (!*optarg && !argv) return -1;
        else if (!*optarg) optarg = argv[++optind];
        /* FALL THROUGH */
    case 'f':
        if (inc_optind)
            optind++;
        if (!optarg) {
            fprintf(stderr, "Missing log file\n");
            return -1;
        }
        DEBUGMSGTL(("logging:options", "%d-%d: '%s'\n", priority, pri_max, optarg));
        logh = netsnmp_register_filelog_handler(optarg, priority, pri_max,
                                                   -1);
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
    /*
     * Log to syslog
     */
    case 'S':
        priority = decode_priority( &optarg, &pri_max );
        if (priority == -1 || !argv)  return -1;
        if (!optarg[0]) {
            /* The command line argument with priority does not contain log
             * facility. The facility must be in next argument then. */
            optind++;
            if (optind < argc)
                optarg = argv[optind];
        }
        /* Fallthrough */
    case 's':
        if (inc_optind)
            optind++;
        if (!optarg) {
            fprintf(stderr, "Missing syslog facility\n");
            return -1;
        }
        logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_SYSLOG, priority);
        if (logh) {
            int facility = decode_facility(optarg);
            if (facility == -1) {
                netsnmp_remove_loghandler(logh);
                return -1;
            }
            logh->pri_max = pri_max;
            logh->token   = strdup(snmp_log_syslogname(NULL));
            logh->magic   = (void *)(intptr_t)facility;
	    snmp_enable_syslog_ident(snmp_log_syslogname(NULL), facility);
	}
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

    /*
     * Don't log 
     */
    case 'N':
        priority = decode_priority( &optarg, &pri_max );
        if (priority == -1)  return -1;
        if (inc_optind)
            optind++;
        /* Fallthrough */
    case 'n':
        /*
         * disable all logs to clean them up (close files, etc),
         * remove all log handlers, then register a null handler.
         */
        snmp_disable_log();
        while(NULL != logh_head)
            netsnmp_remove_loghandler( logh_head );
        logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_NONE, priority);
        if (logh) {
            logh->pri_max = pri_max;
	}
        break;

    default:
        fprintf(stderr, "Unknown logging option passed to -L: %c.\n", *cp);
        return -1;
    }
    return 0;
}

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
char *
snmp_log_syslogname(const char *pstr)
{
  if (pstr)
    strlcpy (syslogname, pstr, sizeof(syslogname));

  return syslogname;
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

void
snmp_log_options_usage(const char *lead, FILE * outf)
{
    const char *pri1_msg = " for level 'pri' and above";
    const char *pri2_msg = " for levels 'p1' to 'p2'";
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
    fprintf(outf, "%se:           log to standard error\n", lead);
    fprintf(outf, "%so:           log to standard output\n", lead);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */
    fprintf(outf, "%sn:           don't log at all\n", lead);
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
    fprintf(outf, "%sf file:      log to the specified file\n", lead);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
    fprintf(outf, "%ss facility:  log to syslog (via the specified facility)\n", lead);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */
    fprintf(outf, "\n%s(variants)\n", lead);
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
    fprintf(outf, "%s[EON] pri:   log to standard error, output or /dev/null%s\n", lead, pri1_msg);
    fprintf(outf, "%s[EON] p1-p2: log to standard error, output or /dev/null%s\n", lead, pri2_msg);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */
    fprintf(outf, "%s[FS] pri token:    log to file/syslog%s\n", lead, pri1_msg);
    fprintf(outf, "%s[FS] p1-p2 token:  log to file/syslog%s\n", lead, pri2_msg);
}

/**
 * Is logging done?
 *
 * @return Returns 0 if logging is off, 1 when it is done.
 *
 */
int
snmp_get_do_logging(void)
{
    return (logh_enabled > 0);
}


static char    *
sprintf_stamp(time_t * now, char *sbuf)
{
    time_t          Now;
    struct tm      *tm;

    if (now == NULL) {
        now = &Now;
        time(now);
    }
    tm = localtime(now);
    sprintf(sbuf, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d ",
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
            tm->tm_hour, tm->tm_min, tm->tm_sec);
    return sbuf;
}

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
void
snmp_disable_syslog_entry(netsnmp_log_handler *logh)
{
    if (!logh || !logh->enabled || logh->type != NETSNMP_LOGHANDLER_SYSLOG)
        return;

#ifdef WIN32
    if (logh->magic) {
        HANDLE eventlog_h = (HANDLE)logh->magic;
        CloseEventLog(eventlog_h);
        logh->magic = NULL;
    }
#else
    closelog();
    logh->imagic  = 0;
#endif

    netsnmp_disable_this_loghandler(logh);
}

void
snmp_disable_syslog(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->enabled && logh->type == NETSNMP_LOGHANDLER_SYSLOG)
            snmp_disable_syslog_entry(logh);
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
void
snmp_disable_filelog_entry(netsnmp_log_handler *logh)
{
    if (!logh /* || !logh->enabled */ || logh->type != NETSNMP_LOGHANDLER_FILE)
        return;

    if (logh->magic) {
        fputs("\n", (FILE*)logh->magic);	/* XXX - why? */
        fclose((FILE*)logh->magic);
        logh->magic   = NULL;
    }
    netsnmp_disable_this_loghandler(logh);
}

void
snmp_disable_filelog(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->enabled && logh->type == NETSNMP_LOGHANDLER_FILE)
            snmp_disable_filelog_entry(logh);
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO

netsnmp_log_handler *
netsnmp_register_stdio_loghandler(int is_stdout, int priority, int priority_max,
                                  const char *tok)
{
    netsnmp_log_handler *logh =
        netsnmp_register_loghandler(NETSNMP_LOGHANDLER_STDERR, priority);
    if (NULL == logh) {
        return NULL;
    }
    if (is_stdout) {
        netsnmp_set_line_buffering(stdout);
        logh->imagic = 1; /* stdout, not stderr */
    } else
        netsnmp_set_line_buffering(stderr);

    logh->pri_max = priority_max;
    if (tok)
        logh->token   = strdup(tok);
    return logh;
}

/*
 * returns that status of stderr logging
 *
 * @retval 0 : stderr logging disabled
 * @retval 1 : stderr logging enabled
 */
int
snmp_stderrlog_status(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->enabled && (logh->type == NETSNMP_LOGHANDLER_STDOUT ||
                              logh->type == NETSNMP_LOGHANDLER_STDERR)) {
            return 1;
       }

    return 0;
}

void
snmp_disable_stderrlog(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->enabled && (logh->type == NETSNMP_LOGHANDLER_STDOUT ||
                              logh->type == NETSNMP_LOGHANDLER_STDERR)) {
            netsnmp_disable_this_loghandler(logh);
	}
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_CALLLOG
void
snmp_disable_calllog(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->enabled && logh->type == NETSNMP_LOGHANDLER_CALLBACK) {
            netsnmp_disable_this_loghandler(logh);
	}
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_CALLLOG */

void
snmp_disable_log(void)
{
    netsnmp_log_handler *logh;

    for (logh = logh_head; logh; logh = logh->next) {
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
        if (logh->type == NETSNMP_LOGHANDLER_SYSLOG)
            snmp_disable_syslog_entry(logh);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
        if (logh->type == NETSNMP_LOGHANDLER_FILE)
            snmp_disable_filelog_entry(logh);
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */
        netsnmp_disable_this_loghandler(logh);
    }
}

/*
 * close and reopen all file based logs, to allow logfile
 * rotation.
 */
void
netsnmp_logging_restart(void)
{
    netsnmp_log_handler *logh;
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
    int doneone = 0;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

    for (logh = logh_head; logh; logh = logh->next) {
        if (0 == logh->enabled)
            continue;
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
        if (logh->type == NETSNMP_LOGHANDLER_SYSLOG) {
            snmp_disable_syslog_entry(logh);
            snmp_enable_syslog_ident(logh->token,(int)(intptr_t)logh->magic);
            doneone = 1;
        }
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
        if (logh->type == NETSNMP_LOGHANDLER_FILE && !doneone) {
            snmp_disable_filelog_entry(logh);
            /** hmm, don't zero status isn't saved.. i think it's
             * safer not to overwrite, in case a hup is just to
             * re-read config files...
             */
            netsnmp_enable_filelog(logh, 1);
        }
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */
    }
}

/* ================================================== */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
void
snmp_enable_syslog(void)
{
    snmp_enable_syslog_ident(snmp_log_syslogname(NULL), LOG_DAEMON);
}

void
snmp_enable_syslog_ident(const char *ident, const int facility)
{
    netsnmp_log_handler *logh;
    int                  found = 0;
    int                  enable = 1;
#ifdef WIN32
    HANDLE               eventlog_h;
#else
    void                *eventlog_h = NULL;
#endif

    snmp_disable_syslog();     /* only one syslog at a time */
#ifdef WIN32
    eventlog_h = OpenEventLog(NULL, ident);
    if (eventlog_h == NULL) {
	    /*
	     * Hmmm.....
	     * Maybe disable this handler, and log the error ?
	     */
        fprintf(stderr, "Could not open event log for %s. Last error: %u\n",
                ident, (unsigned int)GetLastError());
        enable = 0;
    }
#else
    openlog(snmp_log_syslogname(ident), LOG_CONS | LOG_PID, facility);
#endif

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->type == NETSNMP_LOGHANDLER_SYSLOG) {
            logh->magic   = (void*)eventlog_h;
            logh->imagic  = enable;	/* syslog open */
            if (logh->enabled && (0 == enable))
                netsnmp_disable_this_loghandler(logh);
            else if ((0 == logh->enabled) && enable)
                netsnmp_enable_this_loghandler(logh);
            found         = 1;
	}

    if (!found) {
        logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_SYSLOG,
                                           LOG_DEBUG );
        if (logh) {
            logh->magic    = (void*)eventlog_h;
            logh->token    = strdup(ident);
            logh->imagic   = enable;	/* syslog open */
            if (logh->enabled && (0 == enable))
                netsnmp_disable_this_loghandler(logh);
            else if ((0 == logh->enabled) && enable)
                netsnmp_enable_this_loghandler(logh);
        }
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
void
netsnmp_enable_filelog(netsnmp_log_handler *logh, int dont_zero_log)
{
    FILE *logfile;

    if (!logh)
        return;

    if (!logh->magic) {
        logfile = fopen(logh->token, dont_zero_log ? "a" : "w");
        if (!logfile) {
	    snmp_log_perror(logh->token);
            return;
	}
        logh->magic = (void*)logfile;
        netsnmp_set_line_buffering(logfile);
    }
    netsnmp_enable_this_loghandler(logh);
}

netsnmp_log_handler *
netsnmp_register_filelog_handler(const char* logfilename, int priority,
                                 int priority_max, int dont_zero_log)
{
    netsnmp_log_handler *logh =
        netsnmp_register_loghandler(NETSNMP_LOGHANDLER_FILE,
                                    priority );
    if (NULL == logh)
        return NULL;
    logh->pri_max = priority_max;
    logh->token = strdup(logfilename);
    if (-1 == dont_zero_log)
        dont_zero_log = netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
                                               NETSNMP_DS_LIB_APPEND_LOGFILES);
    netsnmp_enable_filelog(logh, dont_zero_log);
    return logh;
}

void
snmp_enable_filelog(const char *logfilename, int dont_zero_log)
{
    netsnmp_log_handler *logh;

    /*
     * don't disable ALL filelogs whenever a new one is enabled.
     * this prevents '-Lf file' from working in snmpd, as the
     * call to set up /var/log/snmpd.log will disable the previous
     * log setup.
     * snmp_disable_filelog();
     */

    if (logfilename) {
        logh = netsnmp_find_loghandler( logfilename );
        if (!logh)
            logh = netsnmp_register_filelog_handler( logfilename, LOG_DEBUG,
                                                     0, dont_zero_log );
        else
            netsnmp_enable_filelog(logh, dont_zero_log);
    } else {
        for (logh = logh_head; logh; logh = logh->next)
            if (logh->type == NETSNMP_LOGHANDLER_FILE)
                netsnmp_enable_filelog(logh, dont_zero_log);
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */


#ifndef NETSNMP_FEATURE_REMOVE_ENABLE_STDERRLOG
/* used in the perl modules and ip-mib/ipv4InterfaceTable/ipv4InterfaceTable_subagent.c */
void
snmp_enable_stderrlog(void)
{
    netsnmp_log_handler *logh;
    int                  found = 0;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->type == NETSNMP_LOGHANDLER_STDOUT ||
            logh->type == NETSNMP_LOGHANDLER_STDERR) {
            netsnmp_enable_this_loghandler(logh);
            found         = 1;
        }

    if (!found) {
        logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_STDERR,
                                           LOG_DEBUG );
        if (logh)
            logh->token    = strdup("stderr");
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_ENABLE_STDERRLOG */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_CALLLOG
void
snmp_enable_calllog(void)	/* XXX - or take a callback routine ??? */
{
    netsnmp_log_handler *logh;
    int                  found = 0;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->type == NETSNMP_LOGHANDLER_CALLBACK) {
            netsnmp_enable_this_loghandler(logh);
            found         = 1;
	}

    if (!found) {
        logh = netsnmp_register_loghandler(NETSNMP_LOGHANDLER_CALLBACK,
                                           LOG_DEBUG );
        if (logh)
            logh->token    = strdup("callback");
    }
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_CALLLOG */


/* ==================================================== */


netsnmp_log_handler *
netsnmp_find_loghandler( const char *token )
{
    netsnmp_log_handler *logh;
    if (!token)
        return NULL;

    for (logh = logh_head; logh; logh = logh->next)
        if (logh->token && !strcmp( token, logh->token ))
            break;

    return logh;
}

int
netsnmp_add_loghandler( netsnmp_log_handler *logh )
{
    int i;
    netsnmp_log_handler *logh2;

    if (!logh)
        return 0;

    /*
     * Find the appropriate point for the new entry...
     *   (logh2 will point to the entry immediately following)
     */
    for (logh2 = logh_head; logh2; logh2 = logh2->next)
        if ( logh2->priority >= logh->priority )
            break;

    /*
     * ... and link it into the main list.
     */
    if (logh2) {
        if (logh2->prev)
            logh2->prev->next = logh;
        else
            logh_head = logh;
        logh->next  = logh2;
        logh2->prev = logh;
    } else if (logh_head ) {
        /*
         * If logh2 is NULL, we're tagging on to the end
         */
        for (logh2 = logh_head; logh2->next; logh2 = logh2->next)
            ;
        logh2->next = logh;
    } else {
        logh_head = logh;
    }

    /*
     * Also tweak the relevant priority-'index' array.
     */
    for (i=LOG_EMERG; i<=logh->priority; i++)
        if (!logh_priorities[i] ||
             logh_priorities[i]->priority >= logh->priority)
             logh_priorities[i] = logh;

    return 1;
}

netsnmp_log_handler *
netsnmp_register_loghandler( int type, int priority )
{
    netsnmp_log_handler *logh;

    logh = SNMP_MALLOC_TYPEDEF(netsnmp_log_handler);
    if (!logh)
        return NULL;

    DEBUGMSGT(("logging:register", "registering log type %d with pri %d\n",
               type, priority));
    if (priority > LOG_DEBUG) {
        DEBUGMSGT(("logging:register", "  limiting pri %d to %d\n", priority,
                   LOG_DEBUG));
        priority = LOG_DEBUG;
    }

    logh->type     = type;
    switch ( type ) {
    case NETSNMP_LOGHANDLER_STDOUT:
        logh->imagic  = 1;
        logh->handler = log_handler_stdouterr;
        break;
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
    case NETSNMP_LOGHANDLER_STDERR:
        logh->handler = log_handler_stdouterr;
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
    case NETSNMP_LOGHANDLER_FILE:
        logh->handler = log_handler_file;
        logh->imagic  = 1;
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
    case NETSNMP_LOGHANDLER_SYSLOG:
        logh->handler = log_handler_syslog;
        break;
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */
    case NETSNMP_LOGHANDLER_CALLBACK:
        logh->handler = log_handler_callback;
        break;
    case NETSNMP_LOGHANDLER_NONE:
        logh->handler = log_handler_null;
        break;
    default:
        free(logh);
        return NULL;
    }
    logh->priority = priority;
    netsnmp_enable_this_loghandler(logh);
    netsnmp_add_loghandler( logh );
    return logh;
}


#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_LOGHANDLER
int
netsnmp_enable_loghandler( const char *token )
{
    netsnmp_log_handler *logh;

    logh = netsnmp_find_loghandler( token );
    if (!logh)
        return 0;
    netsnmp_enable_this_loghandler(logh);
    return 1;
}


int
netsnmp_disable_loghandler( const char *token )
{
    netsnmp_log_handler *logh;

    logh = netsnmp_find_loghandler( token );
    if (!logh)
        return 0;
    netsnmp_disable_this_loghandler(logh);
    return 1;
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_ENABLE_LOGHANDLER */

int
netsnmp_remove_loghandler( netsnmp_log_handler *logh )
{
    int i;
    if (!logh)
        return 0;

    if (logh->prev)
        logh->prev->next = logh->next;
    else
        logh_head = logh->next;

    if (logh->next)
        logh->next->prev = logh->prev;

    for (i=LOG_EMERG; i<=logh->priority; i++)
        if (logh == logh_priorities[i])
            logh_priorities[i] = logh->next;
    free(NETSNMP_REMOVE_CONST(char*, logh->token));
    SNMP_FREE(logh);

    return 1;
}

/* ==================================================== */

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
int
log_handler_stdouterr(  netsnmp_log_handler* logh, int pri, const char *str)
{
    static int      newline = 1;	 /* MTCRITICAL_RESOURCE */
    const char     *newline_ptr;
    char            sbuf[40];

    if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, 
                               NETSNMP_DS_LIB_LOG_TIMESTAMP) && newline) {
        sprintf_stamp(NULL, sbuf);
    } else {
        strcpy(sbuf, "");
    }
    /*
     * Remember whether or not the current line ends with a newline for the
     * next call of log_handler_stdouterr().
     */
    newline_ptr = strrchr(str, '\n');
    newline = newline_ptr && newline_ptr[1] == 0;

    if (logh->imagic)
       printf(         "%s%s", sbuf, str);
    else
       fprintf(stderr, "%s%s", sbuf, str);

    return 1;
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */


#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG
#ifdef WIN32
int
log_handler_syslog(  netsnmp_log_handler* logh, int pri, const char *str)
{
    WORD            etype;
    LPCTSTR         event_msg[2];
    HANDLE          eventlog_h = logh->magic;

        /*
         **  EVENT TYPES:
         **
         **  Information (EVENTLOG_INFORMATION_TYPE)
         **      Information events indicate infrequent but significant
         **      successful operations.
         **  Warning (EVENTLOG_WARNING_TYPE)
         **      Warning events indicate problems that are not immediately
         **      significant, but that may indicate conditions that could
         **      cause future problems. Resource consumption is a good
         **      candidate for a warning event.
         **  Error (EVENTLOG_ERROR_TYPE)
         **      Error events indicate significant problems that the user
         **      should know about. Error events usually indicate a loss of
         **      functionality or data.
         */
    switch (pri) {
        case LOG_EMERG:
        case LOG_ALERT:
        case LOG_CRIT:
        case LOG_ERR:
            etype = EVENTLOG_ERROR_TYPE;
            break;
        case LOG_WARNING:
            etype = EVENTLOG_WARNING_TYPE;
            break;
        case LOG_NOTICE:
        case LOG_INFO:
        case LOG_DEBUG:
            etype = EVENTLOG_INFORMATION_TYPE;
            break;
        default:
            etype = EVENTLOG_INFORMATION_TYPE;
            break;
    }
    event_msg[0] = str;
    event_msg[1] = NULL;
    /* NOTE: 4th parameter must match winservice.mc:MessageId value */
    if (!ReportEvent(eventlog_h, etype, 0, 100, NULL, 1, 0, event_msg, NULL)) {
	    /*
	     * Hmmm.....
	     * Maybe disable this handler, and log the error ?
	     */
        fprintf(stderr, "Could not report event.  Last error: %u\n",
                (unsigned int)GetLastError());
        return 0;
    }
    return 1;
}
#else
int
log_handler_syslog(  netsnmp_log_handler* logh, int pri, const char *str)
{
	/*
	 * XXX
	 * We've got three items of information to work with:
	 *     Is the syslog currently open?
	 *     What ident string to use?
	 *     What facility to log to?
	 *
	 * We've got two "magic" locations (imagic & magic) plus the token
	 */
    if (!(logh->imagic)) {
        const char *ident    = logh->token;
        int   facility = (int)(intptr_t)logh->magic;
        if (!ident)
            ident = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
                                          NETSNMP_DS_LIB_APPTYPE);
        openlog(ident, LOG_CONS | LOG_PID, facility);
        logh->imagic = 1;
    }
    syslog( pri, "%s", str );
    return 1;
}
#endif /* !WIN32 */
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_SYSLOG */


#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_FILE
int
log_handler_file(    netsnmp_log_handler* logh, int pri, const char *str)
{
    FILE           *fhandle;
    char            sbuf[40];
    int             len = strlen( str );

    /*
     * We use imagic to save information about whether the next output
     * will start a new line, and thus might need a timestamp
     */
    if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID, 
                               NETSNMP_DS_LIB_LOG_TIMESTAMP) && logh->imagic) {
        sprintf_stamp(NULL, sbuf);
    } else {
        strcpy(sbuf, "");
    }

    /*
     * If we haven't already opened the file, then do so.
     * Save the filehandle pointer for next time.
     *
     * Note that this should still work, even if the file
     * is closed in the meantime (e.g. a regular "cleanup" sweep)
     */
    fhandle = (FILE*)logh->magic;
    if (!logh->magic) {
        fhandle = fopen(logh->token, "a+");
        if (!fhandle)
            return 0;
        logh->magic = (void*)fhandle;
    }
    fprintf(fhandle, "%s%s", sbuf, str);
    fflush(fhandle);
    if (len > 0) {
        logh->imagic = str[len - 1] == '\n';
    } else {
        logh->imagic = 0;
    }
    return 1;
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_FILE */

int
log_handler_callback(netsnmp_log_handler* logh, int pri, const char *str)
{
	/*
	 * XXX - perhaps replace 'snmp_call_callbacks' processing
	 *       with individual callback log_handlers ??
	 */
    struct snmp_log_message slm;
    int             dodebug = snmp_get_do_debugging();

    slm.priority = pri;
    slm.msg = str;
    if (dodebug)            /* turn off debugging inside the callbacks else will loop */
        snmp_set_do_debugging(0);
    snmp_call_callbacks(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_LOGGING, &slm);
    if (dodebug)
        snmp_set_do_debugging(dodebug);
    return 1;
}

int
log_handler_null(    netsnmp_log_handler* logh, int pri, const char *str)
{
    /*
     * Dummy log handler - just throw away the error completely
     * You probably don't really want to do this!
     */
    return 1;
}

void
snmp_log_string(int priority, const char *str)
{
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
    static int stderr_enabled = 0;
    static netsnmp_log_handler lh = { 1, 0, 0, 0, "stderr",
                                      log_handler_stdouterr, 0, NULL,  NULL,
                                      NULL };
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */
    netsnmp_log_handler *logh;

    /*
     * We've got to be able to log messages *somewhere*!
     * If you don't want stderr logging, then enable something else.
     */
    if (0 == logh_enabled) {
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
        if (!stderr_enabled) {
            ++stderr_enabled;
            netsnmp_set_line_buffering(stderr);
        }
        log_handler_stdouterr( &lh, priority, str );
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */

        return;
    }
#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_STDIO
    else if (stderr_enabled) {
        stderr_enabled = 0;
        log_handler_stdouterr( &lh, LOG_INFO,
                               "Log handling defined - disabling stderr\n" );
    }
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_STDIO */
        

    /*
     * Start at the given priority, and work "upwards"....
     */
    if (priority > LOG_DEBUG)
        priority = LOG_DEBUG;
    logh = logh_priorities[priority];
    for ( ; logh; logh = logh->next ) {
        /*
         * ... but skipping any handlers with a "maximum priority"
         *     that we have already exceeded. And don't forget to
         *     ensure this logging is turned on (see snmp_disable_stderrlog
         *     and its cohorts).
         */
        if (logh->enabled && (priority >= logh->pri_max))
            logh->handler( logh, priority, str );
    }
}

/* ==================================================== */


/**
 * This snmp logging function allows variable argument list given the
 * specified priority, format and a populated va_list structure.
 * The default logfile this function writes to is /var/log/snmpd.log.
 *
 * @param priority is an integer representing the type of message to be written
 *	to the snmp log file.  The types are errors, warning, and information.
 *      - The error types are:
 *        - LOG_EMERG       system is unusable 
 *        - LOG_ALERT       action must be taken immediately 
 *        - LOG_CRIT        critical conditions 
 *        - LOG_ERR         error conditions
 *      - The warning type is:
 *        - LOG_WARNING     warning conditions 
 *      - The information types are:
 *        - LOG_NOTICE      normal but significant condition
 *        - LOG_INFO        informational
 *        - LOG_DEBUG       debug-level messages
 *
 * @param format is a pointer to a char representing the variable argument list
 *	format used.
 *
 * @param ap is a va_list type used to traverse the list of arguments.
 *
 * @return Returns 0 on success, -1 when the code could not format the log-
 *         string, -2 when dynamic memory could not be allocated if the length
 *         of the log buffer is greater then 1024 bytes.  For each of these
 *         errors a LOG_ERR messgae is written to the logfile.
 *
 * @see snmp_log
 */
int
snmp_vlog(int priority, const char *format, va_list ap)
{
    char           *buffer = NULL;
    int             length;

    length = vasprintf(&buffer, format, ap);
    if (length < 0) {
        snmp_log_string(LOG_ERR, "Could not format log-string\n");
        return -1;
    }

    snmp_log_string(priority, buffer);
    free(buffer);
    return 0;
}

/**
 * This snmp logging function allows variable argument list given the
 * specified format and priority.  Calls the snmp_vlog function.
 * The default logfile this function writes to is /var/log/snmpd.log.
 *
 * @see snmp_vlog
 */
int
snmp_log(int priority, const char *format, ...)
{
    va_list         ap;
    int             ret;
    va_start(ap, format);
    ret = snmp_vlog(priority, format, ap);
    va_end(ap);
    return (ret);
}

/*
 * log a critical error.
 */
void
snmp_log_perror(const char *s)
{
    char           *error = strerror(errno);
    if (s) {
        if (error)
            snmp_log(LOG_ERR, "%s: %s\n", s, error);
        else
            snmp_log(LOG_ERR, "%s: Error %d out-of-range\n", s, errno);
    } else {
        if (error)
            snmp_log(LOG_ERR, "%s\n", error);
        else
            snmp_log(LOG_ERR, "Error %d out-of-range\n", errno);
    }
}

#ifndef NETSNMP_FEATURE_REMOVE_LOGGING_EXTERNAL
/* external access to logh_head variable */
netsnmp_log_handler  *
get_logh_head(void)
{
	return logh_head;
}
#endif /* NETSNMP_FEATURE_REMOVE_LOGGING_EXTERNAL */

/**  @} */
