/* mmanon.c
 * anonnymize IP addresses inside the syslog message part
 *
 * Copyright 2013 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 <ctype.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 "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "parserif.h"
#include "hashtable.h"


MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("mmanon")


DEF_OMOD_STATIC_DATA;

/* config variables */

// enumerator for the mode
enum mode { ZERO, RANDOMINT, SIMPLE };

union node {
    struct {
        union node *more;
        union node *less;
    } pointer;
    struct {
        char ip_high[16];
        char ip_low[16];
    } ips;
};

struct ipv6_int {
    unsigned long long high;
    unsigned long long low;
};
/* define operation modes we have */
#define SIMPLE_MODE 0 /* just overwrite */
#define REWRITE_MODE 1 /* rewrite IP address, canoninized */
typedef struct _instanceData {
    struct {
        sbool enable;
        int8_t bits;
        union node *Root;
        int randConsis;
        enum mode mode;
        uchar replaceChar;
    } ipv4;

    struct {
        sbool enable;
        uint8_t bits;
        enum mode anonmode;
        int randConsis;
        struct hashtable *hash;
    } ipv6;

    struct {
        sbool enable;
        uint8_t bits;
        enum mode anonmode;
        int randConsis;
        struct hashtable *hash;
    } embeddedIPv4;
} instanceData;

typedef struct wrkrInstanceData {
    instanceData *pData;
    unsigned randstatus;
} wrkrInstanceData_t;

struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
};
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 */


