root/usr/src/lib/smbsrv/libmlsvc/common/eventlog_log.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <syslog.h>
#include <thread.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <dlfcn.h>
#include <sys/synch.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <ctype.h>
#include <smbsrv/ndl/eventlog.ndl>
#include <smbsrv/libmlsvc.h>

typedef struct logr_eventlog {
        const char *el_name;
        const char *el_path;
} logr_eventlog_t;

logr_eventlog_t logr_eventlog[] = {
        { "System",     "/var/adm/messages" },
        { "smbd",       "/var/smb/smbd_log.txt" },
        { "smbrdr",     "/var/smb/smbrdr_log.txt" }
};

typedef enum {
        LOGR_MONTH = 0,
        LOGR_DAY,
        LOGR_TIME,
        LOGR_HOST,
        LOGR_SOURCE,
        LOGR_IDTAG,
        LOGR_ID,
        LOGR_PRI_FAC,
        LOGR_NARG
} logr_syslog_tokens_t;

/*
 * Event code translation struct for use in processing config file
 */
typedef struct logr_priority {
        char    *p_name;
        int     p_value;
} logr_priority_t;

static logr_priority_t logr_pri_names[] = {
        "panic",        LOG_EMERG,
        "emerg",        LOG_EMERG,
        "alert",        LOG_ALERT,
        "crit",         LOG_CRIT,
        "err",          LOG_ERR,
        "error",        LOG_ERR,
        "warn",         LOG_WARNING,
        "warning",      LOG_WARNING,
        "notice",       LOG_NOTICE,
        "info",         LOG_INFO,
        "debug",        LOG_DEBUG
};

typedef struct logr_syslog_node {
        list_node_t     ln_node;
        char            ln_logline[LOGR_MAXENTRYLEN];
} logr_syslog_node_t;

static void *logr_interposer_hdl = NULL;
static struct {
        boolean_t (*logr_op_supported)(char *);
        int (*logr_op_snapshot)(logr_context_t *);
} logr_interposer_ops;

/*
 * Set the syslog timestamp.
 *
 * This is a private helper for logr_syslog_parse_entry(), which
 * must ensure that the appropriate argv entries are non-null.
 */
static void
logr_syslog_set_timestamp(char **argv, logr_entry_t *le)
{
        char *month = argv[LOGR_MONTH];
        char *day = argv[LOGR_DAY];
        char *time = argv[LOGR_TIME];
        struct timeval  now;
        struct tm tm, cur_tm;
        char buf[32];

        bzero(&tm, sizeof (tm));
        (void) snprintf(buf, 32, "%s %s %s", month, day, time);
        if (strptime(buf, "%b" "%d" "%H:%M:%S", &tm) == NULL) {
                le->le_timestamp.tv_sec = 0;
                return;
        }

        (void) gettimeofday(&now, NULL);
        (void) localtime_r(&now.tv_sec, &cur_tm);

        tm.tm_isdst = cur_tm.tm_isdst;
        tm.tm_year = cur_tm.tm_year;
        if (tm.tm_mon > cur_tm.tm_mon)
                tm.tm_year--;

        le->le_timestamp.tv_sec = mktime(&tm);
}

/*
 * Set the syslog priority.
 *
 * This is a private helper for logr_syslog_parse_entry(), which
 * must ensure that the appropriate argv entries are non-null.
 */
static void
logr_syslog_set_priority(char **argv, logr_entry_t *le)
{
        logr_priority_t *entry;
        char *token;
        int sz = sizeof (logr_pri_names) / sizeof (logr_pri_names[0]);
        int i;

        le->le_pri = LOG_INFO;

        if ((token = argv[LOGR_PRI_FAC]) == NULL)
                return;

        for (i = 0; i < sz; i++) {
                entry = &logr_pri_names[i];

                if (strstr(token, entry->p_name) != NULL) {
                        le->le_pri = entry->p_value;
                        break;
                }
        }
}

