/* A testing tool that just emits a number of
 * messages to the system log socket.
 *
 * Options
 *
 * -s severity (0..7 according to syslog spec, r "rolling", default 6)
 * -m number of messages to generate (default 500)
 * -C liblognorm-stdlog channel description
 * -f message format to use
 * -u Unix datagram socket path
 * -L generated message length for Unix datagram mode
 * -n send a short Unix datagram message with an embedded NUL byte
 * -w milliseconds to wait after sending messages
 *
 * Part of the testbench for rsyslog.
 *
 * Copyright 2010-2018 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#if defined(_AIX)
    #include <unistd.h>
#else
    #include <getopt.h>
#endif
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <syslog.h>
#include <unistd.h>
#ifdef HAVE_LIBLOGGING_STDLOG
    #include <liblogging/stdlog.h>
#endif

static enum { FMT_NATIVE, FMT_SYSLOG_INJECT_L, FMT_SYSLOG_INJECT_C } fmt = FMT_NATIVE;

static void usage(void) {
    fprintf(stderr, "usage: syslog_caller [-m messages] [-s severity] [-u socket] [-L length] [-w milliseconds]\n");
    exit(1);
}

/*
 * Generate a raw syslog message with a priority prefix in `buf`.
 *
 * Parameters:
 * - buf: destination buffer to receive the generated message.
 * - buflen: size of `buf` in bytes.
 * - sev: severity value; only the low syslog severity range is used via (sev % 8).
 * - iRun: message index used in the formatted test message variant.
 * - msgLen: if greater than zero, generate a fixed-length payload (after "<PRI>")
 *           filled with 'A' bytes and truncated to fit `buf`; if zero, generate
 *           a formatted test message including run number and severity.
 *
 * Returns the generated message length in bytes.
 */
static size_t genRawMsg(
    char *buf, const size_t buflen, const int sev, const int iRun, const size_t msgLen, const int embeddedNul) {
    if (buflen == 0) return 0;
    buf[0] = '\0';

    if (embeddedNul) {
        const int n = snprintf(buf, buflen, "<%d>", sev % 8);
        buf[buflen - 1] = '\0';
        if (n < 0) return 0;
        const size_t prefixLen = (size_t)n;
        if (prefixLen >= buflen || buflen - prefixLen < 4) return prefixLen < buflen ? prefixLen : buflen - 1;
        buf[prefixLen] = 'a';
        buf[prefixLen + 1] = '\0';
        buf[prefixLen + 2] = 'b';
        return prefixLen + 3;
    }

    if (msgLen > 0) {
        const int n = snprintf(buf, buflen, "<%d>", sev % 8);
        buf[buflen - 1] = '\0';
        if (n < 0) return 0;
        const size_t prefixLen = (size_t)n;
        if (prefixLen >= buflen) return buflen - 1;
        const size_t avail = buflen - prefixLen - 1;
        const size_t len = msgLen < avail ? msgLen : avail;
        memset(buf + prefixLen, 'A', len);
        buf[prefixLen + len] = '\0';
        return prefixLen + len;
    } else {
        const int n = snprintf(buf, buflen, "<%d>test message nbr %d, severity=%d", sev % 8, iRun, sev % 8);
        buf[buflen - 1] = '\0';
        if (n < 0) return 0;
        return (size_t)n < buflen ? (size_t)n : buflen - 1;
    }
}

/* Send one raw syslog message to a Unix domain datagram socket.
 * sockName: path to the destination Unix socket.
 * sev: syslog severity value used when generating the PRI part.
 * iRun: message sequence number used by the generated text format.
 * msgLen: payload length for raw/fill mode; 0 uses formatted text mode.
 */
static void sendRawUnix(
    const char *sockName, const int sev, const int iRun, const size_t msgLen, const int embeddedNul) {
    int sock;
    struct sockaddr_un sa;
    char msgbuf[4096];
    size_t lenMsg;

    if (sockName == NULL || strlen(sockName) >= sizeof(sa.sun_path)) {
        fprintf(stderr, "Unix socket path invalid or too long: %s\n", sockName ? sockName : "(null)");
        exit(1);
    }
    if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        perror("socket");
        exit(1);
    }
    memset(&sa, 0, sizeof(sa));
    sa.sun_family = AF_UNIX;
    strncpy(sa.sun_path, sockName, sizeof(sa.sun_path) - 1);
    sa.sun_path[sizeof(sa.sun_path) - 1] = '\0';
    lenMsg = genRawMsg(msgbuf, sizeof(msgbuf), sev, iRun, msgLen, embeddedNul);
    if (sendto(sock, msgbuf, lenMsg, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        perror("sendto");
        close(sock);
        exit(1);
    }
    close(sock);
}


