/**
 * The rfc3195 input module.
 *
 * Please note that this file replaces the rfc3195d daemon that was
 * also present in pre-v3 versions of rsyslog.
 *
 * WARNING: due to no demand at all for RFC3195, we have converted rfc3195d
 * to this input module, but we have NOT conducted any testing. Also,
 * the module does not yet properly handle the recovery case. If someone
 * intends to put this module into production, good testing should be
 * made and it also is a good idea to notify me that you intend to use
 * it in production. In this case, I'll probably give the module another
 * cleanup. I don't do this now because so far it looks just like a big
 * waste of time. -- rgerhards, 2008-04-16
 *
 * \author  Rainer Gerhards <rgerhards@adiscon.com>
 *
 * Copyright (C) 2003-2018 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>
#include <string.h>
#include <unistd.h>
#include <sys/errno.h>
#include <assert.h>
#include "rsyslog.h"
#include "dirty.h"
#include "liblogging/liblogging.h"
#include "liblogging/srAPI.h"
#include "liblogging/syslogmessage.h"
#include "module-template.h"
#include "cfsysline.h"
#include "msg.h"
#include "errmsg.h"
#include "glbl.h"
#include "rsconf.h"
#include "unicode-helper.h"

MODULE_TYPE_INPUT;
MODULE_TYPE_NOKEEP;

/* Module static data */
DEF_IMOD_STATIC_DATA;
DEFobjCurrIf(glbl) DEFobjCurrIf(prop)

    /* configuration settings */

    struct modConfData_s {
    EMPTY_STRUCT;
};

static int listenPort = 601;

/* we use a global API object below, because this listener is
 * not very complex. As such, this hack should not harm anything.
 * rgerhards, 2005-10-12
 */
static srAPIObj *pAPI;

static prop_t *pInputName = NULL;
/* there is only one global inputName for all messages generated by this module */


static void cleanupAPI(const int canUseExitLib) {
    if (pAPI == NULL) {
        return;
    }

    if (canUseExitLib) {
        srAPIExitLib(pAPI);
    } else {
        /*
         * liblogging may already have freed transient listener state on some
         * setup failures, but it provides no public way to clear that state
         * before srAPIExitLib() runs its destructor path.
         */
        free(pAPI);
    }
    pAPI = NULL;
}


static size_t getBoundedRawMsgLen(const uchar *const pszRawMsg) {
    const int maxLine = glbl.GetMaxLine(runConf);
    /*
     * liblogging exposes the raw RFC3195 message as a read-only string and
     * does not provide a public length accessor, so keep the scan bounded.
     */
    size_t msgLen = strnlen((const char *)pszRawMsg, (size_t)maxLine + 1);

    if (msgLen > (size_t)maxLine) {
        LogError(0, NO_ERRCODE, "im3195: received RFC3195 payload longer than maxMessageSize (%d), truncating",
                 maxLine);
        msgLen = (size_t)maxLine;
    }

    return msgLen;
}


/* This method is called when a message has been fully received.
 * It passes the received message to the rsyslog main message
 * queue. Please note that this callback is synchronous, thus
 * liblogging will be on hold until it returns. This is important
 * to note because in an error case we might stay in this code
 * for an extended amount of time. So far, we think this is the
 * best solution, but real-world experience might tell us a
 * different truth ;)
 */
#ifdef __GNUC__
    #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
static void OnReceive(srAPIObj __attribute__((unused)) * pMyAPI, srSLMGObj *pSLMG) {
    uchar *pszRawMsg = NULL;
    uchar *fromHost = (uchar *)"[unset]";
    uchar *fromHostIP = (uchar *)"[unset]";
    char *remoteHost = NULL;
    char *hostname = NULL;
    size_t msgLen;
    srRetVal libRet;
    rsRetVal submitRet;

    libRet = srSLMGGetRawMSG(pSLMG, &pszRawMsg);
    if (libRet != SR_RET_OK || pszRawMsg == NULL) {
        LogError(0, NO_ERRCODE, "im3195: liblogging did not provide a raw message, dropping frame");
        return;
    }

    if (srSLMGGetRemoteHost(pSLMG, &remoteHost) == SR_RET_OK && remoteHost != NULL && remoteHost[0] != '\0') {
        fromHostIP = (uchar *)remoteHost;
        fromHost = (uchar *)remoteHost;
    }

    if (srSLMGGetHostname(pSLMG, &hostname) == SR_RET_OK && hostname != NULL && hostname[0] != '\0') {
        fromHost = (uchar *)hostname;
    }

    msgLen = getBoundedRawMsgLen(pszRawMsg);

    submitRet = parseAndSubmitMessage(fromHost, fromHostIP, pszRawMsg, (int)msgLen, PARSE_HOSTNAME, eFLOWCTL_FULL_DELAY,
                                      pInputName, NULL, 0, NULL);
    if (submitRet != RS_RET_OK) {
        LogError(0, NO_ERRCODE, "im3195: error %d submitting received message", submitRet);
    }
}