/* tables for interfacing with the v6 config system */
/* action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {{"ipv4.enable", eCmdHdlrBinary, 0},
                                           {"ipv4.mode", eCmdHdlrGetWord, 0},
                                           {"mode", eCmdHdlrGetWord, 0},
                                           {"ipv4.bits", eCmdHdlrPositiveInt, 0},
                                           {"ipv4.replacechar", eCmdHdlrGetChar, 0},
                                           {"replacementchar", eCmdHdlrGetChar, 0},
                                           {"ipv6.enable", eCmdHdlrBinary, 0},
                                           {"ipv6.anonmode", eCmdHdlrGetWord, 0},
                                           {"ipv6.bits", eCmdHdlrPositiveInt, 0},
                                           {"embeddedipv4.enable", eCmdHdlrBinary, 0},
                                           {"embeddedipv4.anonmode", eCmdHdlrGetWord, 0},
                                           {"embeddedipv4.bits", eCmdHdlrPositiveInt, 0}};
static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, sizeof(actpdescr) / sizeof(struct cnfparamdescr), actpdescr};

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

BEGINendCnfLoad
    CODESTARTendCnfLoad;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
    runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
ENDfreeCnf


BEGINcreateInstance
    CODESTARTcreateInstance;
ENDcreateInstance

BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
    pWrkrData->randstatus = time(NULL);
ENDcreateWrkrInstance


BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
ENDisCompatibleWithFeature


static void delTree(union node *node, const int layer) {
    if (node == NULL) {
        return;
    }
    if (layer == 31) {
        free(node);
    } else {
        delTree(node->pointer.more, layer + 1);
        delTree(node->pointer.less, layer + 1);
        free(node);
    }
}


BEGINfreeInstance
    CODESTARTfreeInstance;
    delTree(pData->ipv4.Root, 0);
    if (pData->ipv6.hash != NULL) {
        hashtable_destroy(pData->ipv6.hash, 1);
    }
    if (pData->embeddedIPv4.hash != NULL) {
        hashtable_destroy(pData->embeddedIPv4.hash, 1);
    }
ENDfreeInstance


BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
ENDfreeWrkrInstance


static inline void setInstParamDefaults(instanceData *pData) {
    pData->ipv4.enable = 1;
    pData->ipv4.bits = 16;
    pData->ipv4.Root = NULL;
    pData->ipv4.randConsis = 0;
    pData->ipv4.mode = ZERO;
    pData->ipv4.replaceChar = 'x';

    pData->ipv6.enable = 1;
    pData->ipv6.bits = 96;
    pData->ipv6.anonmode = ZERO;
    pData->ipv6.randConsis = 0;
    pData->ipv6.hash = NULL;

    pData->embeddedIPv4.enable = 1;
    pData->embeddedIPv4.bits = 96;
    pData->embeddedIPv4.anonmode = ZERO;
    pData->embeddedIPv4.randConsis = 0;
    pData->embeddedIPv4.hash = NULL;
}

BEGINnewActInst
    struct cnfparamvals *pvals;
    int i;
    CODESTARTnewActInst;
    DBGPRINTF("newActInst (mmanon)\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, "ipv4.mode") || !strcmp(actpblk.descr[i].name, "mode")) {
            if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"zero", sizeof("zero") - 1)) {
                pData->ipv4.mode = ZERO;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random", sizeof("random") - 1)) {
                pData->ipv4.mode = RANDOMINT;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"simple", sizeof("simple") - 1) ||
                       !es_strbufcmp(pvals[i].val.d.estr, (uchar *)"rewrite", sizeof("rewrite") - 1)) {
                pData->ipv4.mode = SIMPLE;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random-consistent",
                                     sizeof("random-consistent") - 1)) {
                pData->ipv4.mode = RANDOMINT;
                pData->ipv4.randConsis = 1;
            } else {
                parser_errmsg(
                    "mmanon: configuration error, unknown option for ipv4.mode, "
                    "will use \"zero\"\n");
            }
        } else if (!strcmp(actpblk.descr[i].name, "ipv4.bits")) {
            if ((int8_t)pvals[i].val.d.n <= 32) {
                pData->ipv4.bits = (int8_t)pvals[i].val.d.n;
            } else {
                pData->ipv4.bits = 32;
                parser_errmsg(
                    "warning: invalid number of ipv4.bits (%d), corrected "
                    "to 32",
                    (int)pvals[i].val.d.n);
            }
        } else if (!strcmp(actpblk.descr[i].name, "ipv4.enable")) {
            pData->ipv4.enable = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "ipv4.replacechar") ||
                   !strcmp(actpblk.descr[i].name, "replacementchar")) {
            uchar *tmp = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
            pData->ipv4.replaceChar = tmp[0];
            free(tmp);
        } else if (!strcmp(actpblk.descr[i].name, "ipv6.enable")) {
            pData->ipv6.enable = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "ipv6.bits")) {
            if ((uint8_t)pvals[i].val.d.n <= 128) {
                pData->ipv6.bits = (uint8_t)pvals[i].val.d.n;
            } else {
                pData->ipv6.bits = 128;
                parser_errmsg(
                    "warning: invalid number of ipv6.bits (%d), corrected "
                    "to 128",
                    (int)pvals[i].val.d.n);
            }
        } else if (!strcmp(actpblk.descr[i].name, "ipv6.anonmode")) {
            if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"zero", sizeof("zero") - 1)) {
                pData->ipv6.anonmode = ZERO;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random", sizeof("random") - 1)) {
                pData->ipv6.anonmode = RANDOMINT;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random-consistent",
                                     sizeof("random-consistent") - 1)) {
                pData->ipv6.anonmode = RANDOMINT;
                pData->ipv6.randConsis = 1;
            } else {
                parser_errmsg(
                    "mmanon: configuration error, unknown option for "
                    "ipv6.anonmode, will use \"zero\"\n");
            }
        } else if (!strcmp(actpblk.descr[i].name, "embeddedipv4.enable")) {
            pData->embeddedIPv4.enable = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "embeddedipv4.bits")) {
            if ((uint8_t)pvals[i].val.d.n <= 128) {
                pData->embeddedIPv4.bits = (uint8_t)pvals[i].val.d.n;
            } else {
                pData->embeddedIPv4.bits = 128;
                parser_errmsg(
                    "warning: invalid number of embeddedipv4.bits (%d), "
                    "corrected to 128",
                    (int)pvals[i].val.d.n);
            }
        } else if (!strcmp(actpblk.descr[i].name, "embeddedipv4.anonmode")) {
            if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"zero", sizeof("zero") - 1)) {
                pData->embeddedIPv4.anonmode = ZERO;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random", sizeof("random") - 1)) {
                pData->embeddedIPv4.anonmode = RANDOMINT;
            } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"random-consistent",
                                     sizeof("random-consistent") - 1)) {
                pData->embeddedIPv4.anonmode = RANDOMINT;
                pData->embeddedIPv4.randConsis = 1;
            } else {
                parser_errmsg(
                    "mmanon: configuration error, unknown option for ipv6.anonmode, "
                    "will use \"zero\"\n");
            }
        } else {
            parser_errmsg(
                "mmanon: program error, non-handled "
                "param '%s'\n",
                actpblk.descr[i].name);
        }
    }

    int bHadBitsErr = 0;
    if (pData->ipv4.mode == SIMPLE) {
        if (pData->ipv4.bits < 8 && pData->ipv4.bits > -1) {
            pData->ipv4.bits = 8;
            bHadBitsErr = 1;
        } else if (pData->ipv4.bits < 16 && pData->ipv4.bits > 8) {
            pData->ipv4.bits = 16;
            bHadBitsErr = 1;
        } else if (pData->ipv4.bits < 24 && pData->ipv4.bits > 16) {
            pData->ipv4.bits = 24;
            bHadBitsErr = 1;
        } else if ((pData->ipv4.bits != 32 && pData->ipv4.bits > 24) || pData->ipv4.bits < 0) {
            pData->ipv4.bits = 32;
            bHadBitsErr = 1;
        }
        if (bHadBitsErr) {
            LogError(0, RS_RET_INVLD_ANON_BITS,
                     "mmanon: invalid number of ipv4 bits "
                     "in simple mode, corrected to %d",
                     pData->ipv4.bits);
        }
    }

    CODE_STD_FINALIZERnewActInst;
    cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst


BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
ENDdbgPrintInstInfo


BEGINtryResume
    CODESTARTtryResume;
ENDtryResume


static int getHexVal(char c) {
    if ('0' <= c && c <= '9') {
        return c - '0';
    } else if ('a' <= c && c <= 'f') {
        return (c - 'a') + 10;
    } else if ('A' <= c && c <= 'F') {
        return (c - 'A') + 10;
    } else {
        return -1;
    }
}


/* returns 1 if valid IPv4 digit, 0 if not */
static int isPosByte(const uchar *const __restrict__ buf, const size_t buflen, size_t *const __restrict__ nprocessed) {
    int val = 0; /* Default means no byte found */
    size_t i;
    for (i = 0; i < buflen; i++) {
        if ('0' <= buf[i] && buf[i] <= '9') {
            /* Maximum 3 digits for single IPv4 Number, we only copy up to 4 numbers
             * but process forward to non digits */
            if (i < 4) {
                val = val * 10 + buf[i] - '0';
            }
        } else
            break;
    }
    *nprocessed = i;
    /* Return 1 if more than 1 and less the 4 digits and between 0 and 255 */
    if (i > 0 && i < 4 && (val >= 0 && val <= 255)) {
        return 1;
    } else {
        return 0;
    }
}