#ifdef HAVE_LIBLOGGING_STDLOG
/* buffer must be large "enough" [4K?] */
static void genMsg(char *buf, const int sev, const int iRun) {
    switch (fmt) {
        case FMT_NATIVE:
            snprintf(buf, 4096, "test message nbr %d, severity=%d", iRun, sev);
            break;
        case FMT_SYSLOG_INJECT_L:
            snprintf(buf, 4096, "%s", "test\n");
            break;
        case FMT_SYSLOG_INJECT_C:
            snprintf(buf, 4096, "%s", "test 1\t2");
            break;
    }
}
#endif

int main(int argc, char *argv[]) {
    int i;
    int opt;
    int bRollingSev = 0;
    int sev = 6;
    int msgs = 500;
    const char *unixSockName = NULL;
    size_t msgLen = 0;
    int waitMs = 0;
    int embeddedNul = 0;
#ifdef HAVE_LIBLOGGING_STDLOG
    stdlog_channel_t logchan = NULL;
    const char *chandesc = "syslog:";
    char msgbuf[4096];
#endif

#ifdef HAVE_LIBLOGGING_STDLOG
    stdlog_init(STDLOG_USE_DFLT_OPTS);
    while ((opt = getopt(argc, argv, "m:s:C:f:u:L:nw:")) != -1) {
#else
    while ((opt = getopt(argc, argv, "m:s:u:L:nw:")) != -1) {
#endif
        switch (opt) {
            case 's':
                if (*optarg == 'r') {
                    bRollingSev = 1;
                    sev = 0;
                } else
#ifdef HAVE_LIBLOGGING_STDLOG
                    sev = atoi(optarg) % 8;
#else
                    sev = atoi(optarg);
#endif
                break;
            case 'm':
                msgs = atoi(optarg);
                break;
            case 'u':
                unixSockName = optarg;
                break;
            case 'L':
                msgLen = (size_t)atoi(optarg);
                if (msgLen >= 4096) msgLen = 4095;
                break;
            case 'w':
                waitMs = atoi(optarg);
                break;
            case 'n':
                embeddedNul = 1;
                break;
#ifdef HAVE_LIBLOGGING_STDLOG
            case 'C':
                chandesc = optarg;
                break;
            case 'f':
                if (!strcmp(optarg, "syslog_inject-l"))
                    fmt = FMT_SYSLOG_INJECT_L;
                else if (!strcmp(optarg, "syslog_inject-c"))
                    fmt = FMT_SYSLOG_INJECT_C;
                else
                    usage();
                break;
#endif
            default:
                usage();
                break;
        }
    }

    if (unixSockName != NULL) {
        for (i = 0; i < msgs; ++i) {
            sendRawUnix(unixSockName, sev, i, msgLen, embeddedNul);
            if (bRollingSev) sev++;
        }
        if (waitMs > 0) usleep((unsigned int)waitMs * 1000U);
        return 0;
    }

#ifdef HAVE_LIBLOGGING_STDLOG
    if ((logchan = stdlog_open(argv[0], 0, STDLOG_LOCAL1, chandesc)) == NULL) {
        fprintf(stderr, "error opening logchannel '%s': %s\n", chandesc, strerror(errno));
        exit(1);
    }
#endif
    for (i = 0; i < msgs; ++i) {
#ifdef HAVE_LIBLOGGING_STDLOG
        genMsg(msgbuf, sev, i);
        if (stdlog_log(logchan, sev, "%s", msgbuf) != 0) {
            perror("error writing log record");
            exit(1);
        }
#else
        syslog(sev % 8, "test message nbr %d, severity=%d", i, sev % 8);
#endif
        if (bRollingSev)
#ifdef HAVE_LIBLOGGING_STDLOG
            sev = (sev + 1) % 8;
#else
            sev++;
#endif
    }
    return (0);
}