/*
 * Parse a syslog entry into a log_entry_t structure.  A typical syslog
 * entry has one of the following formats:
 *
 * <month> <day> <time> <host> <msg>
 * <month> <day> <time> <host> <source>: [ID <ID> <facility.priority>] <msg>
 *
 * For Example:
 * Oct 29 09:49:20 galaxy smbd[104039]: [ID 702911 daemon.info] init done
 */
static int
logr_syslog_parse_entry(char *logline, logr_entry_t *le)
{
        char buf[LOGR_MAXENTRYLEN];
        char *argv[LOGR_NARG];
        char *value;
        char *bp;
        int i;

        (void) memset(argv, 0, sizeof (char *) * LOGR_NARG);
        (void) strlcpy(buf, logline, LOGR_MAXENTRYLEN);

        for (bp = buf, i = 0; i < LOGR_NARG; ++i) {
                if (i == LOGR_SOURCE) {
                        /*
                         * If the [ID key is not present, everything
                         * that follows is the message text.
                         */
                        if (strstr(bp, "[ID") == NULL)
                                break;
                }

                do {
                        if ((value = strsep(&bp, " \t")) == NULL)
                                break;
                } while (*value == '\0');

                if ((argv[i] = value) == NULL)
                        return (-1);
        }

        /*
         * bp should be pointing at the remaining message text.
         */
        if ((value = strchr(bp, '\n')) != NULL)
                *value = '\0';

        (void) strlcpy(le->le_msg, bp, LOGR_MAXENTRYLEN);
        (void) strlcpy(le->le_hostname, argv[LOGR_HOST], MAXHOSTNAMELEN);
        logr_syslog_set_timestamp(argv, le);
        logr_syslog_set_priority(argv, le);
        return (0);
}

static void
logr_syslog_destroy_queue(list_t *queue)
{
        logr_syslog_node_t *head;

        while ((head = list_head(queue)) != NULL) {
                list_remove(queue, head);
                free(head);
        }
        list_destroy(queue);
}

static int
logr_syslog_construct_queue(FILE *fp, list_t *queue)
{
        logr_syslog_node_t *node, *head;
        int line_num = 0;
        char logline[LOGR_MAXENTRYLEN];

        list_create(queue, sizeof (logr_syslog_node_t),
            offsetof(logr_syslog_node_t, ln_node));

        bzero(logline, LOGR_MAXENTRYLEN);
        while (fgets(logline, LOGR_MAXENTRYLEN, fp) != NULL) {
                /* Read the last 1024 entries in the queue */
                if (line_num > LOGR_NMSGMASK) {
                        head = list_head(queue);
                        list_remove(queue, head);
                        free(head);
                }

                if ((node = malloc(sizeof (logr_syslog_node_t))) == NULL) {
                        logr_syslog_destroy_queue(queue);
                        return (-1);
                }
                bzero(node->ln_logline, LOGR_MAXENTRYLEN);

                (void) strlcpy(node->ln_logline, logline, LOGR_MAXENTRYLEN);
                list_insert_tail(queue, node);
                bzero(logline, LOGR_MAXENTRYLEN);
                line_num++;
        }

        return (0);
}

/*
 * logr_syslog_load
 *
 * Loads the given log file into log_info_t structure format.
 *
 * Returns pointer to the allocated log structure on success.
 * Note that the caller is responsible for freeing the allocated
 * memory for returned log_info_t structure.
 */
static int
logr_syslog_load(FILE *fp, logr_info_t *log)
{
        logr_entry_t *entry;
        int i = 0;

        list_t queue;
        logr_syslog_node_t *node;

        if (logr_syslog_construct_queue(fp, &queue) < 0)
                return (-1);

        node = list_head(&queue);
        while (node) {
                entry = &log->li_entry[i];

                if (logr_syslog_parse_entry(node->ln_logline, entry) != 0) {
                        node = list_next(&queue, node);
                        continue;
                }

                if (++i > LOGR_NMSGMASK)
                        break;

                node = list_next(&queue, node);
        }

        logr_syslog_destroy_queue(&queue);
        log->li_idx = i;

        return (0);
}

/*
 * logr_syslog_snapshot
 *
 * Return a snapshot of the given log in the buffer
 * provided by the caller. Returns the number of entries in
 * the log.
 */
