root/usr/src/cmd/nscd/nscd_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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdlib.h>
#include <locale.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/varargs.h>
#include <synch.h>
#include <thread.h>
#include <string.h>
#include <unistd.h>
#include "nscd_log.h"
#include "nscd_config.h"
#include "nscd_switch.h"
#include "cache.h"

/*
 * old nscd debug levels
 */
#define DBG_OFF         0
#define DBG_CANT_FIND   2
#define DBG_NETLOOKUPS  4
#define DBG_ALL         6

/* max. chars in a nscd log entry */
#define LOGBUFLEN       1024

/* configuration for the nscd log component */
int             _nscd_log_comp = 0x0;
int             _nscd_log_level = 0x0;
static char     _nscd_logfile[PATH_MAX] = { 0 };

#define NSCD_DEBUG_NONE         '0'
#define NSCD_DEBUG_OPEN         '1'
#define NSCD_DEBUG_CLOSE        '2'

static char     _nscd_debug = NSCD_DEBUG_NONE;
static char     _nscd_logfile_d[PATH_MAX] = { 0 };
static char     _nscd_logfile_s[PATH_MAX] = { 0 };

/* statistics data */
static nscd_cfg_stat_global_log_t logstats = {
        NSCD_CFG_STAT_GROUP_INFO_GLOBAL_LOG, 0 };

/* if no log file specified, log entry goes to stderr */
int _logfd = 2;


/* close old log file and open a new one */
static nscd_rc_t
_nscd_set_lf(
        char    *lf)
{
        int     newlogfd;
        char    *me = "_nscd_set_lf";

        /*
         *  don't try and open the log file /dev/null
         */
        if (lf == NULL || *lf == 0) {
                /* ignore empty log file specs */
                return (NSCD_SUCCESS);
        } else if (strcmp(lf, "/dev/null") == 0) {
                (void) strlcpy(_nscd_logfile, lf, PATH_MAX);
                if (_logfd >= 0)
                        (void) close(_logfd);
                _logfd = -1;
                return (NSCD_SUCCESS);
        } else if (strcmp(lf, "stderr") == 0) {
                (void) strlcpy(_nscd_logfile, lf, PATH_MAX);
                if (_logfd != -1 && _logfd != 2)
                        (void) close(_logfd);
                _logfd = 2;
                return (NSCD_SUCCESS);
        } else {

                /*
                 * In order to open this file securely, we'll try a few tricks
                 */

                if ((newlogfd = open(lf, O_EXCL|O_WRONLY|O_CREAT, 0644)) < 0) {
                        /*
                         * File already exists... now we need to get cute
                         * since opening a file in a world-writeable directory
                         * safely is hard = it could be a hard link or a
                         * symbolic link to a system file.
                         */
                        struct stat before;

                        if (lstat(lf, &before) < 0) {
                                if (_nscd_debug == NSCD_DEBUG_NONE)
                                        _nscd_logit(me, "Cannot open new "
                                            "logfile \"%s\": %sn",
                                            lf, strerror(errno));
                                return (NSCD_CFG_FILE_OPEN_ERROR);
                        }

                        if (S_ISREG(before.st_mode) && /* no symbolic links */
                            (before.st_nlink == 1) && /* no hard links */
                            (before.st_uid == 0)) {   /* owned by root */
                                if ((newlogfd =
                                    open(lf, O_APPEND|O_WRONLY, 0644)) < 0) {
                                        if (_nscd_debug == NSCD_DEBUG_NONE)
                                                _nscd_logit(me,
                                                    "Cannot open new "\
                                                    "logfile \"%s\": %s\n", lf,
                                                    strerror(errno));
                                        return (NSCD_CFG_FILE_OPEN_ERROR);
                                }
                        } else {
                                if (_nscd_debug == NSCD_DEBUG_NONE)
                                        _nscd_logit(me, "Cannot use specified "
                                            "logfile \"%s\": "\
                                            "file is/has links or isn't "
                                            "owned by root\n", lf);
                                return (NSCD_CFG_FILE_OPEN_ERROR);
                        }
                }

                (void) close(_logfd);
                (void) strlcpy(_nscd_logfile, lf, PATH_MAX);
                _logfd = newlogfd;
                if (_nscd_debug == NSCD_DEBUG_NONE)
                        _nscd_logit(me, "Start of new logfile %s\n", lf);
        }
        return (NSCD_SUCCESS);
}


