/* Copyright 2019 Advens
 *
 * 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
 *
 * 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 <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "parserif.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <uuid/uuid.h>
#include <json.h>

#include "protocol.h" /* custom file written for Darwin */

#define JSON_DEFAULT_CONTAINER "!mmdarwin"
#define JSON_DARWIN_ID "darwin_id"
#define INVLD_SOCK -1
#define INITIAL_BUFFER_SIZE 32
#define BUFFER_DEFAULT_MAX_SIZE 65536

MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("mmdarwin")

DEFobjCurrIf(glbl) DEF_OMOD_STATIC_DATA;

typedef struct dyn_buffer_t {
    char *buffer;
    size_t bufferAllocSize;
    size_t bufferMsgSize;
    size_t bufferMaxSize;
} dyn_buffer;

/* config variables */
typedef struct _instanceData {
    char *pUUIDKey; /* the key to the UUID generated by an mmdarwin instance */
    char *pCertitudeKey; /* the key name to save in the enriched log
                line the certitude obtained from Darwin */
    uchar *pSockName; /* the socket path of the filter which will be used by
                 Darwin */
    unsigned long long int filterCode; /* the filter code associated to the filter which will be used
                          by Darwin */
    enum darwin_filter_response_type response; /* the type of response for Darwin: no / back / darwin / both */
    struct {
        int nmemb;
        char **name;
        char **varname;
    } fieldList; /* our keys (fields) to be extracted from the JSON-parsed log line */
    unsigned int socketMaxUse;
    sbool sendPartial;
} instanceData;

typedef struct wrkrInstanceData {
    instanceData *pData;
    int sock; /* the socket of the filter which will be used by Darwin */
    struct sockaddr_un addr; /* the sockaddr_un used to connect to the Darwin filter */
    uint8_t pktSentSocket;
    dyn_buffer darwinBody; /* the body object used (and reused) to hold data to send to Darwin */
    dyn_buffer fieldBuffer;
} wrkrInstanceData_t;

struct modConfData_s {
    /* our overall config object */
    rsconf_t *pConf;
    const char *container;
};

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

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

/* tables for interfacing with the v6 config system
 * action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {
    {"key", eCmdHdlrGetWord, CNFPARAM_REQUIRED},
    {"socketpath", eCmdHdlrGetWord, CNFPARAM_REQUIRED},
    {"fields", eCmdHdlrArray, CNFPARAM_REQUIRED},
    {"filtercode", eCmdHdlrGetWord, 0}, /* optional parameter */
    {"response", eCmdHdlrGetWord, 0}, /* optional parameter */
    {"send_partial", eCmdHdlrBinary, 0}, /* optional parameter */
    {"socket_max_use", eCmdHdlrNonNegInt, 0}, /* optional parameter - will disappear in future updates */
};
static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, sizeof(actpdescr) / sizeof(struct cnfparamdescr), actpdescr};

/* custom functions */
#define min(a, b)               \
    ({                          \
        __typeof__(a) _a = (a); \
        __typeof__(b) _b = (b); \
        _a < _b ? _a : _b;      \
    })

static rsRetVal openSocket(wrkrInstanceData_t *pWrkrData);
static rsRetVal closeSocket(wrkrInstanceData_t *pWrkrData);
static rsRetVal doTryResume(wrkrInstanceData_t *pWrkrData);

static rsRetVal sendMsg(wrkrInstanceData_t *pWrkrData, void *msg, size_t len);
static rsRetVal receiveMsg(wrkrInstanceData_t *pWrkrData, void *response, size_t len);

const char *get_uuid_object(smsg_t *const pMsg);
int get_field(smsg_t *const pMsg, const char *pFieldName, char **ppRetString);
int expand_buffer(dyn_buffer *pBody, size_t new_size);
int add_field_to_body(dyn_buffer *pBody, const char *field, size_t size);
int start_new_line(dyn_buffer *pBody);
int end_body(dyn_buffer *pBody);

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

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

    memset(&pWrkrData->addr, 0, sizeof(struct sockaddr_un));
    pWrkrData->addr.sun_family = AF_UNIX;
    strncpy(pWrkrData->addr.sun_path, (char *)pWrkrData->pData->pSockName, sizeof(pWrkrData->addr.sun_path) - 1);

    DBGPRINTF("mmdarwin::openSocket:: connecting to Darwin...\n");

    if (connect(pWrkrData->sock, (struct sockaddr *)&pWrkrData->addr, sizeof(struct sockaddr_un)) == -1) {
        LogError(errno, RS_RET_NO_SOCKET,
                 "mmdarwin::openSocket:: error connecting to Darwin "
                 "via socket '%s'",
                 pWrkrData->pData->pSockName);

        pWrkrData->sock = INVLD_SOCK;
        ABORT_FINALIZE(RS_RET_NO_SOCKET);
    }

    DBGPRINTF("mmdarwin::openSocket:: connected !\n");