/* 1 - is IPv4, 0 not */
static int syntax_ipv4(const uchar *const __restrict__ buf,
                       const size_t buflen,
                       size_t *const __restrict__ nprocessed) {
    size_t nproc = 0;
    size_t i;
    int r = 0;
    if (isPosByte(buf, buflen, &i) == 0) {
        goto done;
    }
    if (i >= buflen || buf[i] != '.') {
        goto done;
    }
    i++;
    if (isdigit(buf[i]) == 0 || isPosByte(buf + i, buflen - i, &nproc) == 0) {
        goto done;
    }
    i += nproc;

    if (i >= buflen || buf[i] != '.') {
        goto done;
    }
    i++;
    if (isdigit(buf[i]) == 0 || isPosByte(buf + i, buflen - i, &nproc) == 0) {
        goto done;
    }
    i += nproc;

    if (i >= buflen || buf[i] != '.') {
        goto done;
    }
    i++;
    if (isdigit(buf[i]) == 0 || isPosByte(buf + i, buflen - i, &nproc) == 0) {
        goto done;
    }
    i += nproc;

    *nprocessed = i;
    r = 1;
done:
    return r;
}


static int isValidHexNum(const uchar *const __restrict__ buf,
                         const size_t buflen,
                         size_t *const __restrict__ nprocessed,
                         int handleDot) {
    size_t idx = 0;
    int cyc = 0;

    while (idx < buflen) {
        switch (buf[idx]) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':

            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':

            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                cyc++;
                (*nprocessed)++;
                if (cyc == 5) {
                    goto done;
                }
                break;
            case '.':
                if (handleDot && cyc == 0) {
                    (*nprocessed)++;
                    cyc = -2;
                }
                goto done;
            case ':':
                if (cyc == 0) {
                    (*nprocessed)++;
                    cyc = -1;
                }
                goto done;
            default:
                goto done;
        }
        idx++;
    }
done:
    return cyc;
}


