/* omuxsock.c
 * This is the implementation of datgram unix domain socket forwarding.
 *
 * NOTE: read comments in module-template.h to understand how this file
 *       works!
 *
 * Copyright 2010-2016 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 "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include "conf.h"
#include "srUtils.h"
#include "template.h"
#include "msg.h"
#include "cfsysline.h"
#include "module-template.h"
#include "glbl.h"
#include "errmsg.h"
#include "unicode-helper.h"

MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("omuxsock")

/* internal structures
 */
DEF_OMOD_STATIC_DATA;
DEFobjCurrIf(glbl)
#define INVLD_SOCK -1

    typedef struct _instanceData {
    permittedPeers_t *pPermPeers;
    uchar *sockName;
    int sock;
    struct sockaddr_un addr;
} instanceData;


typedef struct wrkrInstanceData {
    instanceData *pData;
} wrkrInstanceData_t;

/* config data */
typedef struct configSettings_s {
    uchar *tplName; /* name of the default template to use */
    uchar *sockName; /* name of the default template to use */
} configSettings_t;
static configSettings_t cs;

/* module-global parameters */
static struct cnfparamdescr modpdescr[] = {
    {"template", eCmdHdlrGetWord, 0},
};
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr};

struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
    uchar *tplName; /* default template */
};

static modConfData_t *loadModConf = NULL; /* modConf ptr to use for the current load process */
static modConfData_t *runModConf = NULL; /* modConf ptr to use for the current exec process */


static pthread_mutex_t mutDoAct = PTHREAD_MUTEX_INITIALIZER;

BEGINinitConfVars /* (re)set config variables to default values */
    CODESTARTinitConfVars;
    cs.tplName = NULL;
    cs.sockName = NULL;
ENDinitConfVars


static rsRetVal doTryResume(instanceData *pData);


/* this function gets the default template. It coordinates action between
 * old-style and new-style configuration parts.
 */
static uchar *getDfltTpl(void) {
    if (loadModConf != NULL && loadModConf->tplName != NULL)
        return loadModConf->tplName;
    else if (cs.tplName == NULL)
        return (uchar *)"RSYSLOG_TraditionalForwardFormat";
    else
        return cs.tplName;
}

/* set the default template to be used
 * This is a module-global parameter, and as such needs special handling. It needs to
 * be coordinated with values set via the v2 config system (rsyslog v6+). What we do
 * is we do not permit this directive after the v2 config system has been used to set
 * the parameter.
 */