#if 0
BEGINbeginCnfLoad
CODESTARTbeginCnfLoad;
ENDbeginCnfLoad


BEGINendCnfLoad
CODESTARTendCnfLoad;
ENDendCnfLoad


BEGINcheckCnf
CODESTARTcheckCnf;
ENDcheckCnf


BEGINactivateCnf
CODESTARTactivateCnf;
ENDactivateCnf


BEGINfreeCnf
CODESTARTfreeCnf;
ENDfreeCnf
#endif


BEGINrunInput
    CODESTARTrunInput;
    /* this is an endless loop - it is terminated when the thread is
     * signalled to do so. This, however, is handled by the framework,
     * right into the sleep below.
     */
    while (!pThrd->bShallStop) {
        /* now move the listener to running state. Control will only
         * return after SIGUSR1.
         */
        if ((iRet = (rsRetVal)srAPIRunListener(pAPI)) != RS_RET_OK) {
            LogError(0, NO_ERRCODE,
                     "error %d running liblogging listener - im3195 "
                     "is defunct",
                     iRet);
            FINALIZE; /* this causes im3195 to become defunct; TODO: recovery handling */
        }
    }
finalize_it:
ENDrunInput


BEGINwillRun
    CODESTARTwillRun;
    if ((pAPI = srAPIInitLib()) == NULL) {
        LogError(0, NO_ERRCODE, "error initializing liblogging - im3195 is defunct");
        ABORT_FINALIZE(RS_RET_ERR);
    }

    if ((iRet = (rsRetVal)srAPISetOption(pAPI, srOPTION_BEEP_LISTENPORT, listenPort)) != RS_RET_OK) {
        LogError(0, NO_ERRCODE, "error %d setting liblogging listen port - im3195 is defunct", iRet);
        cleanupAPI(0);
        FINALIZE;
    }

    if ((iRet = (rsRetVal)srAPISetupListener(pAPI, OnReceive)) != RS_RET_OK) {
        LogError(0, NO_ERRCODE, "error %d setting up liblogging listener - im3195 is defunct", iRet);
        cleanupAPI(0);
        FINALIZE;
    }

finalize_it:
ENDwillRun


BEGINafterRun
    CODESTARTafterRun;
    dbgprintf("Shutting down rfc3195d. Be patient, this can take up to 30 seconds...\n");
    if (pAPI != NULL) {
        srAPIShutdownListener(pAPI);
    }
ENDafterRun


BEGINmodExit
    CODESTARTmodExit;
    cleanupAPI(0); /* terminate liblogging */
    /* global variable cleanup */
    if (pInputName != NULL) prop.Destruct(&pInputName);
    /* release objects we used */
    objRelease(glbl, CORE_COMPONENT);
    objRelease(prop, CORE_COMPONENT);
ENDmodExit


BEGINqueryEtryPt
    CODESTARTqueryEtryPt;
    CODEqueryEtryPt_STD_IMOD_QUERIES;
ENDqueryEtryPt

static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal) {
    listenPort = 601;
    return RS_RET_OK;
}


BEGINmodInit()
    CODESTARTmodInit;
    *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
    CODEmodInit_QueryRegCFSLineHdlr CHKiRet(objUse(glbl, CORE_COMPONENT));
    CHKiRet(objUse(prop, CORE_COMPONENT));

    CHKiRet(
        omsdRegCFSLineHdlr((uchar *)"input3195listenport", 0, eCmdHdlrInt, NULL, &listenPort, STD_LOADABLE_MODULE_ID));
    CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL,
                               STD_LOADABLE_MODULE_ID));

    CHKiRet(prop.Construct(&pInputName));
    CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("im3195"), sizeof("im3195") - 1));
    CHKiRet(prop.ConstructFinalize(pInputName));

ENDmodInit