static int syntax_ipv6(const uchar *const __restrict__ buf,
                       const size_t buflen,
                       size_t *const __restrict__ nprocessed) {
    int lastSep = 0;
    sbool hadAbbrev = 0;
    sbool lastAbbrev = 0;
    int ipParts = 0;
    int numLen;
    int isIP = 0;

    while (*nprocessed < buflen) {
        numLen = isValidHexNum(buf + *nprocessed, buflen - *nprocessed, nprocessed, 0);
        if (numLen > 0 && numLen < 5) {  // found a valid num
            if ((ipParts == 7 && hadAbbrev) || ipParts > 7) {
                isIP = 0;
                goto done;
            }
            if (ipParts == 0 && lastSep && !hadAbbrev) {
                isIP = 0;
                goto done;
            }
            lastSep = 0;
            lastAbbrev = 0;
            ipParts++;
        } else if (numLen < 0) {  //':'
            if (lastSep) {
                if (hadAbbrev) {
                    isIP = 0;
                    goto done;
                } else {
                    hadAbbrev = 1;
                    lastAbbrev = 1;
                }
            }
            lastSep = 1;
        } else if (numLen == 5) {  // maybe truncated with port
            if (hadAbbrev && ipParts >= 2) {
                isIP = 1;
                /* we need to go back 6 chars:
                 * 5 digits plus leading ":" which designates port!
                 */
                *nprocessed -= 6;
            } else {
                isIP = 0;
                /* nprocessed need not be corrected - it's only used if isIP == 1 */
            }
            goto done;
        } else {  // no valid num
            if (lastSep) {
                if (lastAbbrev && ipParts < 8) {
                    isIP = 1;
                    goto done;
                }
                isIP = 0;
                goto done;
            }
            if ((ipParts == 8 && !hadAbbrev) || (ipParts < 8 && hadAbbrev)) {
                isIP = 1;
                goto done;
            } else {
                isIP = 0;
                goto done;
            }
        }
        if (ipParts == 8 && !hadAbbrev) {
            isIP = 1;
            goto done;
        }
    }

    if ((!lastSep && (ipParts == 8 && !hadAbbrev)) || (ipParts < 8 && hadAbbrev)) {
        isIP = 1;
    }

done:
    return isIP;
}


static unsigned ipv42num(const char *str) {
    unsigned num[4] = {0, 0, 0, 0};
    unsigned value = -1;
    size_t len = strlen(str);
    int cyc = 0;
    for (unsigned i = 0; i < len; i++) {
        switch (str[i]) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                num[cyc] = num[cyc] * 10 + (str[i] - '0');
                break;
            case '.':
                cyc++;
                break;
            default:
                // No action needed for other cases
                break;
        }
    }

    value = num[0] * 256 * 256 * 256 + num[1] * 256 * 256 + num[2] * 256 + num[3];
    return (value);
}


static unsigned code_int(unsigned ip, wrkrInstanceData_t *pWrkrData) {
    unsigned random;
    unsigned long long shiftIP_subst = ip;
    // variable needed because shift operation of 32nd bit in unsigned does not work
    switch (pWrkrData->pData->ipv4.mode) {
        case ZERO:
            shiftIP_subst = ((shiftIP_subst >> (pWrkrData->pData->ipv4.bits)) << (pWrkrData->pData->ipv4.bits));
            return (unsigned)shiftIP_subst;
        case RANDOMINT:
            shiftIP_subst = ((shiftIP_subst >> (pWrkrData->pData->ipv4.bits)) << (pWrkrData->pData->ipv4.bits));
            // multiply the random number between 0 and 1 with a mask of (2^n)-1:
            random = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) *
                                ((1ull << (pWrkrData->pData->ipv4.bits)) - 1));
            return (unsigned)shiftIP_subst + random;
        case SIMPLE:  // can't happen, since this case is caught at the start of anonipv4()
        default:
            LogError(0, RS_RET_INTERNAL_ERROR, "mmanon: unexpected code path reached in code_int function");
            return 0;
    }
}


static int num2ipv4(unsigned num, char *str) {
    int numip[4];
    size_t len;
    for (int i = 0; i < 4; i++) {
        numip[i] = num % 256;
        num = num / 256;
    }
    len = snprintf(str, 16, "%d.%d.%d.%d", numip[3], numip[2], numip[1], numip[0]);
    return len;
}


static void getip(uchar *start, size_t end, char *address) {
    size_t i;

    for (i = 0; i < end; i++) {
        address[i] = *(start + i);
    }
    address[i] = '\0';
}

/* in case of error with malloc causing abort of function, the
 * string at the target of address remains the same */
static rsRetVal findip(char *address, wrkrInstanceData_t *pWrkrData) {
    DEFiRet;
    int i;
    unsigned num;
    union node *current;
    union node *Last;
    int MoreLess;
    char *CurrentCharPtr;

    current = pWrkrData->pData->ipv4.Root;
    num = ipv42num(address);
    for (i = 0; i < 31; i++) {
        if (pWrkrData->pData->ipv4.Root == NULL) {
            CHKmalloc(current = (union node *)calloc(1, sizeof(union node)));
            pWrkrData->pData->ipv4.Root = current;
        }
        Last = current;
        if ((num >> (31 - i)) & 1) {
            current = current->pointer.more;
            MoreLess = 1;
        } else {
            current = current->pointer.less;
            MoreLess = 0;
        }
        if (current == NULL) {
            CHKmalloc(current = (union node *)calloc(1, sizeof(union node)));
            if (MoreLess == 1) {
                Last->pointer.more = current;
            } else {
                Last->pointer.less = current;
            }
        }
    }
    if (num & 1) {
        CurrentCharPtr = current->ips.ip_high;
    } else {
        CurrentCharPtr = current->ips.ip_low;
    }
    if (CurrentCharPtr[0] != '\0') {
        strcpy(address, CurrentCharPtr);
    } else {
        num = code_int(num, pWrkrData);
        num2ipv4(num, CurrentCharPtr);
        strcpy(address, CurrentCharPtr);
    }
finalize_it:
    RETiRet;
}