static int
logr_syslog_snapshot(char *logname, logr_info_t *loginfo)
{
        FILE *fp;
        char path[MAXPATHLEN];
        int i;

        if ((loginfo == NULL) || (!logr_is_supported(logname)))
                return (-1);

        path[0] = '\0';
        for (i = 0; i < sizeof (logr_eventlog)/sizeof (logr_eventlog[0]); ++i) {
                if (strcasecmp(logname, logr_eventlog[i].el_name) == 0)
                        (void) strlcpy(path, logr_eventlog[i].el_path,
                            MAXPATHLEN);
        }

        if ((fp = fopen(path, "r")) == 0)
                return (-1);

        if (logr_syslog_load(fp, loginfo) < 0) {
                (void) fclose(fp);
                return (-1);
        }
        (void) fclose(fp);

        if (loginfo->li_idx <= LOGR_NMSGMASK)
                return (loginfo->li_idx);

        return (LOGR_NMSGMASK+1);
}

/*
 * logr_is_supported
 *
 * Determines if a given log is supported or not.
 * Returns B_TRUE on success, B_FALSE on failure.
 */
boolean_t
logr_is_supported(char *log_name)
{
        int i;

        if (log_name == NULL)
                return (B_FALSE);

        if (logr_interposer_ops.logr_op_supported != NULL)
                return (logr_interposer_ops.logr_op_supported(log_name));

        for (i = 0; i < sizeof (logr_eventlog)/sizeof (logr_eventlog[0]); ++i) {
                if (strcasecmp(log_name, logr_eventlog[i].el_name) == 0)
                        return (B_TRUE);
        }

        return (B_FALSE);
}

/*
 * logr_get_snapshot
 *
 * Allocate memory and make a copy, as a snapshot, from system log.
 * Returns 0 on success, -1 on failure.
 */
int
logr_get_snapshot(logr_context_t *ctx)
{
        logr_read_data_t *data = NULL;

        if (logr_interposer_ops.logr_op_snapshot != NULL)
                return (logr_interposer_ops.logr_op_snapshot(ctx));

        ctx->lc_cached_read_data = malloc(sizeof (logr_read_data_t));
        if (ctx->lc_cached_read_data != NULL) {
                data = ctx->lc_cached_read_data;

                data->rd_log = (logr_info_t *)malloc(sizeof (logr_info_t));
                if (data->rd_log == NULL) {
                        free(data);
                        return (-1);
                }
                bzero(data->rd_log, sizeof (logr_info_t));

                data->rd_tot_recnum = logr_syslog_snapshot(ctx->lc_source_name,
                    data->rd_log);
                if (data->rd_tot_recnum < 0) {
                        free(data->rd_log);
                        free(data);
                        return (-1);
                }

                data->rd_first_read = 1;

                return (0);
        }

        return (-1);
}

/*
 * logr_init
 *
 * Initializes the Eventlog service.
 * Checks to see if a event log utility library
 * is interposed. If yes then it'll initializes logr_interposer_ops
 * structure with function pointers from this library.
 */
void
logr_init(void)
{
        logr_interposer_hdl = smb_dlopen();
        if (logr_interposer_hdl == NULL)
                return;

        bzero((void *)&logr_interposer_ops, sizeof (logr_interposer_ops));

        logr_interposer_ops.logr_op_supported =
            (boolean_t (*)())dlsym(logr_interposer_hdl, "logr_is_supported");

        logr_interposer_ops.logr_op_snapshot =
            (int (*)())dlsym(logr_interposer_hdl, "logr_get_snapshot");

        if (logr_interposer_ops.logr_op_supported == NULL ||
            logr_interposer_ops.logr_op_snapshot == NULL)
                logr_fini();
}

/*
 * logr_fini
 *
 * Finalizes the Eventlog service.
 * Closes handle to interposed library.
 */
void
logr_fini(void)
{
        smb_dlclose(logr_interposer_hdl);
        logr_interposer_hdl = NULL;
        bzero((void *)&logr_interposer_ops, sizeof (logr_interposer_ops));
}