finalize_it:
    if (iRet != RS_RET_OK) {
        closeSocket(pWrkrData);
    }
    RETiRet;
}

/* close socket to remote system
 */
static rsRetVal closeSocket(wrkrInstanceData_t *pWrkrData) {
    DEFiRet;
    if (pWrkrData->sock != INVLD_SOCK) {
        if (close(pWrkrData->sock) != 0) {
            char errStr[1024];
            int eno = errno;
            DBGPRINTF("mmdarwin::closeSocket:: error %d closing the socket: %s.\n", eno,
                      rs_strerror_r(eno, errStr, sizeof(errStr)));
        }
        pWrkrData->sock = INVLD_SOCK;
    }
    RETiRet;
}

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

    DBGPRINTF("mmdarwin::doTryResume:: trying to resume\n");
    closeSocket(pWrkrData);
    iRet = openSocket(pWrkrData);

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

    RETiRet;
}

/* send a message via TCP
 * inspired by rgehards, 2007-12-20
 */
static rsRetVal sendMsg(wrkrInstanceData_t *pWrkrData, void *msg, size_t len) {
    DEFiRet;

    DBGPRINTF("mmdarwin::sendMsg:: sending message to Darwin...\n");

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

    if (pWrkrData->sock != INVLD_SOCK) {
        if (send(pWrkrData->sock, msg, len, 0) == -1) {
            char errStr[1024];
            DBGPRINTF("mmdarwin::sendData:: error while sending data: error[%d] -> %s\n", errno,
                      rs_strerror_r(errno, errStr, sizeof(errStr)));
            iRet = RS_RET_SUSPENDED;
        }
    }

finalize_it:
    RETiRet;
}

/* receive a message via TCP
 * inspired by rgehards, 2007-12-20
 */
static rsRetVal receiveMsg(wrkrInstanceData_t *pWrkrData, void *response, size_t len) {
    DEFiRet;

    DBGPRINTF("mmdarwin::receiveMsg:: receiving message from Darwin...\n");

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

    if (pWrkrData->sock != INVLD_SOCK) {
        if (recv(pWrkrData->sock, response, len, MSG_WAITALL) <= 0) {
            char errStr[1024];
            DBGPRINTF("mmdarwin::receiveMsg:: error while receiving data: error[%d] -> %s\n", errno,
                      rs_strerror_r(errno, errStr, sizeof(errStr)));
            iRet = RS_RET_NONE;
        }
    }

finalize_it:
    RETiRet;
}

/**
 * Get the string corresponding to a field supposedly present in the provided message
 *
 * params:
 *  - pMsg: a pointer to the rsyslog message where the field should be
 *  - pFieldName: a nul-terminated pointer to string representing the name of the field to search for
 *  - ppRetString: the pointer to contain the potential return string
 *
 * return: 1 if a string was put in ppRetString, 0 otherwise
 *
 * note: the string placed in ppRetString should be freed by the caller
 */
int get_field(smsg_t *const pMsg, const char *pFieldName, char **ppRetString) {
    DBGPRINTF("mmdarwin::get_field:: getting key '%s' in msg\n", pFieldName);
    struct json_object *pJson = NULL;
    char *pFieldString = NULL;
    int retVal = 0;

    msgPropDescr_t propDesc;
    msgPropDescrFill(&propDesc, (uchar *)pFieldName, strlen(pFieldName));
    msgGetJSONPropJSONorString(pMsg, &propDesc, &pJson, (uchar **)&pFieldString);

    if (pFieldString) {
        *ppRetString = pFieldString;
        DBGPRINTF("mmdarwin::get_field:: got string\n");
        retVal = 1;
    } else if (pJson) {
        pFieldString = (char *)json_object_get_string(pJson);
        if (pFieldString) {
            *ppRetString = strdup(pFieldString);
            retVal = 1;
            DBGPRINTF("mmdarwin::get_field:: got string from json\n");
            json_object_put(pJson);
        }
    }

    msgPropDescrDestruct(&propDesc);
    return retVal;
}