static void process_IPv4(char *address, wrkrInstanceData_t *pWrkrData) {
    unsigned num;

    if (pWrkrData->pData->ipv4.randConsis) {
        findip(address, pWrkrData);
    } else {
        num = ipv42num(address);
        num = code_int(num, pWrkrData);
        num2ipv4(num, address);
    }
}


static void simpleAnon(wrkrInstanceData_t *const pWrkrData, uchar *const msg, int *const hasChanged, int iplen) {
    int maxidx = iplen - 1;

    int j = -1;
    for (int i = (pWrkrData->pData->ipv4.bits / 8); i > 0; i--) {
        j++;
        while ('0' <= msg[maxidx - j] && msg[maxidx - j] <= '9') {
            if (msg[maxidx - j] != pWrkrData->pData->ipv4.replaceChar) {
                msg[maxidx - j] = pWrkrData->pData->ipv4.replaceChar;
                *hasChanged = 1;
            }
            j++;
        }
    }
}


static void anonipv4(wrkrInstanceData_t *pWrkrData, uchar **msg, int *pLenMsg, int *idx, int *hasChanged) {
    char address[16];
    char caddress[16];
    int offset = *idx;
    uchar *msgcpy = *msg;
    size_t iplen;
    size_t caddresslen;
    int oldLen = *pLenMsg;

    if (syntax_ipv4((*msg) + offset, *pLenMsg - offset, &iplen)) {
        if (pWrkrData->pData->ipv4.mode == SIMPLE) {
            simpleAnon(pWrkrData, *msg + *idx, hasChanged, iplen);
            *idx += iplen;
            return;
        }

        assert(iplen < sizeof(address));
        getip(*msg + offset, iplen, address);
        offset += iplen;
        strcpy(caddress, address);
        process_IPv4(caddress, pWrkrData);
        caddresslen = strlen(caddress);
        *hasChanged = 1;

        if (caddresslen != strlen(address)) {
            *pLenMsg = *pLenMsg + ((int)caddresslen - (int)strlen(address));
            *msg = (uchar *)malloc(*pLenMsg);
            memcpy(*msg, msgcpy, *idx);
        }
        memcpy(*msg + *idx, caddress, caddresslen);
        *idx = *idx + caddresslen;
        if (*idx < *pLenMsg) {
            memcpy(*msg + *idx, msgcpy + offset, oldLen - offset);
        }
        if (msgcpy != *msg) {
            free(msgcpy);
        }
    }
}


static void code_ipv6_int(struct ipv6_int *ip, wrkrInstanceData_t *pWrkrData, int useEmbedded) {
    unsigned long long randlow = 0;
    unsigned long long randhigh = 0;
    unsigned tmpRand;
    int fullbits;

    int bits = useEmbedded ? pWrkrData->pData->embeddedIPv4.bits : pWrkrData->pData->ipv6.bits;
    enum mode anonmode = useEmbedded ? pWrkrData->pData->embeddedIPv4.anonmode : pWrkrData->pData->ipv6.anonmode;

    if (bits == 128) {  // has to be handled separately, since shift
                        // 128 bits doesn't work on unsigned long long
        ip->high = 0;
        ip->low = 0;
    } else if (bits > 64) {
        ip->low = 0;
        ip->high = (ip->high >> (bits - 64)) << (bits - 64);
    } else if (bits == 64) {
        ip->low = 0;
    } else {
        ip->low = (ip->low >> bits) << bits;
    }
    switch (anonmode) {
        case ZERO:
            break;
        case RANDOMINT:
            if (bits == 128) {
                for (int i = 0; i < 8; i++) {
                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    ip->high <<= 8;
                    ip->high |= tmpRand;

                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    ip->low <<= 8;
                    ip->low |= tmpRand;
                }
            } else if (bits > 64) {
                for (int i = 0; i < 8; i++) {
                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    ip->low <<= 8;
                    ip->low |= tmpRand;
                }

                bits -= 64;
                fullbits = bits / 8;
                bits = bits % 8;
                while (fullbits > 0) {
                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    randhigh <<= 8;
                    randhigh |= tmpRand;
                    fullbits--;
                }
                tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * ((1 << bits) - 1));
                randhigh <<= bits;
                randhigh |= tmpRand;

                ip->high |= randhigh;
            } else if (bits == 64) {
                for (int i = 0; i < 8; i++) {
                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    ip->low <<= 8;
                    ip->low |= tmpRand;
                }
            } else {
                fullbits = bits / 8;
                bits = bits % 8;
                while (fullbits > 0) {
                    tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * 0xff);
                    randlow <<= 8;
                    randlow |= tmpRand;
                    fullbits--;
                }
                tmpRand = (unsigned)((rand_r(&(pWrkrData->randstatus)) / (double)RAND_MAX) * ((1 << bits) - 1));
                randlow <<= bits;
                randlow |= tmpRand;

                ip->low |= randlow;
            }
            break;
        case SIMPLE:  // can't happen, since this case is caught at the start of anonipv4()
        default:
            LogError(0, RS_RET_INTERNAL_ERROR, "mmanon: unexpected code path reached in code_int function");
    }
}


