root/usr.bin/rdist/message.c
/*      $OpenBSD: message.c,v 1.30 2021/06/22 20:19:28 jmc Exp $        */

/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <errno.h>
#include <limits.h>
#include <paths.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "defs.h"

/*
 * Message handling functions for both rdist and rdistd.
 */


#define MSGBUFSIZ       32*1024

int                     debug = 0;              /* Debugging level */
int                     nerrs = 0;              /* Number of errors */

/*
 * Message Types
 */
struct msgtype {
        int             mt_type;                /* Type (bit) */
        char           *mt_name;                /* Name of message type */
} msgtypes[] = {
        { MT_CHANGE,    "change" },
        { MT_INFO,      "info" },
        { MT_NOTICE,    "notice" },
        { MT_NERROR,    "nerror" },
        { MT_FERROR,    "ferror" },
        { MT_WARNING,   "warning" },
        { MT_VERBOSE,   "verbose" },
        { MT_ALL,       "all" },
        { MT_DEBUG,     "debug" },
        { 0 },
};

/*
 * Description of message facilities
 */
struct msgfacility {
        /* compile time initialized data */
        int             mf_msgfac;              /* One of MF_* from below */
        char           *mf_name;                /* Name of this facility */
        void          (*mf_sendfunc)            /* Function to send msg */
                        (struct msgfacility *, int, int, char *);
        /* run time initialized data */
        int             mf_msgtypes;            /* Bitmask of MT_* from above*/
        char           *mf_filename;            /* Name of file */
        FILE           *mf_fptr;                /* File pointer to output to */
};

/*
 * Message Facilities
 */
#define MF_STDOUT       1                       /* Standard Output */
#define MF_NOTIFY       2                       /* Notify mail service */
#define MF_FILE         3                       /* A normal file */
#define MF_SYSLOG       4                       /* syslog() */

static void msgsendstdout(struct msgfacility *, int, int, char *);
static void msgsendsyslog(struct msgfacility *, int, int, char *);
static void msgsendfile(struct msgfacility *, int, int, char *);
static void msgsendnotify(struct msgfacility *, int, int, char *);

/*
 * Message Facilities
 */
struct msgfacility msgfacility[] = {
        { MF_STDOUT,    "stdout",       msgsendstdout },
        { MF_FILE,      "file",         msgsendfile },
        { MF_SYSLOG,    "syslog",       msgsendsyslog },
        { MF_NOTIFY,    "notify",       msgsendnotify },
        { 0 },
};

static struct msgfacility *getmsgfac(char *);
static struct msgtype *getmsgtype(char *);
static char *setmsgtypes(struct msgfacility *, char *);
static void _message(int, char *);
static void _debugmsg(int, char *);
static void _error(const char *);
static void _fatalerr(const char *);

/*
 * Print enabled message logging info
 */
void
msgprconfig(void)
{
        int i, x;
        static char buf[MSGBUFSIZ];

        debugmsg(DM_MISC, "Current message logging config:");
        for (i = 0; msgfacility[i].mf_name; ++i) {
                (void) snprintf(buf, sizeof(buf), "    %.*s=", 
                               (int)(sizeof(buf) - 7), msgfacility[i].mf_name);
                for (x = 0; msgtypes[x].mt_name; ++x)
                        if (IS_ON(msgfacility[i].mf_msgtypes, 
                                  msgtypes[x].mt_type)) {
                                if (x > 0)
                                        (void) strlcat(buf, ",", sizeof(buf));
                                (void) strlcat(buf, msgtypes[x].mt_name,
                                    sizeof(buf));
                        }
                debugmsg(DM_MISC, "%s", buf);
        }

}

/*
 * Get the Message Facility entry "name"
 */
static struct msgfacility *
getmsgfac(char *name)
{
        int i;

        for (i = 0; msgfacility[i].mf_name; ++i)
                if (strcasecmp(name, msgfacility[i].mf_name) == 0)
                        return(&msgfacility[i]);

        return(NULL);
}

/*
 * Get the Message Type entry named "name"
 */
static struct msgtype *
getmsgtype(char *name)
{
        int i;

        for (i = 0; msgtypes[i].mt_name; ++i)
                if (strcasecmp(name, msgtypes[i].mt_name) == 0)
                        return(&msgtypes[i]);

        return(NULL);
}

/*
 * Set Message Type information for Message Facility "msgfac" as
 * indicated by string "str".
 */