/**
 * expands the buffer object in the dyn_buffer object
 *
 * params:
 *  - pBody: a pointer to the concerned structure to expand
 *  - new_size: the new size to give to the underlying buffer
 *
 * return: 0 if the expansion was successful, -1 otherwise
 */
int expand_buffer(dyn_buffer *pBody, size_t new_size) {
    /* return error if new_size tries to exceed max defined size */
    if (new_size > pBody->bufferMaxSize) return -1;
    while (pBody->bufferAllocSize < new_size) pBody->bufferAllocSize += INITIAL_BUFFER_SIZE;

    DBGPRINTF("mmdarwin::expand_buffer:: expanding buffer to %zu\n", pBody->bufferAllocSize);

    char *tmp = realloc(pBody->buffer, pBody->bufferAllocSize * sizeof(char));

    if (!tmp) {
        DBGPRINTF("mmdarwin::expand_buffer:: could not resize buffer\n");
        return -1;
    }

    pBody->buffer = tmp;
    return 0;
}

/**
 * adds a field to the dyn_buffer buffer
 *
 * params:
 *  - pBody: the pointer on the dyn_buffer structure
 *  - field: the potentially not null-terminated string to add as a field to the dyn_buffer
 *  - size: the size of the string (without the '\0' character)
 *
 * return: 0 if the field was indeed added to the dyn_buffer, -1 otherwise
 */
int add_field_to_body(dyn_buffer *pBody, const char *field, size_t size) {
    /* get required additional size for field, quotes, colon, and \0
    and potentially also for the beginning of the message structure */
    int beginning = (pBody->bufferMsgSize == 0) ? 2 : 0;
    size_t requiredBodySize = pBody->bufferMsgSize + size + 4 + beginning;

    /* resize body buffer if necessary */
    if (requiredBodySize > pBody->bufferAllocSize) {
        if (expand_buffer(pBody, requiredBodySize) != 0) {
            return -1;
        }
    }

    /* add message structure beginning if current message is empty */
    if (!pBody->bufferMsgSize) {
        pBody->buffer[0] = '[';
        pBody->buffer[1] = '[';
        pBody->bufferMsgSize += 2;
    }

    /* add field with quotes and colon */
    pBody->buffer[pBody->bufferMsgSize++] = '\"';
    memcpy((void *)&pBody->buffer[pBody->bufferMsgSize], (const void *)field, size);
    pBody->bufferMsgSize += size;
    pBody->buffer[pBody->bufferMsgSize++] = '\"';
    pBody->buffer[pBody->bufferMsgSize++] = ',';

    return 0;
}

/**
 * small helper function to start a new input line (used for bulk-calls) in the dyn_buffer.
 * will close current line with a ']' and start the next with a '['.
 * will also remove leading ',' in fields list.
 *
 * params:
 *  - pBody: the pointer on the dyn_buffer on which to start a new input line
 *
 * return: 0 if successful, -1 otherwise
 */
int start_new_line(dyn_buffer *pBody) {
    /* don't if the message is empty */
    if (!pBody->bufferMsgSize) {
        return -1;
    }

    DBGPRINTF("mmdarwin::start_new_line:: starting new line entry in body\n");

    if (pBody->bufferAllocSize < pBody->bufferMsgSize + 2) {
        if (expand_buffer(pBody, pBody->bufferAllocSize + 2) != 0) {
            return -1;
        }
    }

    pBody->buffer[pBody->bufferMsgSize - 1] = ']';
    pBody->buffer[pBody->bufferMsgSize++] = ',';
    pBody->buffer[pBody->bufferMsgSize++] = '[';
    return 0;
}

/**
 * small helper function to close the dyn_buffer structure.
 * will close the line list with two ']' and will remove the leading ',' in the fields list
 *
 * params:
 *  - pBody: the pointer on the dyn_buffer on which to start a new input line
 *
 * return: 0 if successful, -1 otherwise
 */