// separate function from recognising ipv6, since the recognition might get more
// complex. This function always stays
// the same, since it always gets an valid ipv6 input
static void ipv62num(char *const address, const size_t iplen, struct ipv6_int *const ip) {
    int num[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    int cyc = 0;
    int dots = 0;
    int val;
    unsigned i;

    for (i = 0; i < iplen && dots < 2; i++) {
        val = getHexVal(address[i]);
        if (val == -1) {
            dots++;
            if (dots < 2) {
                cyc++;
            }
        } else {
            num[cyc] = num[cyc] * 16 + val;
            dots = 0;
        }
    }
    if (dots == 2) {
        if (i < iplen - 1) {
            int shift = 0;
            cyc = 7;
            for (unsigned j = iplen - 1; j >= i; j--) {
                val = getHexVal(address[j]);
                if (val == -1) {
                    cyc--;
                    shift = 0;
                } else {
                    val <<= shift;
                    shift += 4;
                    num[cyc] += val;
                }
            }
        } else {
            while (cyc < 8) {
                num[cyc] = 0;
                cyc++;
            }
        }
    }

    for (i = 0; i < 4; i++) {
        ip->high <<= 16;
        ip->high |= num[i];
    }
    while (i < 8) {
        ip->low <<= 16;
        ip->low |= num[i];
        i++;
    }
}


static void num2ipv6(struct ipv6_int *ip, char *address) {
    int num[8];
    int i;

    for (i = 7; i > 3; i--) {
        num[i] = ip->low & 0xffff;
        ip->low >>= 16;
    }
    while (i > -1) {
        num[i] = ip->high & 0xffff;
        ip->high >>= 16;
        i--;
    }

    snprintf(address, 40, "%x:%x:%x:%x:%x:%x:%x:%x", num[0], num[1], num[2], num[3], num[4], num[5], num[6], num[7]);
}


static int keys_equal_fn(void *key1, void *key2) {
    struct ipv6_int *const k1 = (struct ipv6_int *)key1;
    struct ipv6_int *const k2 = (struct ipv6_int *)key2;

    return ((k1->high == k2->high) && (k1->low == k2->low));
}


static unsigned hash_from_key_fn(void *k) {
    struct ipv6_int *const key = (struct ipv6_int *)k;
    unsigned hashVal;

    hashVal = (key->high & 0xFFC00000) | (key->low & 0x3FFFFF);
    return hashVal;
}


static void num2embedded(struct ipv6_int *ip, char *address) {
    int num[8];
    int i;

    for (i = 7; i > 3; i--) {
        num[i] = ip->low & 0xffff;
        ip->low >>= 16;
    }
    while (i > -1) {
        num[i] = ip->high & 0xffff;
        ip->high >>= 16;
        i--;
    }

    snprintf(address, 46, "%x:%x:%x:%x:%x:%x:%d.%d.%d.%d", num[0], num[1], num[2], num[3], num[4], num[5],
             (num[6] & 0xff00) >> 8, num[6] & 0xff, (num[7] & 0xff00) >> 8, num[7] & 0xff);
}


static rsRetVal findIPv6(struct ipv6_int *num, char *address, wrkrInstanceData_t *const pWrkrData, int useEmbedded) {
    struct ipv6_int *hashKey = NULL;
    DEFiRet;
    struct hashtable *hash = useEmbedded ? pWrkrData->pData->embeddedIPv4.hash : pWrkrData->pData->ipv6.hash;


    if (hash == NULL) {
        CHKmalloc(hash = create_hashtable(512, hash_from_key_fn, keys_equal_fn, NULL));
        if (useEmbedded) {
            pWrkrData->pData->embeddedIPv4.hash = hash;
        } else {
            pWrkrData->pData->ipv6.hash = hash;
        }
    }

    char *val = (char *)(hashtable_search(hash, num));

    if (val != NULL) {
        strcpy(address, val);
    } else {
        CHKmalloc(hashKey = (struct ipv6_int *)malloc(sizeof(struct ipv6_int)));
        hashKey->low = num->low;
        hashKey->high = num->high;

        if (useEmbedded) {
            code_ipv6_int(num, pWrkrData, 1);
            num2embedded(num, address);
        } else {
            code_ipv6_int(num, pWrkrData, 0);
            num2ipv6(num, address);
        }
        char *hashString;
        CHKmalloc(hashString = strdup(address));

        if (!hashtable_insert(hash, hashKey, hashString)) {
            DBGPRINTF("hashtable error: insert to %s-table failed", useEmbedded ? "embedded ipv4" : "ipv6");
            free(hashString);
            ABORT_FINALIZE(RS_RET_ERR);
        }
        hashKey = NULL;
    }
finalize_it:
    free(hashKey);
    RETiRet;
}


static void process_IPv6(char *address, wrkrInstanceData_t *pWrkrData, const size_t iplen) {
    struct ipv6_int num = {0, 0};

    ipv62num(address, iplen, &num);

    if (pWrkrData->pData->ipv6.randConsis) {
        findIPv6(&num, address, pWrkrData, 0);
    } else {
        code_ipv6_int(&num, pWrkrData, 0);
        num2ipv6(&num, address);
    }
}


static void anonipv6(wrkrInstanceData_t *pWrkrData, uchar **msg, int *pLenMsg, int *idx, int *hasChanged) {
    size_t iplen = 0;
    int offset = *idx;
    char address[40];
    uchar *msgcpy = *msg;
    size_t caddresslen;
    size_t oldLen = *pLenMsg;

    int syn = syntax_ipv6(*msg + offset, *pLenMsg - offset, &iplen);
    if (syn) {
        assert(iplen < sizeof(address));  // has to be < instead of <= since address includes space for a '\0'
        getip(*msg + offset, iplen, address);
        offset += iplen;
        process_IPv6(address, pWrkrData, iplen);

        caddresslen = strlen(address);
        *hasChanged = 1;

        if (caddresslen != iplen) {
            *pLenMsg = *pLenMsg + ((int)caddresslen - (int)iplen);
            *msg = (uchar *)malloc(*pLenMsg);
            memcpy(*msg, msgcpy, *idx);
        }
        memcpy(*msg + *idx, address, caddresslen);
        *idx = *idx + caddresslen;
        if (*idx < *pLenMsg) {
            memcpy(*msg + *idx, msgcpy + offset, oldLen - offset);
        }
        if (msgcpy != *msg) {
            free(msgcpy);
        }
    }
}


static size_t findV4Start(const uchar *const __restrict__ buf, size_t dotPos) {
    while (dotPos > 0) {
        if (buf[dotPos] == ':') {
            return dotPos + 1;
        }
        dotPos--;
    }
    return -1;  // should not happen
}


static int syntax_embedded(const uchar *const __restrict__ buf,
                           const size_t buflen,
                           size_t *const __restrict__ nprocessed,
                           size_t *v4Start) {
    int lastSep = 0;
    sbool hadAbbrev = 0;
    int ipParts = 0;
    int numLen;
    int isIP = 0;
    size_t ipv4Len;

    while (*nprocessed < buflen) {
        numLen = isValidHexNum(buf + *nprocessed, buflen - *nprocessed, nprocessed, 1);
        if (numLen > 0) {  // found a valid num
            if ((ipParts == 6 && hadAbbrev) || ipParts > 6) {  // is 6 since the first part of
                // IPv4 will also result in a valid hexvalue
                isIP = 0;
                goto done;
            }
            if (ipParts == 0 && lastSep && !hadAbbrev) {
                isIP = 0;
                goto done;
            }
            lastSep = 0;
            ipParts++;
        } else if (numLen == -1) {  //':'
            if (lastSep) {
                if (hadAbbrev) {
                    isIP = 0;
                    goto done;
                } else {
                    hadAbbrev = 1;
                }
            }
            lastSep = 1;
        } else if (numLen == -2) {  //'.'
            if (lastSep || (ipParts == 0 && hadAbbrev) || (ipParts <= 6 && !hadAbbrev)) {
                isIP = 0;
                goto done;
            }
            *v4Start = findV4Start(buf, (*nprocessed) - 1);
            if (syntax_ipv4(buf + (*v4Start), buflen, &ipv4Len)) {
                *nprocessed += (ipv4Len - ((*nprocessed) - (*v4Start)));
                isIP = 1;
                goto done;
            } else {
                isIP = 0;
                goto done;
            }
        } else {  // no valid num
            isIP = 0;
            goto done;
        }
    }

    isIP = 0;

done:
    return isIP;
}


static void embedded2num(char *address, size_t v4Start, struct ipv6_int *ip) {
    int num[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    int cyc = 0;
    int dots = 0;
    int val;
    unsigned i;

    unsigned v4Val = ipv42num(address + v4Start);
    num[7] = v4Val & 0xffff;
    num[6] = (v4Val & 0xffff0000) >> 16;

    for (i = 0; i < v4Start && dots < 2; i++) {
        val = getHexVal(address[i]);
        if (val == -1) {
            dots++;
            if (dots < 2) {
                cyc++;
            }
        } else {
            num[cyc] = num[cyc] * 16 + val;
            dots = 0;
        }
    }
    if (dots == 2) {
        if (i < v4Start) {
            int shift = 0;
            cyc = 5;
            for (unsigned j = v4Start - 1; j >= i; j--) {
                val = getHexVal(address[j]);
                if (val == -1) {
                    cyc--;
                    shift = 0;
                } else {
                    val <<= shift;
                    shift += 4;
                    num[cyc] += val;
                }
            }
        } else {
            while (cyc < 6) {
                num[cyc] = 0;
                cyc++;
            }
        }
    }

    for (i = 0; i < 4; i++) {
        ip->high <<= 16;
        ip->high |= num[i];
    }
    while (i < 8) {
        ip->low <<= 16;
        ip->low |= num[i];
        i++;
    }
}


static void process_embedded(char *address, wrkrInstanceData_t *pWrkrData, size_t v4Start) {
    struct ipv6_int num = {0, 0};

    embedded2num(address, v4Start, &num);

    if (pWrkrData->pData->embeddedIPv4.randConsis) {
        findIPv6(&num, address, pWrkrData, 1);
    } else {
        code_ipv6_int(&num, pWrkrData, 1);
        num2embedded(&num, address);
    }
}


static void anonEmbedded(wrkrInstanceData_t *pWrkrData, uchar **msg, int *pLenMsg, int *idx, int *hasChanged) {
    size_t iplen = 0;
    int offset = *idx;
    char address[46];
    uchar *msgcpy = *msg;
    unsigned caddresslen;
    size_t oldLen = *pLenMsg;
    size_t v4Start;

    int syn = syntax_embedded(*msg + offset, *pLenMsg - offset, &iplen, &v4Start);
    if (syn) {
        assert(iplen < sizeof(address));
        getip(*msg + offset, iplen, address);
        offset += iplen;
        process_embedded(address, pWrkrData, v4Start);

        caddresslen = strlen(address);
        *hasChanged = 1;

        if (caddresslen != iplen) {
            *pLenMsg = *pLenMsg + ((int)caddresslen - (int)iplen);
            *msg = (uchar *)malloc(*pLenMsg);
            memcpy(*msg, msgcpy, *idx);
        }
        memcpy(*msg + *idx, address, caddresslen);
        *idx = *idx + caddresslen;
        if (*idx < *pLenMsg) {
            memcpy(*msg + *idx, msgcpy + offset, oldLen - offset);
        }
        if (msgcpy != *msg) {
            free(msgcpy);
        }
    }
}

BEGINdoAction_NoStrings
    smsg_t **ppMsg = (smsg_t **)pMsgData;
    smsg_t *pMsg = ppMsg[0];
    uchar *msg;
    int lenMsg;
    int i;
    int hasChanged = 0;
    CODESTARTdoAction;
    lenMsg = getMSGLen(pMsg);
    msg = (uchar *)strdup((char *)getMSG(pMsg));

    for (i = 0; i <= lenMsg - 2; i++) {
        if (pWrkrData->pData->embeddedIPv4.enable) {
            anonEmbedded(pWrkrData, &msg, &lenMsg, &i, &hasChanged);
        }
        if (pWrkrData->pData->ipv4.enable) {
            anonipv4(pWrkrData, &msg, &lenMsg, &i, &hasChanged);
        }
        if (pWrkrData->pData->ipv6.enable) {
            anonipv6(pWrkrData, &msg, &lenMsg, &i, &hasChanged);
        }
    }
    if (hasChanged) {
        MsgReplaceMSG(pMsg, msg, lenMsg);
    }
    free(msg);
ENDdoAction


NO_LEGACY_CONF_parseSelectorAct


    BEGINmodExit CODESTARTmodExit;
ENDmodExit


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


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