static char *
setmsgtypes(struct msgfacility *msgfac, char *str)
{
        static char ebuf[BUFSIZ];
        char *cp;
        char *strptr, *word;
        struct msgtype *mtp;

        /*
         * MF_SYSLOG is the only supported message facility for the server
         */
        if (isserver && (msgfac->mf_msgfac != MF_SYSLOG && 
                         msgfac->mf_msgfac != MF_FILE)) {
                (void) snprintf(ebuf, sizeof(ebuf),
                "The \"%.*s\" message facility cannot be used by the server.",
                                100, msgfac->mf_name);
                return(ebuf);
        }

        strptr = str;

        /*
         * Do any necessary Message Facility preparation
         */
        switch(msgfac->mf_msgfac) {
        case MF_FILE:
                /*
                 * The MF_FILE string should look like "<file>=<types>".
                 */
                if ((cp = strchr(strptr, '=')) == NULL)
                        return(
                           "No file name found for \"file\" message facility");
                *cp++ = CNULL;

                if ((msgfac->mf_fptr = fopen(strptr, "w")) == NULL)
                        fatalerr("Cannot open log file for writing: %s: %s.",
                                 strptr, SYSERR);
                msgfac->mf_filename = xstrdup(strptr);

                strptr = cp;
                break;

        case MF_NOTIFY:
                break;

        case MF_STDOUT:
                msgfac->mf_fptr = stdout;
                break;

        case MF_SYSLOG:
                openlog(progname, LOG_PID, LOG_DAEMON);
                break;
        }

        /*
         * Parse each type word
         */
        msgfac->mf_msgtypes = 0;        /* Start from scratch */
        while (strptr) {
                word = strptr;
                if ((cp = strchr(strptr, ',')) != NULL)
                        *cp++ = CNULL;
                strptr = cp;

                if ((mtp = getmsgtype(word)) != NULL) {
                        msgfac->mf_msgtypes |= mtp->mt_type;
                        /*
                         * XXX This is really a kludge until we add real
                         * control over debugging.
                         */
                        if (!debug && isserver && 
                            strcasecmp(word, "debug") == 0)
                                debug = DM_ALL;
                } else {
                        (void) snprintf(ebuf, sizeof(ebuf),
                                        "Message type \"%.*s\" is invalid.",
                                        100, word);
                        return(ebuf);
                }
        }

        return(NULL);
}

/*
 * Parse a message logging option string
 */
char *
msgparseopts(char *msgstr, int doset)
{
        static char ebuf[BUFSIZ], msgbuf[MSGBUFSIZ];
        char *cp, *optstr;
        char *word;
        struct msgfacility *msgfac;

        if (msgstr == NULL)
                return("NULL message string");

        /* strtok() is harmful */
        (void) strlcpy(msgbuf, msgstr, sizeof(msgbuf));

        /*
         * Each <facility>=<types> list is separated by ":".
         */
        for (optstr = strtok(msgbuf, ":"); optstr;
             optstr = strtok(NULL, ":")) {

                if ((cp = strchr(optstr, '=')) == NULL)
                        return("No '=' found");

                *cp++ = CNULL;
                word = optstr;
                if ((int)strlen(word) <= 0)
                        return("No message facility specified");
                if ((int)strlen(cp) <= 0)
                        return("No message type specified");

                if ((msgfac = getmsgfac(word)) == NULL) {
                        (void) snprintf(ebuf, sizeof(ebuf),
                                        "%.*s is not a valid message facility", 
                                        100, word);
                        return(ebuf);
                }
                
                if (doset) {
                        char *mcp;

                        if ((mcp = setmsgtypes(msgfac, cp)) != NULL)
                                return(mcp);
                }
        }

        if (isserver && debug) {
                debugmsg(DM_MISC, "%s", getversion());
                msgprconfig();
        }

        return(NULL);
}

/*
 * Send a message to facility "stdout".
 * For rdistd, this is really the rdist client.
 */