int end_body(dyn_buffer *pBody) {
    /* don't if the message is empty */
    if (!pBody->bufferMsgSize) {
        return -1;
    }

    DBGPRINTF("mmdarwin::end_body:: finishing body structure\n");

    if (pBody->bufferAllocSize < pBody->bufferMsgSize + 2) {
        if (expand_buffer(pBody, pBody->bufferAllocSize + 2) != 0) {
            return -1;
        }
    }

    pBody->buffer[pBody->bufferMsgSize - 1] = ']';
    pBody->buffer[pBody->bufferMsgSize++] = ']';
    pBody->buffer[pBody->bufferMsgSize++] = '\0';
    return 0;
}

/**
 * Get the potential existing uuid put by previous mmdarwin call in a json
 *
 * params:
 *  - pJson: the pointer on the json
 *
 * return: a valid json_object pointer if found, NULL otherwise
 */
const char *get_uuid_object(smsg_t *const pMsg) {
    struct json_object *mmdarwin_object = NULL;
    const char *result = NULL, *key = NULL;

    msgPropDescr_t propDesc;
    msgPropDescrFill(&propDesc, (uchar *)runModConf->container, strlen(runModConf->container));
    msgGetJSONPropJSON(pMsg, &propDesc, &mmdarwin_object);

    if (mmdarwin_object) {
        struct json_object_iterator it = json_object_iter_begin(mmdarwin_object);
        struct json_object_iterator itEnd = json_object_iter_end(mmdarwin_object);

        while (!json_object_iter_equal(&it, &itEnd)) {
            key = json_object_iter_peek_name(&it);

            if (!strcmp(key, JSON_DARWIN_ID)) {
                // should always be a (non-empty) null-terminated string, safe to use with strdup()
                result = strdup(json_object_get_string(json_object_iter_peek_value(&it)));
                break;
            }

            json_object_iter_next(&it);
        }
        json_object_put(mmdarwin_object);
    }

    msgPropDescrDestruct(&propDesc);
    return result;
}

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

BEGINendCnfLoad
    CODESTARTendCnfLoad;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
    runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
    free((void *)pModConf->container);
ENDfreeCnf

BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
    DBGPRINTF("%s\n", pData->pSockName);
ENDdbgPrintInstInfo

BEGINcreateInstance
    CODESTARTcreateInstance;
ENDcreateInstance

BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
    pWrkrData->pktSentSocket = 0;
    pWrkrData->darwinBody.bufferAllocSize = 0;
    pWrkrData->darwinBody.bufferMaxSize = BUFFER_DEFAULT_MAX_SIZE;
    pWrkrData->darwinBody.bufferMsgSize = 0;
    pWrkrData->sock = INVLD_SOCK;
ENDcreateWrkrInstance

BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
ENDisCompatibleWithFeature

BEGINfreeInstance
    CODESTARTfreeInstance;
    if (pData->fieldList.name != NULL) {
        for (int i = 0; i < pData->fieldList.nmemb; ++i) {
            free(pData->fieldList.name[i]);
            free(pData->fieldList.varname[i]);
        }
        free(pData->fieldList.name);
        free(pData->fieldList.varname);
    }
    free(pData->pUUIDKey);
    free(pData->pCertitudeKey);
    free(pData->pSockName);
ENDfreeInstance

BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
    closeSocket(pWrkrData);
    free(pWrkrData->darwinBody.buffer);
ENDfreeWrkrInstance

BEGINsetModCnf
    struct cnfparamvals *pvals = NULL;
    int i;
    CODESTARTsetModCnf;
    loadModConf->container = NULL;
    pvals = nvlstGetParams(lst, &modpblk, NULL);
    if (pvals == NULL) {
        LogError(0, RS_RET_MISSING_CNFPARAMS,
                 "mmdarwin: error processing module config parameters missing [module(...)]");
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }
    if (Debug) {
        DBGPRINTF("mmdarwin::setModCnf:: module (global) param blk for mmdarwin:\n");
        cnfparamsPrint(&modpblk, pvals);
    }

    for (i = 0; i < modpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(modpblk.descr[i].name, "container")) {
            loadModConf->container = es_str2cstr(pvals[i].val.d.estr, NULL);
            if (loadModConf->container[0] != '!' && loadModConf->container[0] != '.') {
                LogError(0, RS_RET_INVALID_PARAMS,
                         "mmdarwin: container should either"
                         " begin with '!' or '.'\n");
                ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
            }
        } else {
            DBGPRINTF(
                "mmdarwin::setModCnf:: program error, non-handled "
                "param '%s'\n",
                modpblk.descr[i].name);
        }
    }

    if (loadModConf->container == NULL) {
        CHKmalloc(loadModConf->container = strdup(JSON_DEFAULT_CONTAINER));
    }