/* log an entry to the configured nscd log file */
void
_nscd_logit(
        char            *funcname,
        char            *format,
        ...)
{
        static mutex_t  loglock = DEFAULTMUTEX;
        struct timeval  tv;
        char            tid_buf[32];
        char            pid_buf[32];
        char            buffer[LOGBUFLEN];
        int             safechars, offset;
        va_list         ap;

        if (_logfd < 0)
                return;

        if (_nscd_debug == NSCD_DEBUG_OPEN) {
                (void) mutex_lock(&loglock);
                if (_nscd_debug == NSCD_DEBUG_OPEN &&
                    *_nscd_logfile_d != '\0' &&
                    (strcmp(_nscd_logfile, "/dev/null") == 0 ||
                    strcmp(_nscd_logfile, "stderr") == 0)) {
                        (void) strlcpy(_nscd_logfile_s,
                            _nscd_logfile, PATH_MAX);
                        (void) _nscd_set_lf(_nscd_logfile_d);
                }
                _nscd_debug = NSCD_DEBUG_NONE;
                (void) mutex_unlock(&loglock);
        } else if (_nscd_debug == NSCD_DEBUG_CLOSE) {
                (void) mutex_lock(&loglock);
                if (_nscd_debug == NSCD_DEBUG_CLOSE)
                        (void) _nscd_set_lf(_nscd_logfile_s);
                _nscd_debug = NSCD_DEBUG_NONE;
                (void) mutex_unlock(&loglock);
        }

        va_start(ap, format);

        if (gettimeofday(&tv, NULL) != 0 ||
            ctime_r(&tv.tv_sec, buffer, LOGBUFLEN) == NULL) {
                (void) snprintf(buffer, LOGBUFLEN,
                    "<time conversion failed>\t");
        } else {
                (void) sprintf(tid_buf, "--%d", thr_self());
                (void) sprintf(pid_buf, "--%ld", getpid());
                /*
                 * ctime_r() includes some stuff we don't want;
                 * adjust length to overwrite " YYYY\n" and
                 * include tid string length.
                 */
                offset = strlen(buffer) - 6;
                safechars = LOGBUFLEN - (offset - 1);
                (void) snprintf(buffer + offset,
                    safechars, ".%.4ld%s%s\t%s:\n\t\t",
                    tv.tv_usec/100, tid_buf, pid_buf,
                    funcname);
        }
        offset = strlen(buffer);
        safechars = LOGBUFLEN - (offset - 1);
        /*LINTED: E_SEC_PRINTF_VAR_FMT*/
        if (vsnprintf(buffer + offset, safechars, format, ap) >
            safechars) {
                (void) strncat(buffer, "...\n", LOGBUFLEN);
        }

        (void) mutex_lock(&loglock);
        (void) write(_logfd, buffer, strlen(buffer));
        logstats.entries_logged++;
        (void) mutex_unlock(&loglock);

        va_end(ap);
}

/*
 * Map old nscd debug level (0 -10) to log level:
 *      -- >= 6: DBG_ALL                --> NSCD_LOG_LEVEL_ALL
 *      -- >= 4: DBG_DBG_NETLOOKUPS     --> NSCD_LOG_LEVEL_CANT_FIND
 *      -- >= 2: DBG_CANT_FIND          --> NSCD_LOG_LEVEL_CANT_FIND
 *      -- >= 0: DBG_OFF                --> NSCD_LOG_LEVEL_NONE
 */
static int
debug_to_log_level(
        int     level)
{
        if (level >= 0 && level <= 10) {
                if (level >= DBG_ALL)
                        return (NSCD_LOG_LEVEL_ALL);
                else if (level >= DBG_NETLOOKUPS)
                        return (NSCD_LOG_LEVEL_CANT_FIND);
                else if (level >= DBG_CANT_FIND)
                        return (NSCD_LOG_LEVEL_CANT_FIND);
                else if (level >= DBG_OFF)
                        return (NSCD_LOG_LEVEL_NONE);
        }
        return (level);
}

/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_notify(
        void                            *data,
        struct nscd_cfg_param_desc      *pdesc,
        nscd_cfg_id_t                   *nswdb,
        nscd_cfg_flag_t                 dflag,
        nscd_cfg_error_t                **errorp,
        void                            *cookie)
{

        nscd_cfg_global_log_t           *logcfg;
        int                             off;

        /*
         * At init time, the whole group of config params are received.
         * At update time, group or individual parameter value could
         * be received.
         */

        if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {

                logcfg = (nscd_cfg_global_log_t *)data;

                _nscd_log_comp = logcfg->debug_comp;
                _nscd_log_level = logcfg->debug_level;

                /*
                 * logcfg->logfile should have been opened
                 * by _nscd_cfg_log_verify()
                 */

                return (NSCD_SUCCESS);
        }

        /*
         * individual config parameter
         */
        off = offsetof(nscd_cfg_global_log_t, debug_comp);
        if (pdesc->p_offset == off) {
                _nscd_log_comp = *(nscd_cfg_bitmap_t *)data;
                return (NSCD_SUCCESS);
        }

        off = offsetof(nscd_cfg_global_log_t, debug_level);
        if (pdesc->p_offset == off)
                _nscd_log_level = *(nscd_cfg_bitmap_t *)data;

        /*
         * logcfg->logfile should have been opened
         * by _nscd_cfg_log_verify()
         */

        return (NSCD_SUCCESS);
}