static rsRetVal setLegacyDfltTpl(void __attribute__((unused)) * pVal, uchar *newVal) {
    DEFiRet;

    if (loadModConf != NULL && loadModConf->tplName != NULL) {
        free(newVal);
        LogError(0, RS_RET_ERR,
                 "omuxsock default template already set via module "
                 "global parameter - can no longer be changed");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    free(cs.tplName);
    cs.tplName = newVal;
finalize_it:
    RETiRet;
}


static rsRetVal closeSocket(instanceData *pData) {
    DEFiRet;
    if (pData->sock != INVLD_SOCK) {
        close(pData->sock);
        pData->sock = INVLD_SOCK;
    }
    RETiRet;
}


BEGINbeginCnfLoad
    CODESTARTbeginCnfLoad;
    loadModConf = pModConf;
    pModConf->pConf = pConf;
    pModConf->tplName = NULL;
ENDbeginCnfLoad

BEGINsetModCnf
    struct cnfparamvals *pvals = NULL;
    int i;
    CODESTARTsetModCnf;
    pvals = nvlstGetParams(lst, &modpblk, NULL);
    if (pvals == NULL) {
        LogError(0, RS_RET_MISSING_CNFPARAMS,
                 "error processing module "
                 "config parameters [module(...)]");
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    if (Debug) {
        dbgprintf("module (global) param blk for omuxsock:\n");
        cnfparamsPrint(&modpblk, pvals);
    }

    for (i = 0; i < modpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(modpblk.descr[i].name, "template")) {
            loadModConf->tplName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
            if (cs.tplName != NULL) {
                LogError(0, RS_RET_DUP_PARAM,
                         "omuxsock: default template "
                         "was already set via legacy directive - may lead to inconsistent "
                         "results.");
            }
        } else {
            dbgprintf(
                "omuxsock: program error, non-handled "
                "param '%s' in beginCnfLoad\n",
                modpblk.descr[i].name);
        }
    }
finalize_it:
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf

BEGINendCnfLoad
    CODESTARTendCnfLoad;
    loadModConf = NULL; /* done loading */
    /* free legacy config vars */
    free(cs.tplName);
    cs.tplName = NULL;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
    runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
    free(pModConf->tplName);
ENDfreeCnf

BEGINcreateInstance
    CODESTARTcreateInstance;
    pData->sock = INVLD_SOCK;
ENDcreateInstance

BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
ENDcreateWrkrInstance


BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
    if (eFeat == sFEATURERepeatedMsgReduction) iRet = RS_RET_OK;
ENDisCompatibleWithFeature


BEGINfreeInstance
    CODESTARTfreeInstance;
    /* final cleanup */
    closeSocket(pData);
    free(pData->sockName);
ENDfreeInstance

BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
ENDfreeWrkrInstance


BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
    DBGPRINTF("%s", pData->sockName);
ENDdbgPrintInstInfo


/* Send a message via UDP
 * rgehards, 2007-12-20
 */
static rsRetVal sendMsg(instanceData *pData, char *msg, size_t len) {
    DEFiRet;
    unsigned lenSent = 0;

    if (pData->sock == INVLD_SOCK) {
        CHKiRet(doTryResume(pData));
    }

    if (pData->sock != INVLD_SOCK) {
        lenSent = sendto(pData->sock, msg, len, 0, (const struct sockaddr *)&pData->addr, sizeof(pData->addr));
        if (lenSent != len) {
            int eno = errno;
            char errStr[1024];
            DBGPRINTF("omuxsock suspending: sendto(), socket %d, error: %d = %s.\n", pData->sock, eno,
                      rs_strerror_r(eno, errStr, sizeof(errStr)));
        }
    }

finalize_it:
    RETiRet;
}


/* open socket to remote system
 */
static rsRetVal openSocket(instanceData *pData) {
    DEFiRet;
    assert(pData->sock == INVLD_SOCK);

    if ((pData->sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        char errStr[1024];
        int eno = errno;
        DBGPRINTF("error %d creating AF_UNIX/SOCK_DGRAM: %s.\n", eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
        pData->sock = INVLD_SOCK;
        ABORT_FINALIZE(RS_RET_NO_SOCKET);
    }

    /* set up server address structure */
    memset(&pData->addr, 0, sizeof(pData->addr));
    pData->addr.sun_family = AF_UNIX;
    strncpy(pData->addr.sun_path, (char *)pData->sockName, sizeof(pData->addr.sun_path));
    pData->addr.sun_path[sizeof(pData->addr.sun_path) - 1] = '\0';

finalize_it:
    if (iRet != RS_RET_OK) {
        closeSocket(pData);
    }
    RETiRet;
}


/* try to resume connection if it is not ready
 */
static rsRetVal doTryResume(instanceData *pData) {
    DEFiRet;

    DBGPRINTF("omuxsock trying to resume\n");
    closeSocket(pData);
    iRet = openSocket(pData);

    if (iRet != RS_RET_OK) {
        iRet = RS_RET_SUSPENDED;
    }

    RETiRet;
}


BEGINtryResume
    CODESTARTtryResume;
    iRet = doTryResume(pWrkrData->pData);
ENDtryResume

BEGINdoAction
    char *psz = NULL; /* temporary buffering */
    register unsigned l;
    int iMaxLine;
    CODESTARTdoAction;
    pthread_mutex_lock(&mutDoAct);
    CHKiRet(doTryResume(pWrkrData->pData));

    iMaxLine = glbl.GetMaxLine(runModConf->pConf);

    DBGPRINTF(" omuxsock:%s\n", pWrkrData->pData->sockName);

    psz = (char *)ppString[0];
    l = strlen((char *)psz);
    if ((int)l > iMaxLine) l = iMaxLine;

    CHKiRet(sendMsg(pWrkrData->pData, psz, l));

finalize_it:
    pthread_mutex_unlock(&mutDoAct);
ENDdoAction


BEGINparseSelectorAct
    CODESTARTparseSelectorAct;
    CODE_STD_STRING_REQUESTparseSelectorAct(1)

        /* first check if this config line is actually for us */
        if (strncmp((char *)p, ":omuxsock:", sizeof(":omuxsock:") - 1)) {
        ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
    }

    /* ok, if we reach this point, we have something for us */
    p += sizeof(":omuxsock:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
    CHKiRet(createInstance(&pData));

    /* check if a non-standard template is to be applied */
    if (*(p - 1) == ';') --p;
    CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, getDfltTpl()));

    if (cs.sockName == NULL) {
        LogError(0, RS_RET_NO_SOCK_CONFIGURED, "No output socket configured for omuxsock\n");
        ABORT_FINALIZE(RS_RET_NO_SOCK_CONFIGURED);
    }

    pData->sockName = cs.sockName;
    cs.sockName = NULL; /* pData is now owner and will fee it */

    CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


/* a common function to free our configuration variables - used both on exit
 * and on $ResetConfig processing. -- rgerhards, 2008-05-16
 */
static void freeConfigVars(void) {
    free(cs.tplName);
    cs.tplName = NULL;
    free(cs.sockName);
    cs.sockName = NULL;
}


BEGINmodExit
    CODESTARTmodExit;
    /* release what we no longer need */
    objRelease(glbl, CORE_COMPONENT);

    freeConfigVars();
ENDmodExit


BEGINqueryEtryPt
    CODESTARTqueryEtryPt;
    CODEqueryEtryPt_STD_OMOD_QUERIES;
    CODEqueryEtryPt_STD_OMOD8_QUERIES;
    CODEqueryEtryPt_STD_CONF2_QUERIES;
    CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES;
ENDqueryEtryPt


/* Reset config variables for this module to default values.
 * rgerhards, 2008-03-28
 */
static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal) {
    freeConfigVars();
    return RS_RET_OK;
}


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

    CHKiRet(regCfSysLineHdlr((uchar *)"omuxsockdefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl, NULL, NULL));
    CHKiRet(regCfSysLineHdlr((uchar *)"omuxsocksocket", 0, eCmdHdlrGetWord, NULL, &cs.sockName, NULL));
    CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL,
                               STD_LOADABLE_MODULE_ID));
ENDmodInit

/* vim:set ai:
 */