static void
msgsendstdout(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
{
        char cmd;

        if (isserver) {
                if (rem_w < 0 || IS_ON(flags, MT_NOREMOTE))
                        return;

                cmd = CNULL;

                switch(mtype) {
                case MT_NERROR:         cmd = C_ERRMSG;         break;
                case MT_FERROR:         cmd = C_FERRMSG;        break;
                case MT_NOTICE:         cmd = C_NOTEMSG;        break;
                case MT_REMOTE:         cmd = C_LOGMSG;         break;
                }

                if (cmd != CNULL)
                        (void) sendcmd(cmd, "%s", msgbuf);
        } else {
                switch(mtype) {
                case MT_FERROR:
                case MT_NERROR:
                        if (msgbuf && *msgbuf) {
                                (void) fprintf(stderr, "%s\n", msgbuf);
                                (void) fflush(stderr);
                        }
                        break;

                case MT_DEBUG:
                        /* 
                         * Only things that are strictly MT_DEBUG should
                         * be shown.
                         */
                        if (flags != MT_DEBUG)
                                return;
                case MT_NOTICE:
                case MT_CHANGE:
                case MT_INFO:
                case MT_VERBOSE:
                case MT_WARNING:
                        if (msgbuf && *msgbuf) {
                                (void) printf("%s\n", msgbuf);
                                (void) fflush(stdout);
                        }
                        break;
                }
        }
}

/*
 * Send a message to facility "syslog"
 */
static void
msgsendsyslog(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
{
        int syslvl = 0;

        if (!msgbuf || !*msgbuf)
                return;

        switch(mtype) {
#if     defined(SL_NERROR)
        case MT_NERROR:         syslvl = SL_NERROR;     break;
#endif
#if     defined(SL_FERROR)
        case MT_FERROR:         syslvl = SL_FERROR;     break;
#endif
#if     defined(SL_WARNING)
        case MT_WARNING:        syslvl = SL_WARNING;    break;
#endif
#if     defined(SL_CHANGE)
        case MT_CHANGE:         syslvl = SL_CHANGE;     break;
#endif
#if     defined(SL_INFO)
        case MT_SYSLOG:
        case MT_VERBOSE:
        case MT_INFO:           syslvl = SL_INFO;       break;
#endif
#if     defined(SL_NOTICE)
        case MT_NOTICE:         syslvl = SL_NOTICE;     break;
#endif
#if     defined(SL_DEBUG)
        case MT_DEBUG:          syslvl = SL_DEBUG;      break;
#endif
        }

        if (syslvl)
                syslog(syslvl, "%s", msgbuf);
}

/*
 * Send a message to a "file" facility.
 */
static void
msgsendfile(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
{
        if (msgfac->mf_fptr == NULL)
                return;

        if (!msgbuf || !*msgbuf)
                return;

        (void) fprintf(msgfac->mf_fptr, "%s\n", msgbuf);
        (void) fflush(msgfac->mf_fptr);
}

/*
 * Same method as msgsendfile()
 */
static void
msgsendnotify(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
{
        char *tempfile;

        if (IS_ON(flags, MT_DEBUG))
                return;

        if (!msgbuf || !*msgbuf)
                return;

        if (!msgfac->mf_fptr) {
                char *cp;
                int fd;
                size_t len;

                /*
                 * Create and open a new temporary file
                 */
                if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
                        cp = _PATH_TMP;
                len = strlen(cp) + 1 + sizeof(_RDIST_TMP);
                tempfile = xmalloc(len);
                (void) snprintf(tempfile, len, "%s/%s", cp, _RDIST_TMP);

                msgfac->mf_filename = tempfile;
                if ((fd = mkstemp(msgfac->mf_filename)) == -1 ||
                    (msgfac->mf_fptr = fdopen(fd, "w")) == NULL)
                    fatalerr("Cannot open notify file for writing: %s: %s.",
                        msgfac->mf_filename, SYSERR);
                debugmsg(DM_MISC, "Created notify temp file '%s'",
                         msgfac->mf_filename);
        }

        if (msgfac->mf_fptr == NULL)
                return;

        (void) fprintf(msgfac->mf_fptr, "%s\n", msgbuf);
        (void) fflush(msgfac->mf_fptr);
}

/*
 * Insure currenthost is set to something reasonable.
 */
void
checkhostname(void)
{
        static char mbuf[HOST_NAME_MAX+1];
        char *cp;

        if (!currenthost) {
                if (gethostname(mbuf, sizeof(mbuf)) == 0) {
                        if ((cp = strchr(mbuf, '.')) != NULL)
                                *cp = CNULL;
                        currenthost = xstrdup(mbuf);
                } else
                        currenthost = "(unknown)";
        }
}

/*
 * Print a message contained in "msgbuf" if a level "lvl" is set.
 */
static void
_message(int flags, char *msgbuf)
{
        int i, x;
        static char mbuf[2048];

        if (msgbuf && *msgbuf) {
                /*
                 * Ensure no stray newlines are present
                 */
                msgbuf[strcspn(msgbuf, "\n")] = CNULL;

                checkhostname();
                if (strncmp(currenthost, msgbuf, strlen(currenthost)) == 0)
                        (void) strlcpy(mbuf, msgbuf, sizeof(mbuf));
                else
                        (void) snprintf(mbuf, sizeof(mbuf), 
                                        "%s: %s", currenthost, msgbuf);
        } else
                mbuf[0] = '\0';

        /*
         * Special case for messages that only get
         * logged to the system log facility
         */
        if (IS_ON(flags, MT_SYSLOG)) {
                msgsendsyslog(NULL, MT_SYSLOG, flags, mbuf);
                return;
        }

        /*
         * Special cases
         */
        if (isserver && IS_ON(flags, MT_NOTICE)) {
                msgsendstdout(NULL, MT_NOTICE, flags, mbuf);
                return;
        } else if (isserver && IS_ON(flags, MT_REMOTE))
                msgsendstdout(NULL, MT_REMOTE, flags, mbuf);
        else if (isserver && IS_ON(flags, MT_NERROR))
                msgsendstdout(NULL, MT_NERROR, flags, mbuf);
        else if (isserver && IS_ON(flags, MT_FERROR))
                msgsendstdout(NULL, MT_FERROR, flags, mbuf);

        /*
         * For each Message Facility, check each Message Type to see
         * if the bits in "flags" are set.  If so, call the appropriate
         * Message Facility to dispatch the message.
         */
        for (i = 0; msgfacility[i].mf_name; ++i)
                for (x = 0; msgtypes[x].mt_name; ++x)
                        /* 
                         * XXX MT_ALL should not be used directly 
                         */
                        if (msgtypes[x].mt_type != MT_ALL &&
                            IS_ON(flags, msgtypes[x].mt_type) &&
                            IS_ON(msgfacility[i].mf_msgtypes,
                                  msgtypes[x].mt_type))
                                (*msgfacility[i].mf_sendfunc)(&msgfacility[i],
                                                           msgtypes[x].mt_type,
                                                              flags,
                                                              mbuf);
}

/*
 * Front-end to _message()
 */
void
message(int lvl, const char *fmt, ...)
{
        static char buf[MSGBUFSIZ];
        va_list args;

        if (fmt != NULL) {
                va_start(args, fmt);
                (void) vsnprintf(buf, sizeof(buf), fmt, args);
                va_end(args);
        }

        _message(lvl, fmt ? buf : NULL);
}

/*
 * Display a debugging message
 */
static void
_debugmsg(int lvl, char *buf)
{
        if (IS_ON(debug, lvl))
                _message(MT_DEBUG, buf);
}

/*
 * Front-end to _debugmsg()
 */
void
debugmsg(int lvl, const char *fmt, ...)
{
        static char buf[MSGBUFSIZ];
        va_list args;

        va_start(args, fmt);
        (void) vsnprintf(buf, sizeof(buf), fmt, args);
        va_end(args);

        _debugmsg(lvl, buf);
}

/*
 * Print an error message
 */
static void
_error(const char *msg)
{
        static char buf[MSGBUFSIZ];

        nerrs++;
        buf[0] = CNULL;

        if (msg) {
                if (isserver)
                        (void) snprintf(buf, sizeof(buf),
                                        "REMOTE ERROR: %s", msg);
                else
                        (void) snprintf(buf, sizeof(buf),
                                        "LOCAL ERROR: %s", msg);
        }

        _message(MT_NERROR, (buf[0]) ? buf : NULL);
}

/*
 * Frontend to _error()
 */
void
error(const char *fmt, ...)
{
        static char buf[MSGBUFSIZ];
        va_list args;

        buf[0] = CNULL;
        va_start(args, fmt);
        if (fmt)
                (void) vsnprintf(buf, sizeof(buf), fmt, args);
        va_end(args);

        _error((buf[0]) ? buf : NULL);
}

/*
 * Display a fatal message
 */
static void
_fatalerr(const char *msg)
{
        static char buf[MSGBUFSIZ];

        ++nerrs;

        if (isserver)
                (void) snprintf(buf, sizeof(buf), "REMOTE ERROR: %s", msg);
        else
                (void) snprintf(buf, sizeof(buf), "LOCAL ERROR: %s", msg);

        _message(MT_FERROR, buf);

        exit(nerrs);
}

/*
 * Front-end to _fatalerr()
 */
void
fatalerr(const char *fmt, ...)
{
        static char buf[MSGBUFSIZ];
        va_list args;

        va_start(args, fmt);
        (void) vsnprintf(buf, sizeof(buf), fmt, args);
        va_end(args);

        _fatalerr(buf);
}

/*
 * Get the name of the file used for notify.
 * A side effect is that the file pointer to the file
 * is closed.  We assume this function is only called when
 * we are ready to read the file.
 */
char *
getnotifyfile(void)
{
        int i;

        for (i = 0; msgfacility[i].mf_name; i++)
                if (msgfacility[i].mf_msgfac == MF_NOTIFY &&
                    msgfacility[i].mf_fptr) {
                        (void) fclose(msgfacility[i].mf_fptr);
                        msgfacility[i].mf_fptr = NULL;
                        return(msgfacility[i].mf_filename);
                }

        return(NULL);
}