/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_verify(
        void                            *data,
        struct  nscd_cfg_param_desc     *pdesc,
        nscd_cfg_id_t                   *nswdb,
        nscd_cfg_flag_t                 dflag,
        nscd_cfg_error_t                **errorp,
        void                            **cookie)
{
        nscd_cfg_global_log_t           *logcfg;
        nscd_cfg_bitmap_t               bt;
        int                             off;

        /*
         * There is no switch db specific config params
         * for the nscd log component. It is a bug if
         * the input param description is global.
         */
        if (_nscd_cfg_flag_is_not_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL))
                return (NSCD_CFG_PARAM_DESC_ERROR);

        /*
         * At init time, the whole group of config params are received.
         * At update time, group or individual parameter value could
         * be received.
         */

        if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {

                logcfg = (nscd_cfg_global_log_t *)data;

                if (_nscd_cfg_bitmap_valid(logcfg->debug_comp,
                    NSCD_LOG_ALL) == 0)
                        return (NSCD_CFG_SYNTAX_ERROR);

                if (_nscd_cfg_bitmap_valid(logcfg->debug_level,
                    NSCD_LOG_LEVEL_ALL) == 0)
                        return (NSCD_CFG_SYNTAX_ERROR);

                if (logcfg->logfile != NULL)
                        return (_nscd_set_lf(logcfg->logfile));

                return (NSCD_SUCCESS);
        }

        /*
         * individual config parameter
         */

        off = offsetof(nscd_cfg_global_log_t, debug_comp);
        if (pdesc->p_offset == off) {

                bt = *(nscd_cfg_bitmap_t *)data;
                if (_nscd_cfg_bitmap_valid(bt, NSCD_LOG_ALL) == 0)
                        return (NSCD_CFG_SYNTAX_ERROR);

                return (NSCD_SUCCESS);
        }

        off = offsetof(nscd_cfg_global_log_t, debug_level);
        if (pdesc->p_offset == off) {

                bt = *(nscd_cfg_bitmap_t *)data;
                if (_nscd_cfg_bitmap_valid(bt, NSCD_LOG_LEVEL_ALL) == 0)
                        return (NSCD_CFG_SYNTAX_ERROR);

                return (NSCD_SUCCESS);
        }

        off = offsetof(nscd_cfg_global_log_t, logfile);
        if (pdesc->p_offset == off) {
                if (data != NULL)
                        return (_nscd_set_lf((char *)data));
                else
                        return (NSCD_SUCCESS);
        }

        return (NSCD_CFG_PARAM_DESC_ERROR);
}

/* ARGSUSED */
nscd_rc_t
_nscd_cfg_log_get_stat(
        void                            **stat,
        struct nscd_cfg_stat_desc       *sdesc,
        nscd_cfg_id_t                   *nswdb,
        nscd_cfg_flag_t                 *dflag,
        void                            (**free_stat)(void *stat),
        nscd_cfg_error_t                **errorp)
{

        *(nscd_cfg_stat_global_log_t **)stat = &logstats;

        /* indicate the statistics are static, i.e., do not free */
        *dflag = _nscd_cfg_flag_set(*dflag, NSCD_CFG_DFLAG_STATIC_DATA);

        return (NSCD_SUCCESS);
}

/*
 * set the name of the current log file and make it current.
 */
nscd_rc_t
_nscd_set_log_file(
        char                    *name)
{
        nscd_rc_t               rc;
        nscd_cfg_handle_t       *h;

        rc = _nscd_cfg_get_handle("logfile", NULL, &h, NULL);
        if (rc != NSCD_SUCCESS)
                return (rc);

        rc = _nscd_cfg_set(h, name, NULL);
        _nscd_cfg_free_handle(h);
        if (rc != NSCD_SUCCESS)
                exit(rc);

        return (NSCD_SUCCESS);
}

/* Set debug level to the new one and make it current */
nscd_rc_t
_nscd_set_debug_level(
        int                     level)
{
        nscd_rc_t               rc;
        nscd_cfg_handle_t       *h;
        int                     l = 0;
        int                     c = -1;

        /* old nscd debug level is 1 to 10, map it to log_level and log_comp */
        if (level >= 0 && level <= 10) {
                l = debug_to_log_level(level);
                c = NSCD_LOG_CACHE;
        } else
                l = level;

        if (level < 0)
                c = -1 * level / 1000000;

        if (c != -1) {
                rc = _nscd_cfg_get_handle("debug-components", NULL, &h, NULL);
                if (rc != NSCD_SUCCESS)
                        return (rc);

                rc = _nscd_cfg_set(h, &c, NULL);
                _nscd_cfg_free_handle(h);
                if (rc != NSCD_SUCCESS)
                        exit(rc);
        }

        rc = _nscd_cfg_get_handle("debug-level", NULL, &h, NULL);
        if (rc != NSCD_SUCCESS)
                return (rc);

        if (level < 0)
                l = -1 * level % 1000000;

        rc = _nscd_cfg_set(h, &l, NULL);
        _nscd_cfg_free_handle(h);
        if (rc != NSCD_SUCCESS)
                exit(rc);

        return (NSCD_SUCCESS);
}

void
_nscd_get_log_info(
        char    *level,
        int     llen,
        char    *file,
        int     flen)
{
        if (_nscd_log_level != 0)
                (void) snprintf(level, llen, "%d", _nscd_log_level);
        if (*_nscd_logfile != '\0')
                (void) strlcpy(file, _nscd_logfile, flen);
}