finalize_it:
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf

static inline void setInstParamDefaults(instanceData *pData) {
    DBGPRINTF("mmdarwin::setInstParamDefaults::\n");
    pData->pUUIDKey = NULL;
    pData->pCertitudeKey = NULL;
    pData->pSockName = NULL;
    pData->fieldList.nmemb = 0;
    pData->filterCode = DARWIN_FILTER_CODE_NO;
    pData->response = DARWIN_RESPONSE_SEND_NO;
    pData->socketMaxUse = 0;
    pData->sendPartial = 0;
}

BEGINnewActInst
    struct cnfparamvals *pvals;
    int i;
    CODESTARTnewActInst;
    DBGPRINTF("mmdarwin::newActInst::\n");
    if ((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    CODE_STD_STRING_REQUESTnewActInst(1);
    CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG));
    CHKiRet(createInstance(&pData));
    setInstParamDefaults(pData);

    for (i = 0; i < actpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;

        if (!strcmp(actpblk.descr[i].name, "key")) {
            char *key = es_str2cstr(pvals[i].val.d.estr, NULL);
            char vnamebuf[1024];
            snprintf(vnamebuf, sizeof(vnamebuf), "%s!%s", loadModConf->container, key);
            CHKmalloc(pData->pCertitudeKey = strdup(vnamebuf));
            free(key);
            DBGPRINTF("mmdarwin::newActInst:: certitudeKey is %s\n", pData->pCertitudeKey);
        } else if (!strcmp(actpblk.descr[i].name, "socketpath")) {
            pData->pSockName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
            DBGPRINTF("mmdarwin::newActInst:: sockName is %s\n", pData->pSockName);
        } else if (!strcmp(actpblk.descr[i].name, "socket_max_use")) {
            pData->socketMaxUse = (uint32_t)pvals[i].val.d.n;
            DBGPRINTF("mmdarwin::newActInst:: socketMaxUse is %d\n", pData->socketMaxUse);
        } else if (!strcmp(actpblk.descr[i].name, "send_partial")) {
            pData->sendPartial = (sbool)pvals[i].val.d.n;
            if (pData->sendPartial) {
                DBGPRINTF("mmdarwin::newActInst:: sending bodies even if fields are missing\n");
            } else {
                DBGPRINTF("mmdarwin::newActInst:: only sending complete bodies\n");
            }
        } else if (!strcmp(actpblk.descr[i].name, "response")) {
            char *response = es_str2cstr(pvals[i].val.d.estr, NULL);

            if (!strcmp(response, "no")) {
                pData->response = DARWIN_RESPONSE_SEND_NO;
                DBGPRINTF("mmdarwin::newActInst:: response type is 'no'\n");
            } else if (!strcmp(response, "back")) {
                pData->response = DARWIN_RESPONSE_SEND_BACK;
                DBGPRINTF("mmdarwin::newActInst:: response type is 'back'\n");
            } else if (!strcmp(response, "darwin")) {
                pData->response = DARWIN_RESPONSE_SEND_DARWIN;
                DBGPRINTF("mmdarwin::newActInst:: response type is 'darwin'\n");
            } else if (!strcmp(response, "both")) {
                pData->response = DARWIN_RESPONSE_SEND_BOTH;
                DBGPRINTF("mmdarwin::newActInst:: response type is 'both'\n");
            } else {
                DBGPRINTF("mmdarwin::newActInst:: invalid 'response' value: %s. 'No response' set.\n", response);

                pData->response = DARWIN_RESPONSE_SEND_NO;
                DBGPRINTF("mmdarwin::newActInst:: response type is 'no'\n");
            }

            free(response);
        } else if (!strcmp(actpblk.descr[i].name, "filtercode")) {
            char *filterCode = es_str2cstr(pvals[i].val.d.estr, NULL);
            pData->filterCode = strtoull(filterCode, NULL, 16);
            free(filterCode);
        } else if (!strcmp(actpblk.descr[i].name, "fields")) {
            pData->fieldList.nmemb = pvals[i].val.d.ar->nmemb;
            CHKmalloc(pData->fieldList.name = calloc(pData->fieldList.nmemb, sizeof(char *)));
            CHKmalloc(pData->fieldList.varname = calloc(pData->fieldList.nmemb, sizeof(char *)));

            for (int j = 0; j < pData->fieldList.nmemb; ++j) {
                char *const param = es_str2cstr(pvals[i].val.d.ar->arr[j], NULL);
                char *varname = NULL;
                char *name;
                if (*param == ':') {
                    char *b = strchr(param + 1, ':');
                    if (b == NULL) {
                        parser_errmsg("mmdarwin::newActInst:: missing closing colon: '%s'", param);
                        ABORT_FINALIZE(RS_RET_ERR);
                    }

                    *b = '\0'; /* split name & varname */
                    varname = param + 1;
                    name = b + 1;
                } else {
                    name = param;
                }
                CHKmalloc(pData->fieldList.name[j] = strdup(name));
                char vnamebuf[1024];
                snprintf(vnamebuf, sizeof(vnamebuf), "%s!%s", loadModConf->container,
                         (varname == NULL) ? name : varname);
                CHKmalloc(pData->fieldList.varname[j] = strdup(vnamebuf));
                free(param);
                DBGPRINTF("mmdarwin::newActInst:: will look for field %s\n", pData->fieldList.name[j]);
            }
        } else {
            DBGPRINTF("mmdarwin::newActInst:: program error, non-handled param '%s'\n", actpblk.descr[i].name);
        }
    }

    // reserve space for 'container!key\0'
    size_t sizeKey = strlen(loadModConf->container) + strlen(JSON_DARWIN_ID) + 2;
    pData->pUUIDKey = malloc(sizeKey);
    snprintf(pData->pUUIDKey, sizeKey, "%s!%s", loadModConf->container, JSON_DARWIN_ID);
    DBGPRINTF("mmdarwin:: uuid key is %s\n", pData->pUUIDKey);

    CODE_STD_FINALIZERnewActInst;
    cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst

BEGINtryResume
    CODESTARTtryResume;
    iRet = doTryResume(pWrkrData);
ENDtryResume

BEGINdoAction_NoStrings
    smsg_t **ppMsg = (smsg_t **)pMsgData; /* the raw data */
    smsg_t *pMsg = ppMsg[0]; /* the raw log line */
    instanceData *pData = pWrkrData->pData; /* the parameters given for the plugin */
    char *pFieldValue = NULL; /* ponter to the found field value */
    int fieldsNum = 0; /* number of fields retrieved */

    CODESTARTdoAction;
    DBGPRINTF("mmdarwin::doAction:: beggining action\n");
    pWrkrData->darwinBody.bufferMsgSize = 0;
    fieldsNum = 0;

    for (int i = 0; i < pData->fieldList.nmemb; i++) {
        DBGPRINTF("mmdarwin::doAction:: processing field '%s'\n", pData->fieldList.name[i]);
        pFieldValue = NULL;

        /* case 1: static field. We simply forward it to Darwin */
        if (pData->fieldList.name[i][0] != '!' && pData->fieldList.name[i][0] != '.') {
            pFieldValue = strdup(pData->fieldList.name[i]);
        }
        /* case 2: dynamic field. We retrieve its value from the JSON logline and forward it to
         * Darwin */
        else {
            if (!get_field(pMsg, pData->fieldList.name[i], &pFieldValue)) {
                DBGPRINTF(
                    "mmdarwin::doAction:: \
could not extract field '%s' from message\n",
                    pData->fieldList.name[i]);
                continue;
            }
        }

        DBGPRINTF("mmdarwin::doAction:: got value of field '%s': '%s'\n", pData->fieldList.name[i], pFieldValue);

        if (add_field_to_body(&(pWrkrData->darwinBody), pFieldValue, strlen(pFieldValue)) != 0) {
            DBGPRINTF("mmdarwin::doAction:: could not add field to body, aborting\n");
            free(pFieldValue);
            ABORT_FINALIZE(RS_RET_ERR);
        }

        fieldsNum++;
        free(pFieldValue);
    }

    if (fieldsNum) {
        if (!pData->sendPartial && fieldsNum != pData->fieldList.nmemb) {
            DBGPRINTF(
                "mmdarwin::doAction:: not all fields could be retrieved, not sending partial message."
                " (if you wish to send partial messages anyway, set 'send_partial' to 'on' in instance parameters)\n");
            FINALIZE;
        }
        if (end_body(&(pWrkrData->darwinBody)) != 0) ABORT_FINALIZE(RS_RET_ERR);
    } else {
        DBGPRINTF("mmdarwin::doAction:: no fields retrieved, finalizing\n");
        FINALIZE;
    }

    DBGPRINTF("mmdarwin::doAction:: body to send: '%s'\n", pWrkrData->darwinBody.buffer);

    if (pData->socketMaxUse) {
        /* need to rotate socket connections */
        if (!pWrkrData->pktSentSocket) {
            DBGPRINTF("mmdarwin::doAction:: opening a new connection\n");
            CHKiRet(doTryResume(pWrkrData));
        }
        pWrkrData->pktSentSocket = (pWrkrData->pktSentSocket + 1) % pData->socketMaxUse;
    }

    /* the Darwin header to be sent to the filter */
    darwin_filter_packet_t header = {.type = DARWIN_PACKET_OTHER,
                                     .response = pData->response,
                                     .filter_code = pData->filterCode,
                                     .body_size = pWrkrData->darwinBody.bufferMsgSize};

    const char *uuid = get_uuid_object(pMsg);
    if (uuid) {
        DBGPRINTF("mmdarwin: using existing UUID = %s\n", uuid);
        if (uuid_parse(uuid, header.evt_id))
            LogError(0, RS_RET_ERR, "mmdarwin:: failed to parse existing UUID: %s\n", uuid);
        free((void *)uuid);
    } else {
        uuid_generate(header.evt_id);
        char uuidStr[40];
        uuid_unparse(header.evt_id, uuidStr);
        DBGPRINTF("mmdarwin: generated new UUID = %s\n", uuidStr);
        msgAddJSON(pMsg, (uchar *)pData->pUUIDKey, json_object_new_string(uuidStr), 0, 0);
    }

    DBGPRINTF("mmdarwin::doAction:: sending header to Darwin\n");
    CHKiRet(sendMsg(pWrkrData, &header, sizeof(darwin_filter_packet_t)));

    DBGPRINTF("mmdarwin::doAction:: sending body to Darwin\n");
    CHKiRet(sendMsg(pWrkrData, (void *)(pWrkrData->darwinBody.buffer), pWrkrData->darwinBody.bufferMsgSize));

    /* there is no need to wait for a response that will never come */
    if (pData->response == DARWIN_RESPONSE_SEND_NO || pData->response == DARWIN_RESPONSE_SEND_DARWIN) {
        DBGPRINTF(
            "mmdarwin::doAction:: no response will be sent back "
            "(darwin response type is set to 'no' or 'darwin')\n");
        goto finalize_it;
    }

    darwin_filter_packet_t response;
    memset(&response, 0, sizeof(response));
    DBGPRINTF("mmdarwin::doAction:: receiving from Darwin\n");
    CHKiRet(receiveMsg(pWrkrData, &response, sizeof(response)));

    unsigned int certitude = response.certitude_list[0];
    DBGPRINTF("mmdarwin::doAction:: end of the transaction, certitude is %d\n", certitude);

    msgAddJSON(pMsg, (uchar *)pData->pCertitudeKey, json_object_new_int(certitude), 0, 0);

finalize_it:
    DBGPRINTF("mmdarwin::doAction:: finished processing log line\n");

ENDdoAction

NO_LEGACY_CONF_parseSelectorAct

    BEGINmodExit CODESTARTmodExit;
objRelease(glbl, CORE_COMPONENT);
ENDmodExit

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

BEGINmodInit()
    CODESTARTmodInit;
    /* we only support the current interface specification */
    *ipIFVersProvided = CURR_MOD_IF_VERSION;
    CODEmodInit_QueryRegCFSLineHdlr DBGPRINTF("mmdarwin::modInit:: module compiled with rsyslog version %s.\n",
                                              VERSION);
    CHKiRet(objUse(glbl, CORE_COMPONENT));
ENDmodInit
