From dcc721a95bef6f0d8e6d8775b8efe33e5aecd562 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 18:28:20 +0200 Subject: Adding upstream version 8.2402.0. Signed-off-by: Daniel Baumann --- runtime/msg.c | 5476 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5476 insertions(+) create mode 100644 runtime/msg.c (limited to 'runtime/msg.c') diff --git a/runtime/msg.c b/runtime/msg.c new file mode 100644 index 0000000..b35bc1d --- /dev/null +++ b/runtime/msg.c @@ -0,0 +1,5476 @@ +/* msg.c + * The msg object. Implementation of all msg-related functions + * + * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007-2023 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * The rsyslog runtime library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The rsyslog runtime library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the rsyslog runtime library. If not, see . + * + * A copy of the GPL can be found in the file "COPYING" in this distribution. + * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. + */ +#include "config.h" +#include +#include +#include +#define SYSLOG_NAMES +#include +#include +#include +#include +#ifdef HAVE_SYSINFO_UPTIME +#include +#endif +#include +#include +#include +#ifdef HAVE_MALLOC_H +# include +#endif +#ifdef USE_LIBUUID +# include +#endif +#include +#include "rsyslog.h" +#include "srUtils.h" +#include "stringbuf.h" +#include "template.h" +#include "msg.h" +#include "datetime.h" +#include "glbl.h" +#include "regexp.h" +#include "atomic.h" +#include "unicode-helper.h" +#include "ruleset.h" +#include "prop.h" +#include "net.h" +#include "var.h" +#include "rsconf.h" +#include "parserif.h" +#include "errmsg.h" + +#define DEV_DEBUG 0 /* set to 1 to enable very verbose developer debugging messages */ + +/* inlines */ +extern void msgSetPRI(smsg_t *const __restrict__ pMsg, syslog_pri_t pri); + +/* TODO: move the global variable root to the config object - had no time to to it + * right now before vacation -- rgerhards, 2013-07-22 + */ +static pthread_mutex_t glblVars_lock; +struct json_object *global_var_root = NULL; + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(datetime) +DEFobjCurrIf(glbl) +DEFobjCurrIf(regexp) +DEFobjCurrIf(prop) +DEFobjCurrIf(net) +DEFobjCurrIf(var) + +static const char *one_digit[10] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static const char *two_digits[100] = { + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"}; + +static const char *wdayNames[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + +/* Table of days in a year, needed for getYearDay */ +static const char *daysInYear[366] = { + "001", "002", "003", "004", "005", "006", "007", "008", "009", + "010", "011", "012", "013", "014", "015", "016", "017", "018", "019", + "020", "021", "022", "023", "024", "025", "026", "027", "028", "029", + "030", "031", "032", "033", "034", "035", "036", "037", "038", "039", + "040", "041", "042", "043", "044", "045", "046", "047", "048", "049", + "050", "051", "052", "053", "054", "055", "056", "057", "058", "059", + "060", "061", "062", "063", "064", "065", "066", "067", "068", "069", + "070", "071", "072", "073", "074", "075", "076", "077", "078", "079", + "080", "081", "082", "083", "084", "085", "086", "087", "088", "089", + "090", "091", "092", "093", "094", "095", "096", "097", "098", "099", + "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", + "110", "111", "112", "113", "114", "115", "116", "117", "118", "119", + "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", + "130", "131", "132", "133", "134", "135", "136", "137", "138", "139", + "140", "141", "142", "143", "144", "145", "146", "147", "148", "149", + "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", + "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", + "170", "171", "172", "173", "174", "175", "176", "177", "178", "179", + "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", + "190", "191", "192", "193", "194", "195", "196", "197", "198", "199", + "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", + "210", "211", "212", "213", "214", "215", "216", "217", "218", "219", + "220", "221", "222", "223", "224", "225", "226", "227", "228", "229", + "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", + "240", "241", "242", "243", "244", "245", "246", "247", "248", "249", + "250", "251", "252", "253", "254", "255", "256", "257", "258", "259", + "260", "261", "262", "263", "264", "265", "266", "267", "268", "269", + "270", "271", "272", "273", "274", "275", "276", "277", "278", "279", + "280", "281", "282", "283", "284", "285", "286", "287", "288", "289", + "290", "291", "292", "293", "294", "295", "296", "297", "298", "299", + "300", "301", "302", "303", "304", "305", "306", "307", "308", "309", + "310", "311", "312", "313", "314", "315", "316", "317", "318", "319", + "320", "321", "322", "323", "324", "325", "326", "327", "328", "329", + "330", "331", "332", "333", "334", "335", "336", "337", "338", "339", + "340", "341", "342", "343", "344", "345", "346", "347", "348", "349", + "350", "351", "352", "353", "354", "355", "356", "357", "358", "359", + "360", "361", "362", "363", "364", "365", "366"}; + +/* The following is a table of supported years. This permits us + * to avoid dynamic memory allocation. Note that the time-based + * algos need to be upgraded after the year 2099 in any case. + * Quite honestly, I don't expect that this is a real problem ;) + */ +static const char *years[] = { + "1967", "1968", "1969", "1970", "1971", "1972", "1973", "1974", + "1975", "1976", "1977", "1978", "1979", "1980", "1981", "1982", + "1983", "1984", "1985", "1986", "1987", "1988", "1989", "1990", + "1991", "1992", "1993", "1994", "1995", "1996", "1997", "1998", + "1999", "2000", "2001", "2002", "2003", "2004", "2005", "2006", + "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", + "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022", + "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030", + "2031", "2032", "2033", "2034", "2035", "2036", "2037", "2038", + "2039", "2040", "2041", "2042", "2043", "2044", "2045", "2046", + "2047", "2048", "2049", "2050", "2051", "2052", "2053", "2054", + "2055", "2056", "2057", "2058", "2059", "2060", "2061", "2062", + "2063", "2064", "2065", "2066", "2067", "2068", "2069", "2070", + "2071", "2072", "2073", "2074", "2075", "2076", "2077", "2078", + "2079", "2080", "2081", "2082", "2083", "2084", "2085", "2086", + "2087", "2088", "2089", "2090", "2091", "2092", "2093", "2094", + "2095", "2096", "2097", "2098", "2099" }; + +static struct { + uchar *pszName; +} syslog_pri_names[200] = { + { UCHAR_CONSTANT("0") }, + { UCHAR_CONSTANT("1") }, + { UCHAR_CONSTANT("2") }, + { UCHAR_CONSTANT("3") }, + { UCHAR_CONSTANT("4") }, + { UCHAR_CONSTANT("5") }, + { UCHAR_CONSTANT("6") }, + { UCHAR_CONSTANT("7") }, + { UCHAR_CONSTANT("8") }, + { UCHAR_CONSTANT("9") }, + { UCHAR_CONSTANT("10") }, + { UCHAR_CONSTANT("11") }, + { UCHAR_CONSTANT("12") }, + { UCHAR_CONSTANT("13") }, + { UCHAR_CONSTANT("14") }, + { UCHAR_CONSTANT("15") }, + { UCHAR_CONSTANT("16") }, + { UCHAR_CONSTANT("17") }, + { UCHAR_CONSTANT("18") }, + { UCHAR_CONSTANT("19") }, + { UCHAR_CONSTANT("20") }, + { UCHAR_CONSTANT("21") }, + { UCHAR_CONSTANT("22") }, + { UCHAR_CONSTANT("23") }, + { UCHAR_CONSTANT("24") }, + { UCHAR_CONSTANT("25") }, + { UCHAR_CONSTANT("26") }, + { UCHAR_CONSTANT("27") }, + { UCHAR_CONSTANT("28") }, + { UCHAR_CONSTANT("29") }, + { UCHAR_CONSTANT("30") }, + { UCHAR_CONSTANT("31") }, + { UCHAR_CONSTANT("32") }, + { UCHAR_CONSTANT("33") }, + { UCHAR_CONSTANT("34") }, + { UCHAR_CONSTANT("35") }, + { UCHAR_CONSTANT("36") }, + { UCHAR_CONSTANT("37") }, + { UCHAR_CONSTANT("38") }, + { UCHAR_CONSTANT("39") }, + { UCHAR_CONSTANT("40") }, + { UCHAR_CONSTANT("41") }, + { UCHAR_CONSTANT("42") }, + { UCHAR_CONSTANT("43") }, + { UCHAR_CONSTANT("44") }, + { UCHAR_CONSTANT("45") }, + { UCHAR_CONSTANT("46") }, + { UCHAR_CONSTANT("47") }, + { UCHAR_CONSTANT("48") }, + { UCHAR_CONSTANT("49") }, + { UCHAR_CONSTANT("50") }, + { UCHAR_CONSTANT("51") }, + { UCHAR_CONSTANT("52") }, + { UCHAR_CONSTANT("53") }, + { UCHAR_CONSTANT("54") }, + { UCHAR_CONSTANT("55") }, + { UCHAR_CONSTANT("56") }, + { UCHAR_CONSTANT("57") }, + { UCHAR_CONSTANT("58") }, + { UCHAR_CONSTANT("59") }, + { UCHAR_CONSTANT("60") }, + { UCHAR_CONSTANT("61") }, + { UCHAR_CONSTANT("62") }, + { UCHAR_CONSTANT("63") }, + { UCHAR_CONSTANT("64") }, + { UCHAR_CONSTANT("65") }, + { UCHAR_CONSTANT("66") }, + { UCHAR_CONSTANT("67") }, + { UCHAR_CONSTANT("68") }, + { UCHAR_CONSTANT("69") }, + { UCHAR_CONSTANT("70") }, + { UCHAR_CONSTANT("71") }, + { UCHAR_CONSTANT("72") }, + { UCHAR_CONSTANT("73") }, + { UCHAR_CONSTANT("74") }, + { UCHAR_CONSTANT("75") }, + { UCHAR_CONSTANT("76") }, + { UCHAR_CONSTANT("77") }, + { UCHAR_CONSTANT("78") }, + { UCHAR_CONSTANT("79") }, + { UCHAR_CONSTANT("80") }, + { UCHAR_CONSTANT("81") }, + { UCHAR_CONSTANT("82") }, + { UCHAR_CONSTANT("83") }, + { UCHAR_CONSTANT("84") }, + { UCHAR_CONSTANT("85") }, + { UCHAR_CONSTANT("86") }, + { UCHAR_CONSTANT("87") }, + { UCHAR_CONSTANT("88") }, + { UCHAR_CONSTANT("89") }, + { UCHAR_CONSTANT("90") }, + { UCHAR_CONSTANT("91") }, + { UCHAR_CONSTANT("92") }, + { UCHAR_CONSTANT("93") }, + { UCHAR_CONSTANT("94") }, + { UCHAR_CONSTANT("95") }, + { UCHAR_CONSTANT("96") }, + { UCHAR_CONSTANT("97") }, + { UCHAR_CONSTANT("98") }, + { UCHAR_CONSTANT("99") }, + { UCHAR_CONSTANT("100") }, + { UCHAR_CONSTANT("101") }, + { UCHAR_CONSTANT("102") }, + { UCHAR_CONSTANT("103") }, + { UCHAR_CONSTANT("104") }, + { UCHAR_CONSTANT("105") }, + { UCHAR_CONSTANT("106") }, + { UCHAR_CONSTANT("107") }, + { UCHAR_CONSTANT("108") }, + { UCHAR_CONSTANT("109") }, + { UCHAR_CONSTANT("110") }, + { UCHAR_CONSTANT("111") }, + { UCHAR_CONSTANT("112") }, + { UCHAR_CONSTANT("113") }, + { UCHAR_CONSTANT("114") }, + { UCHAR_CONSTANT("115") }, + { UCHAR_CONSTANT("116") }, + { UCHAR_CONSTANT("117") }, + { UCHAR_CONSTANT("118") }, + { UCHAR_CONSTANT("119") }, + { UCHAR_CONSTANT("120") }, + { UCHAR_CONSTANT("121") }, + { UCHAR_CONSTANT("122") }, + { UCHAR_CONSTANT("123") }, + { UCHAR_CONSTANT("124") }, + { UCHAR_CONSTANT("125") }, + { UCHAR_CONSTANT("126") }, + { UCHAR_CONSTANT("127") }, + { UCHAR_CONSTANT("128") }, + { UCHAR_CONSTANT("129") }, + { UCHAR_CONSTANT("130") }, + { UCHAR_CONSTANT("131") }, + { UCHAR_CONSTANT("132") }, + { UCHAR_CONSTANT("133") }, + { UCHAR_CONSTANT("134") }, + { UCHAR_CONSTANT("135") }, + { UCHAR_CONSTANT("136") }, + { UCHAR_CONSTANT("137") }, + { UCHAR_CONSTANT("138") }, + { UCHAR_CONSTANT("139") }, + { UCHAR_CONSTANT("140") }, + { UCHAR_CONSTANT("141") }, + { UCHAR_CONSTANT("142") }, + { UCHAR_CONSTANT("143") }, + { UCHAR_CONSTANT("144") }, + { UCHAR_CONSTANT("145") }, + { UCHAR_CONSTANT("146") }, + { UCHAR_CONSTANT("147") }, + { UCHAR_CONSTANT("148") }, + { UCHAR_CONSTANT("149") }, + { UCHAR_CONSTANT("150") }, + { UCHAR_CONSTANT("151") }, + { UCHAR_CONSTANT("152") }, + { UCHAR_CONSTANT("153") }, + { UCHAR_CONSTANT("154") }, + { UCHAR_CONSTANT("155") }, + { UCHAR_CONSTANT("156") }, + { UCHAR_CONSTANT("157") }, + { UCHAR_CONSTANT("158") }, + { UCHAR_CONSTANT("159") }, + { UCHAR_CONSTANT("160") }, + { UCHAR_CONSTANT("161") }, + { UCHAR_CONSTANT("162") }, + { UCHAR_CONSTANT("163") }, + { UCHAR_CONSTANT("164") }, + { UCHAR_CONSTANT("165") }, + { UCHAR_CONSTANT("166") }, + { UCHAR_CONSTANT("167") }, + { UCHAR_CONSTANT("168") }, + { UCHAR_CONSTANT("169") }, + { UCHAR_CONSTANT("170") }, + { UCHAR_CONSTANT("171") }, + { UCHAR_CONSTANT("172") }, + { UCHAR_CONSTANT("173") }, + { UCHAR_CONSTANT("174") }, + { UCHAR_CONSTANT("175") }, + { UCHAR_CONSTANT("176") }, + { UCHAR_CONSTANT("177") }, + { UCHAR_CONSTANT("178") }, + { UCHAR_CONSTANT("179") }, + { UCHAR_CONSTANT("180") }, + { UCHAR_CONSTANT("181") }, + { UCHAR_CONSTANT("182") }, + { UCHAR_CONSTANT("183") }, + { UCHAR_CONSTANT("184") }, + { UCHAR_CONSTANT("185") }, + { UCHAR_CONSTANT("186") }, + { UCHAR_CONSTANT("187") }, + { UCHAR_CONSTANT("188") }, + { UCHAR_CONSTANT("189") }, + { UCHAR_CONSTANT("190") }, + { UCHAR_CONSTANT("191") }, + { UCHAR_CONSTANT("192") }, + { UCHAR_CONSTANT("193") }, + { UCHAR_CONSTANT("194") }, + { UCHAR_CONSTANT("195") }, + { UCHAR_CONSTANT("196") }, + { UCHAR_CONSTANT("197") }, + { UCHAR_CONSTANT("198") }, + { UCHAR_CONSTANT("199") } + }; +static char hexdigit[16] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +#if defined(_AIX) +/* AIXPORT : replace facility names with aso and caa only for AIX */ +static const char *syslog_fac_names[LOG_NFACILITIES] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", + "news", "uucp", "cron", "authpriv", "ftp", "aso", "audit", + "alert", "caa", "local0", "local1", "local2", "local3", + "local4", "local5", "local6", "local7", "invld" }; +/* length of the facility names string (for optimizatiions) */ +static short len_syslog_fac_names[LOG_NFACILITIES] = { 4, 4, 4, 6, 4, 6, 3, + 4, 4, 4, 8, 3, 3, 5, + 5, 3, 6, 6, 6, 6, + 6, 6, 6, 6, 5 }; + +#else +/*syslog facility names (as of RFC5424) */ +static const char *syslog_fac_names[LOG_NFACILITIES] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr", + "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit", + "alert", "clock", "local0", "local1", "local2", "local3", + "local4", "local5", "local6", "local7", "invld" }; +/* length of the facility names string (for optimizatiions) */ +static short len_syslog_fac_names[LOG_NFACILITIES] = { 4, 4, 4, 6, 4, 6, 3, + 4, 4, 4, 8, 3, 3, 5, + 5, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 5 }; +#endif + +/* table of severity names (in numerical order)*/ +static const char *syslog_severity_names[8] = { "emerg", "alert", "crit", "err", "warning", "notice", "info", + "debug" }; +static short len_syslog_severity_names[8] = { 5, 5, 4, 3, 7, 6, 4, 5 }; + +/* numerical values as string - this is the most efficient approach to convert severity + * and facility values to a numerical string... -- rgerhars, 2009-06-17 + */ + +static const char *syslog_number_names[LOG_NFACILITIES] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", + "15", "16", "17", "18", "19", "20", "21", "22", "23", "24" }; + +/* global variables */ +#if defined(HAVE_MALLOC_TRIM) && !defined(HAVE_ATOMIC_BUILTINS) +static pthread_mutex_t mutTrimCtr; /* mutex to handle malloc trim */ +#endif + +/* some forward declarations */ +static int getAPPNAMELen(smsg_t * const pM, sbool bLockMutex); +static rsRetVal jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf, + struct json_object **parent, int bCreate); +static uchar * jsonPathGetLeaf(uchar *name, int lenName); +struct json_object *jsonDeepCopy(struct json_object *src); +static json_bool jsonVarExtract(struct json_object* root, const char *key, struct json_object **value); +void getRawMsgAfterPRI(smsg_t * const pM, uchar **pBuf, int *piLen); + + +/* the locking and unlocking implementations: */ +static inline void +MsgLock(smsg_t *pThis) +{ + #if DEV_DEBUG == 1 + dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); + #endif + pthread_mutex_lock(&pThis->mut); +} +static inline void +MsgUnlock(smsg_t *pThis) +{ + #if DEV_DEBUG == 1 + dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); + #endif + pthread_mutex_unlock(&pThis->mut); +} + + +/* set RcvFromIP name in msg object WITHOUT calling AddRef. + * rgerhards, 2013-01-22 + */ +static inline void +MsgSetRcvFromIPWithoutAddRef(smsg_t *pThis, prop_t *new) +{ + if(pThis->pRcvFromIP != NULL) + prop.Destruct(&pThis->pRcvFromIP); + pThis->pRcvFromIP = new; +} + + +/* set RcvFrom name in msg object WITHOUT calling AddRef. + * rgerhards, 2013-01-22 + */ +static void MsgSetRcvFromWithoutAddRef(smsg_t *pThis, prop_t *new) +{ + assert(pThis != NULL); + + if(pThis->msgFlags & NEEDS_DNSRESOL) { + if(pThis->rcvFrom.pfrominet != NULL) + free(pThis->rcvFrom.pfrominet); + pThis->msgFlags &= ~NEEDS_DNSRESOL; + } else { + if(pThis->rcvFrom.pRcvFrom != NULL) + prop.Destruct(&pThis->rcvFrom.pRcvFrom); + } + pThis->rcvFrom.pRcvFrom = new; +} + + +/* rgerhards 2012-04-18: set associated ruleset (by ruleset name) + * pRuleset pointer inside msg is updated. If ruleset cannot be found, + * no update is done and an error message emitted. + */ +static void ATTR_NONNULL() +MsgSetRulesetByName(smsg_t * const pMsg, cstr_t *const rulesetName) +{ + uchar *const rs_name = rsCStrGetSzStrNoNULL(rulesetName); + const rsRetVal localRet = + rulesetGetRuleset(runConf, &(pMsg->pRuleset), rs_name); + + if(localRet != RS_RET_OK) { + LogError(0, localRet, "msg: ruleset '%s' could not be found and could not " + "be assigned to message object. This possibly leads to the message " + "being processed incorrectly. We cannot do anything against this, but " + "wanted to let you know.", rs_name); + } +} + +/* do a DNS reverse resolution, if not already done, reflect status + * rgerhards, 2009-11-16 + */ +static rsRetVal +resolveDNS(smsg_t * const pMsg) { + rsRetVal localRet; + prop_t *propFromHost = NULL; + prop_t *ip; + prop_t *localName; + DEFiRet; + + MsgLock(pMsg); + CHKiRet(objUse(net, CORE_COMPONENT)); + if(pMsg->msgFlags & NEEDS_DNSRESOL) { + if (pMsg->msgFlags & PRESERVE_CASE) { + localRet = net.cvthname(pMsg->rcvFrom.pfrominet, NULL, &localName, &ip); + } else { + localRet = net.cvthname(pMsg->rcvFrom.pfrominet, &localName, NULL, &ip); + } + if(localRet == RS_RET_OK) { + /* we pass down the props, so no need for AddRef */ + MsgSetRcvFromWithoutAddRef(pMsg, localName); + MsgSetRcvFromIPWithoutAddRef(pMsg, ip); + } + } +finalize_it: + if(iRet != RS_RET_OK) { + /* best we can do: remove property */ + MsgSetRcvFromStr(pMsg, UCHAR_CONSTANT(""), 0, &propFromHost); + prop.Destruct(&propFromHost); + } + MsgUnlock(pMsg); + if(propFromHost != NULL) + prop.Destruct(&propFromHost); + RETiRet; +} + + +void +getInputName(const smsg_t * const pM, uchar **ppsz, int *const plen) +{ + if(pM == NULL || pM->pInputName == NULL) { + *ppsz = UCHAR_CONSTANT(""); + *plen = 0; + } else { + prop.GetString(pM->pInputName, ppsz, plen); + } +} + + +static uchar* +getRcvFromIP(smsg_t * const pM) +{ + uchar *psz; + int len; + if(pM == NULL) { + psz = UCHAR_CONSTANT(""); + } else { + resolveDNS(pM); /* make sure we have a resolved entry */ + if(pM->pRcvFromIP == NULL) + psz = UCHAR_CONSTANT(""); + else + prop.GetString(pM->pRcvFromIP, &psz, &len); + } + return psz; +} + + +/* map a property name (string) to a property ID */ +rsRetVal +propNameToID(const uchar *const pName, propid_t *const pPropID) +{ + DEFiRet; + + /* sometimes there are aliases to the original MonitoWare + * property names. These come after || in the ifs below. */ + if(!strcasecmp((char*) pName, "msg")) { + *pPropID = PROP_MSG; + } else if(!strcasecmp((char*) pName, "timestamp") + || !strcasecmp((char*) pName, "timereported")) { + *pPropID = PROP_TIMESTAMP; + } else if(!strcasecmp((char*) pName, "hostname") || !strcasecmp((char*) pName, "source")) { + *pPropID = PROP_HOSTNAME; + } else if(!strcasecmp((char*) pName, "syslogtag")) { + *pPropID = PROP_SYSLOGTAG; + } else if(!strcasecmp((char*) pName, "rawmsg")) { + *pPropID = PROP_RAWMSG; + } else if(!strcasecmp((char*) pName, "rawmsg-after-pri")) { + *pPropID = PROP_RAWMSG_AFTER_PRI; + } else if(!strcasecmp((char*) pName, "inputname")) { + *pPropID = PROP_INPUTNAME; + } else if(!strcasecmp((char*) pName, "fromhost")) { + *pPropID = PROP_FROMHOST; + } else if(!strcasecmp((char*) pName, "fromhost-ip")) { + *pPropID = PROP_FROMHOST_IP; + } else if(!strcasecmp((char*) pName, "pri")) { + *pPropID = PROP_PRI; + } else if(!strcasecmp((char*) pName, "pri-text")) { + *pPropID = PROP_PRI_TEXT; + } else if(!strcasecmp((char*) pName, "iut")) { + *pPropID = PROP_IUT; + } else if(!strcasecmp((char*) pName, "syslogfacility")) { + *pPropID = PROP_SYSLOGFACILITY; + } else if(!strcasecmp((char*) pName, "syslogfacility-text")) { + *pPropID = PROP_SYSLOGFACILITY_TEXT; + } else if(!strcasecmp((char*) pName, "syslogseverity") || !strcasecmp((char*) pName, "syslogpriority")) { + *pPropID = PROP_SYSLOGSEVERITY; + } else if(!strcasecmp((char*) pName, "syslogseverity-text") || + !strcasecmp((char*) pName, "syslogpriority-text")) { + *pPropID = PROP_SYSLOGSEVERITY_TEXT; + } else if(!strcasecmp((char*) pName, "timegenerated")) { + *pPropID = PROP_TIMEGENERATED; + } else if(!strcasecmp((char*) pName, "programname")) { + *pPropID = PROP_PROGRAMNAME; + } else if(!strcasecmp((char*) pName, "protocol-version")) { + *pPropID = PROP_PROTOCOL_VERSION; + } else if(!strcasecmp((char*) pName, "structured-data")) { + *pPropID = PROP_STRUCTURED_DATA; + } else if(!strcasecmp((char*) pName, "app-name")) { + *pPropID = PROP_APP_NAME; + } else if(!strcasecmp((char*) pName, "procid")) { + *pPropID = PROP_PROCID; + } else if(!strcasecmp((char*) pName, "msgid")) { + *pPropID = PROP_MSGID; + } else if(!strcasecmp((char*) pName, "jsonmesg")) { + *pPropID = PROP_JSONMESG; + } else if(!strcasecmp((char*) pName, "parsesuccess")) { + *pPropID = PROP_PARSESUCCESS; +#ifdef USE_LIBUUID + } else if(!strcasecmp((char*) pName, "uuid")) { + *pPropID = PROP_UUID; +#endif + /* here start system properties (those, that do not relate to the message itself */ + } else if(!strcasecmp((char*) pName, "$NOW")) { + *pPropID = PROP_SYS_NOW; + } else if(!strcasecmp((char*) pName, "$YEAR")) { + *pPropID = PROP_SYS_YEAR; + } else if(!strcasecmp((char*) pName, "$MONTH")) { + *pPropID = PROP_SYS_MONTH; + } else if(!strcasecmp((char*) pName, "$DAY")) { + *pPropID = PROP_SYS_DAY; + } else if(!strcasecmp((char*) pName, "$HOUR")) { + *pPropID = PROP_SYS_HOUR; + } else if(!strcasecmp((char*) pName, "$HHOUR")) { + *pPropID = PROP_SYS_HHOUR; + } else if(!strcasecmp((char*) pName, "$QHOUR")) { + *pPropID = PROP_SYS_QHOUR; + } else if(!strcasecmp((char*) pName, "$MINUTE")) { + *pPropID = PROP_SYS_MINUTE; + } else if(!strcasecmp((char*) pName, "$WDAY")) { + *pPropID = PROP_SYS_WDAY; + } else if(!strcasecmp((char*) pName, "$now-utc")) { + *pPropID = PROP_SYS_NOW_UTC; + } else if(!strcasecmp((char*) pName, "$year-utc")) { + *pPropID = PROP_SYS_YEAR_UTC; + } else if(!strcasecmp((char*) pName, "$month-utc")) { + *pPropID = PROP_SYS_MONTH_UTC; + } else if(!strcasecmp((char*) pName, "$day-utc")) { + *pPropID = PROP_SYS_DAY_UTC; + } else if(!strcasecmp((char*) pName, "$hour-utc")) { + *pPropID = PROP_SYS_HOUR_UTC; + } else if(!strcasecmp((char*) pName, "$hhour-utc")) { + *pPropID = PROP_SYS_HHOUR_UTC; + } else if(!strcasecmp((char*) pName, "$qhour-utc")) { + *pPropID = PROP_SYS_QHOUR_UTC; + } else if(!strcasecmp((char*) pName, "$minute-utc")) { + *pPropID = PROP_SYS_MINUTE_UTC; + } else if(!strcasecmp((char*) pName, "$wday-utc")) { + *pPropID = PROP_SYS_WDAY_UTC; + } else if(!strcasecmp((char*) pName, "$now-unixtimestamp")) { + *pPropID = PROP_SYS_NOW_UXTIMESTAMP; + } else if(!strcasecmp((char*) pName, "$MYHOSTNAME")) { + *pPropID = PROP_SYS_MYHOSTNAME; + } else if(!strcasecmp((char*) pName, "$!all-json")) { + *pPropID = PROP_CEE_ALL_JSON; + } else if(!strcasecmp((char*) pName, "$!all-json-plain")) { + *pPropID = PROP_CEE_ALL_JSON_PLAIN; + } else if(!strcasecmp((char*) pName, "$BOM")) { + *pPropID = PROP_SYS_BOM; + } else if(!strcasecmp((char*) pName, "$UPTIME")) { + *pPropID = PROP_SYS_UPTIME; + } else if(!strncmp((char*) pName, "$!", 2) || pName[0] == '!') { + *pPropID = PROP_CEE; + } else if(!strncmp((char*) pName, "$.", 2) || pName[0] == '.') { + *pPropID = PROP_LOCAL_VAR; + } else if(!strncmp((char*) pName, "$/", 2) || pName[0] == '/') { + *pPropID = PROP_GLOBAL_VAR; + } else { + DBGPRINTF("PROP_INVALID for name '%s'\n", pName); + *pPropID = PROP_INVALID; + iRet = RS_RET_VAR_NOT_FOUND; + } + + RETiRet; +} + + +/* map a property ID to a name string (useful for displaying) */ +uchar *propIDToName(propid_t propID) +{ + switch(propID) { + case PROP_MSG: + return UCHAR_CONSTANT("msg"); + case PROP_TIMESTAMP: + return UCHAR_CONSTANT("timestamp"); + case PROP_HOSTNAME: + return UCHAR_CONSTANT("hostname"); + case PROP_SYSLOGTAG: + return UCHAR_CONSTANT("syslogtag"); + case PROP_RAWMSG: + return UCHAR_CONSTANT("rawmsg"); + case PROP_RAWMSG_AFTER_PRI: + return UCHAR_CONSTANT("rawmsg-after-pri"); + case PROP_INPUTNAME: + return UCHAR_CONSTANT("inputname"); + case PROP_FROMHOST: + return UCHAR_CONSTANT("fromhost"); + case PROP_FROMHOST_IP: + return UCHAR_CONSTANT("fromhost-ip"); + case PROP_PRI: + return UCHAR_CONSTANT("pri"); + case PROP_PRI_TEXT: + return UCHAR_CONSTANT("pri-text"); + case PROP_IUT: + return UCHAR_CONSTANT("iut"); + case PROP_SYSLOGFACILITY: + return UCHAR_CONSTANT("syslogfacility"); + case PROP_SYSLOGFACILITY_TEXT: + return UCHAR_CONSTANT("syslogfacility-text"); + case PROP_SYSLOGSEVERITY: + return UCHAR_CONSTANT("syslogseverity"); + case PROP_SYSLOGSEVERITY_TEXT: + return UCHAR_CONSTANT("syslogseverity-text"); + case PROP_TIMEGENERATED: + return UCHAR_CONSTANT("timegenerated"); + case PROP_PROGRAMNAME: + return UCHAR_CONSTANT("programname"); + case PROP_PROTOCOL_VERSION: + return UCHAR_CONSTANT("protocol-version"); + case PROP_STRUCTURED_DATA: + return UCHAR_CONSTANT("structured-data"); + case PROP_APP_NAME: + return UCHAR_CONSTANT("app-name"); + case PROP_PROCID: + return UCHAR_CONSTANT("procid"); + case PROP_MSGID: + return UCHAR_CONSTANT("msgid"); + case PROP_JSONMESG: + return UCHAR_CONSTANT("jsonmesg"); + case PROP_PARSESUCCESS: + return UCHAR_CONSTANT("parsesuccess"); +#ifdef USE_LIBUUID + case PROP_UUID: + return UCHAR_CONSTANT("uuid"); +#endif + case PROP_SYS_NOW: + return UCHAR_CONSTANT("$NOW"); + case PROP_SYS_YEAR: + return UCHAR_CONSTANT("$YEAR"); + case PROP_SYS_MONTH: + return UCHAR_CONSTANT("$MONTH"); + case PROP_SYS_DAY: + return UCHAR_CONSTANT("$DAY"); + case PROP_SYS_HOUR: + return UCHAR_CONSTANT("$HOUR"); + case PROP_SYS_HHOUR: + return UCHAR_CONSTANT("$HHOUR"); + case PROP_SYS_QHOUR: + return UCHAR_CONSTANT("$QHOUR"); + case PROP_SYS_MINUTE: + return UCHAR_CONSTANT("$MINUTE"); + case PROP_SYS_NOW_UTC: + return UCHAR_CONSTANT("$NOW-UTC"); + case PROP_SYS_YEAR_UTC: + return UCHAR_CONSTANT("$YEAR-UTC"); + case PROP_SYS_MONTH_UTC: + return UCHAR_CONSTANT("$MONTH-UTC"); + case PROP_SYS_DAY_UTC: + return UCHAR_CONSTANT("$DAY-UTC"); + case PROP_SYS_HOUR_UTC: + return UCHAR_CONSTANT("$HOUR-UTC"); + case PROP_SYS_HHOUR_UTC: + return UCHAR_CONSTANT("$HHOUR-UTC"); + case PROP_SYS_QHOUR_UTC: + return UCHAR_CONSTANT("$QHOUR-UTC"); + case PROP_SYS_MINUTE_UTC: + return UCHAR_CONSTANT("$MINUTE-UTC"); + case PROP_SYS_WDAY: + return UCHAR_CONSTANT("$WDAY"); + case PROP_SYS_WDAY_UTC: + return UCHAR_CONSTANT("$WDAY-UTC"); + case PROP_SYS_NOW_UXTIMESTAMP: + return UCHAR_CONSTANT("$NOW-UNIXTIMESTAMP"); + case PROP_SYS_MYHOSTNAME: + return UCHAR_CONSTANT("$MYHOSTNAME"); + case PROP_CEE_ALL_JSON: + return UCHAR_CONSTANT("$!all-json"); + case PROP_CEE_ALL_JSON_PLAIN: + return UCHAR_CONSTANT("$!all-json-plain"); + case PROP_SYS_BOM: + return UCHAR_CONSTANT("$BOM"); + case PROP_SYS_UPTIME: + return UCHAR_CONSTANT("$UPTIME"); + case PROP_CEE: + return UCHAR_CONSTANT("*CEE-based property*"); + case PROP_LOCAL_VAR: + return UCHAR_CONSTANT("*LOCAL_VARIABLE*"); + case PROP_GLOBAL_VAR: + return UCHAR_CONSTANT("*GLOBAL_VARIABLE*"); + default: + return UCHAR_CONSTANT("*invalid property id*"); + } +} + + +/* This is common code for all Constructors. It is defined in an + * inline'able function so that we can save a function call in the + * actual constructors (otherwise, the msgConstruct would need + * to call msgConstructWithTime(), which would require a + * function call). Now, both can use this inline function. This + * enables us to be optimal, but still have the code just once. + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor does not query system time + * itself but rather uses a user-supplied value. This enables the caller + * to do some tricks to save processing time (done, for example, in the + * udp input). + * NOTE: this constructor does NOT call calloc(), as we have many bytes + * inside the structure which do not need to be cleared. bzero() will + * heavily thrash the cache, so we do the init manually (which also + * is the right thing to do with pointers, as they are not neccessarily + * a binary 0 on all machines [but today almost always...]). + * rgerhards, 2008-10-06 + */ +static rsRetVal +msgBaseConstruct(smsg_t **ppThis) +{ + DEFiRet; + smsg_t *pM; + + assert(ppThis != NULL); + CHKmalloc(pM = malloc(sizeof(smsg_t))); + objConstructSetObjInfo(pM); /* intialize object helper entities */ + + /* initialize members in ORDER they appear in structure (think "cache line"!) */ + pM->flowCtlType = 0; + pM->bParseSuccess = 0; + pM->iRefCount = 1; + pM->iSeverity = LOG_DEBUG; + pM->iFacility = LOG_INVLD; + pM->iLenPROGNAME = -1; + pM->offAfterPRI = 0; + pM->offMSG = -1; + pM->iProtocolVersion = 0; + pM->msgFlags = 0; + pM->iLenRawMsg = 0; + pM->iLenMSG = 0; + pM->iLenTAG = 0; + pM->iLenHOSTNAME = 0; + pM->pszRawMsg = NULL; + pM->pszHOSTNAME = NULL; + pM->pszRcvdAt3164 = NULL; + pM->pszRcvdAt3339 = NULL; + pM->pszRcvdAt_MySQL = NULL; + pM->pszRcvdAt_PgSQL = NULL; + pM->pszTIMESTAMP3164 = NULL; + pM->pszTIMESTAMP3339 = NULL; + pM->pszTIMESTAMP_MySQL = NULL; + pM->pszTIMESTAMP_PgSQL = NULL; + pM->pszStrucData = NULL; + pM->lenStrucData = 0; + pM->pCSAPPNAME = NULL; + pM->pCSPROCID = NULL; + pM->pCSMSGID = NULL; + pM->pInputName = NULL; + pM->pRcvFromIP = NULL; + pM->rcvFrom.pRcvFrom = NULL; + pM->pRuleset = NULL; + pM->json = NULL; + pM->localvars = NULL; + pM->dfltTZ[0] = '\0'; + memset(&pM->tRcvdAt, 0, sizeof(pM->tRcvdAt)); + memset(&pM->tTIMESTAMP, 0, sizeof(pM->tTIMESTAMP)); + pM->TAG.pszTAG = NULL; + pM->pszTimestamp3164[0] = '\0'; + pM->pszTimestamp3339[0] = '\0'; + pM->pszTIMESTAMP_SecFrac[0] = '\0'; + pM->pszRcvdAt_SecFrac[0] = '\0'; + pM->pszTIMESTAMP_Unix[0] = '\0'; + pM->pszRcvdAt_Unix[0] = '\0'; + pM->pszUUID = NULL; + pthread_mutex_init(&pM->mut, NULL); + + #if DEV_DEBUG == 1 + dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM); + #endif + + *ppThis = pM; + +finalize_it: + RETiRet; +} + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor does not query system time + * itself but rather uses a user-supplied value. This enables the caller + * to do some tricks to save processing time (done, for example, in the + * udp input). + * rgerhards, 2008-10-06 + */ +rsRetVal msgConstructWithTime(smsg_t **ppThis, const struct syslogTime *stTime, const time_t ttGenTime) +{ + DEFiRet; + + CHKiRet(msgBaseConstruct(ppThis)); + (*ppThis)->ttGenTime = ttGenTime; + memcpy(&(*ppThis)->tRcvdAt, stTime, sizeof(struct syslogTime)); + memcpy(&(*ppThis)->tTIMESTAMP, stTime, sizeof(struct syslogTime)); + +finalize_it: + RETiRet; +} + + +/* "Constructor" for a msg "object". Returns a pointer to + * the new object or NULL if no such object could be allocated. + * An object constructed via this function should only be destroyed + * via "msgDestruct()". This constructor, for historical reasons, + * also sets the two timestamps to the current time. + */ +rsRetVal msgConstruct(smsg_t **ppThis) +{ + DEFiRet; + + CHKiRet(msgBaseConstruct(ppThis)); + /* we initialize both timestamps to contain the current time, so that they + * are consistent. Also, this saves us from doing any further time calls just + * to obtain a timestamp. The memcpy() should not really make a difference, + * especially as I think there is no codepath currently where it would not be + * required (after I have cleaned up the pathes ;)). -- rgerhards, 2008-10-02 + */ + datetime.getCurrTime(&((*ppThis)->tRcvdAt), &((*ppThis)->ttGenTime), TIME_IN_LOCALTIME); + memcpy(&(*ppThis)->tTIMESTAMP, &(*ppThis)->tRcvdAt, sizeof(struct syslogTime)); + +finalize_it: + RETiRet; +} + + +/* Special msg constructor, to be used when an object is deserialized. + * we do only the base init as we know the properties will be set in + * any case by the deserializer. We still do the "inexpensive" inits + * just to be on the safe side. The whole process needs to be + * refactored together with the msg serialization subsystem. + */ +rsRetVal +msgConstructForDeserializer(smsg_t **ppThis) +{ + return msgBaseConstruct(ppThis); +} + + +/* some free handlers for (slightly) complicated cases... All of them may be called + * with an empty element. + */ +static inline void freeTAG(smsg_t *pThis) +{ + if(pThis->iLenTAG >= CONF_TAG_BUFSIZE) + free(pThis->TAG.pszTAG); +} +static inline void freeHOSTNAME(smsg_t *pThis) +{ + if(pThis->iLenHOSTNAME >= CONF_HOSTNAME_BUFSIZE) + free(pThis->pszHOSTNAME); +} + + +rsRetVal msgDestruct(smsg_t **ppThis) +{ + DEFiRet; + smsg_t *pThis; + int currRefCount; +# ifdef HAVE_MALLOC_TRIM + int currCnt; +# endif +CODESTARTobjDestruct(msg) + #if DEV_DEBUG == 1 + dbgprintf("msgDestruct\t0x%lx, " + "Ref now: %d\n", (unsigned long)pThis, pThis->iRefCount - 1); + #endif +# ifdef HAVE_ATOMIC_BUILTINS + currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, NULL); +# else + MsgLock(pThis); + currRefCount = --pThis->iRefCount; +# endif + if(currRefCount == 0) + { + #if DEV_DEBUG == 1 + dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", + (unsigned long)pThis); + #endif + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + freeTAG(pThis); + freeHOSTNAME(pThis); + if(pThis->pInputName != NULL) + prop.Destruct(&pThis->pInputName); + if((pThis->msgFlags & NEEDS_DNSRESOL) == 0) { + if(pThis->rcvFrom.pRcvFrom != NULL) + prop.Destruct(&pThis->rcvFrom.pRcvFrom); + } else { + free(pThis->rcvFrom.pfrominet); + } + if(pThis->pRcvFromIP != NULL) + prop.Destruct(&pThis->pRcvFromIP); + free(pThis->pszRcvdAt3164); + free(pThis->pszRcvdAt3339); + free(pThis->pszRcvdAt_MySQL); + free(pThis->pszRcvdAt_PgSQL); + free(pThis->pszTIMESTAMP_MySQL); + free(pThis->pszTIMESTAMP_PgSQL); + free(pThis->pszStrucData); + if(pThis->iLenPROGNAME >= CONF_PROGNAME_BUFSIZE) + free(pThis->PROGNAME.ptr); + if(pThis->pCSAPPNAME != NULL) + rsCStrDestruct(&pThis->pCSAPPNAME); + if(pThis->pCSPROCID != NULL) + rsCStrDestruct(&pThis->pCSPROCID); + if(pThis->pCSMSGID != NULL) + rsCStrDestruct(&pThis->pCSMSGID); + if(pThis->json != NULL) + json_object_put(pThis->json); + if(pThis->localvars != NULL) + json_object_put(pThis->localvars); + if(pThis->pszUUID != NULL) + free(pThis->pszUUID); +# ifndef HAVE_ATOMIC_BUILTINS + MsgUnlock(pThis); +# endif + pthread_mutex_destroy(&pThis->mut); + /* now we need to do our own optimization. Testing has shown that at least the glibc + * malloc() subsystem returns memory to the OS far too late in our case. So we need + * to help it a bit, by calling malloc_trim(), which will tell the alloc subsystem + * to consolidate and return to the OS. We keep 128K for our use, as a safeguard + * to too-frequent reallocs. But more importantly, we call this hook only every + * 100,000 messages (which is an approximation, as we do not work with atomic + * operations on the counter. --- rgerhards, 2009-06-22. + */ +# ifdef HAVE_MALLOC_TRIM + { /* standard C requires a new block for a new variable definition! + * To simplify matters, we use modulo arithmetic and live with the fact + * that we trim too often when the counter wraps. + */ + static unsigned iTrimCtr = 1; + currCnt = ATOMIC_INC_AND_FETCH_unsigned(&iTrimCtr, &mutTrimCtr); + if(currCnt % 100000 == 0) { + malloc_trim(128*1024); + } + } +# endif + } else { +# ifndef HAVE_ATOMIC_BUILTINS + MsgUnlock(pThis); +# endif + pThis = NULL; /* tell framework not to destructing the object! */ + } +ENDobjDestruct(msg) + +/* The macros below are used in MsgDup(). I use macros + * to keep the fuction code somewhat more readyble. It is my + * replacement for inline functions in CPP + */ +#define tmpCOPYSZ(name) \ + if(pOld->psz##name != NULL) { \ + if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + pNew->iLen##name = pOld->iLen##name;\ + } + +/* copy the CStr objects. + * if the old value is NULL, we do not need to do anything because we + * initialized the new value to NULL via calloc(). + */ +#define tmpCOPYCSTR(name) \ + if(pOld->pCS##name != NULL) {\ + if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\ + msgDestruct(&pNew);\ + return NULL;\ + }\ + cstrFinalize(pNew->pCS##name); \ + } +/* Constructs a message object by duplicating another one. + * Returns NULL if duplication failed. We do not need to lock the + * message object here, because a fully-created msg object is never + * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup() + * can never run into a situation where the message object is being + * modified while its content is copied - it's forbidden by definition. + * rgerhards, 2007-07-10 + */ +smsg_t* MsgDup(smsg_t* pOld) +{ + smsg_t* pNew; + rsRetVal localRet; + + assert(pOld != NULL); + + if(msgConstructWithTime(&pNew, &pOld->tTIMESTAMP, pOld->ttGenTime) != RS_RET_OK) { + return NULL; + } + + /* now copy the message properties */ + pNew->iRefCount = 1; + pNew->iSeverity = pOld->iSeverity; + pNew->iFacility = pOld->iFacility; + pNew->msgFlags = pOld->msgFlags; + pNew->iProtocolVersion = pOld->iProtocolVersion; + pNew->tRcvdAt = pOld->tRcvdAt; + pNew->offMSG = pOld->offMSG; + pNew->iLenRawMsg = pOld->iLenRawMsg; + pNew->iLenMSG = pOld->iLenMSG; + pNew->iLenTAG = pOld->iLenTAG; + pNew->iLenHOSTNAME = pOld->iLenHOSTNAME; + if((pOld->msgFlags & NEEDS_DNSRESOL)) { + localRet = msgSetFromSockinfo(pNew, pOld->rcvFrom.pfrominet); + if(localRet != RS_RET_OK) { + /* if something fails, we accept loss of this property, it is + * better than losing the whole message. + */ + pNew->msgFlags &= ~NEEDS_DNSRESOL; + pNew->rcvFrom.pRcvFrom = NULL; /* make sure no dangling values */ + } + } else { + if(pOld->rcvFrom.pRcvFrom != NULL) { + pNew->rcvFrom.pRcvFrom = pOld->rcvFrom.pRcvFrom; + prop.AddRef(pNew->rcvFrom.pRcvFrom); + } + } + if(pOld->pRcvFromIP != NULL) { + pNew->pRcvFromIP = pOld->pRcvFromIP; + prop.AddRef(pNew->pRcvFromIP); + } + if(pOld->pInputName != NULL) { + pNew->pInputName = pOld->pInputName; + prop.AddRef(pNew->pInputName); + } + if(pOld->iLenTAG > 0) { + if(pOld->iLenTAG < CONF_TAG_BUFSIZE) { + memcpy(pNew->TAG.szBuf, pOld->TAG.szBuf, pOld->iLenTAG + 1); + } else { + if((pNew->TAG.pszTAG = srUtilStrDup(pOld->TAG.pszTAG, pOld->iLenTAG)) == NULL) { + msgDestruct(&pNew); + return NULL; + } + pNew->iLenTAG = pOld->iLenTAG; + } + } + if(pOld->pszRawMsg == pOld->szRawMsg) { + memcpy(pNew->szRawMsg, pOld->szRawMsg, pOld->iLenRawMsg + 1); + pNew->pszRawMsg = pNew->szRawMsg; + } else { + tmpCOPYSZ(RawMsg); + } + if(pOld->pszHOSTNAME == NULL) { + pNew->pszHOSTNAME = NULL; + } else { + if(pOld->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) { + memcpy(pNew->szHOSTNAME, pOld->szHOSTNAME, pOld->iLenHOSTNAME + 1); + pNew->pszHOSTNAME = pNew->szHOSTNAME; + } else { + tmpCOPYSZ(HOSTNAME); + } + } + if(pOld->pszStrucData == NULL) { + pNew->pszStrucData = NULL; + } else { + pNew->pszStrucData = (uchar*)strdup((char*)pOld->pszStrucData); + pNew->lenStrucData = pOld->lenStrucData; + } + + tmpCOPYCSTR(APPNAME); + tmpCOPYCSTR(PROCID); + tmpCOPYCSTR(MSGID); + + if(pOld->json != NULL) + pNew->json = jsonDeepCopy(pOld->json); + if(pOld->localvars != NULL) + pNew->localvars = jsonDeepCopy(pOld->localvars); + + /* we do not copy all other cache properties, as we do not even know + * if they are needed once again. So we let them re-create if needed. + */ + + return pNew; +} +#undef tmpCOPYSZ +#undef tmpCOPYCSTR + + +/* This method serializes a message object. That means the whole + * object is modified into text form. That text form is suitable for + * later reconstruction of the object by calling MsgDeSerialize(). + * The most common use case for this method is the creation of an + * on-disk representation of the message object. + * We do not serialize the cache properties. We re-create them when needed. + * This saves us a lot of memory. Performance is no concern, as serializing + * is a so slow operation that recration of the caches does not count. Also, + * we do not serialize --currently none--, as this is only a helper variable + * during msg construction - and never again used later. + * rgerhards, 2008-01-03 + */ +static rsRetVal MsgSerialize(smsg_t *pThis, strm_t *pStrm) +{ + uchar *psz; + int len; + DEFiRet; + + assert(pThis != NULL); + assert(pStrm != NULL); + + /* then serialize elements */ + CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis)); + objSerializeSCALAR(pStrm, iProtocolVersion, SHORT); + objSerializeSCALAR(pStrm, iSeverity, SHORT); + objSerializeSCALAR(pStrm, iFacility, SHORT); + objSerializeSCALAR(pStrm, msgFlags, INT); + objSerializeSCALAR(pStrm, ttGenTime, INT); + objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME); + objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME); + + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszTAG"), PROPTYPE_PSZ, (void*) + ((pThis->iLenTAG < CONF_TAG_BUFSIZE) ? pThis->TAG.szBuf : pThis->TAG.pszTAG))); + + objSerializePTR(pStrm, pszRawMsg, PSZ); + objSerializePTR(pStrm, pszHOSTNAME, PSZ); + getInputName(pThis, &psz, &len); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszInputName"), PROPTYPE_PSZ, (void*) psz)); + psz = getRcvFrom(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFrom"), PROPTYPE_PSZ, (void*) psz)); + psz = getRcvFromIP(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFromIP"), PROPTYPE_PSZ, (void*) psz)); + psz = pThis->pszStrucData; + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszStrucData"), PROPTYPE_PSZ, (void*) psz)); + if(pThis->json != NULL) { + MsgLock(pThis); + psz = (uchar*) json_object_get_string(pThis->json); + MsgUnlock(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("json"), PROPTYPE_PSZ, (void*) psz)); + } + if(pThis->localvars != NULL) { + MsgLock(pThis); + psz = (uchar*) json_object_get_string(pThis->localvars); + MsgUnlock(pThis); + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("localvars"), PROPTYPE_PSZ, (void*) psz)); + } + + objSerializePTR(pStrm, pCSAPPNAME, CSTR); + objSerializePTR(pStrm, pCSPROCID, CSTR); + objSerializePTR(pStrm, pCSMSGID, CSTR); + + objSerializePTR(pStrm, pszUUID, PSZ); + + if(pThis->pRuleset != NULL) { + CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRuleset"), PROPTYPE_PSZ, + rulesetGetName(pThis->pRuleset))); + } + + /* offset must be serialized after pszRawMsg, because we need that to obtain the correct + * MSG size. + */ + objSerializeSCALAR(pStrm, offMSG, INT); + + CHKiRet(obj.EndSerialize(pStrm)); + +finalize_it: + RETiRet; +} + + +/* This is a helper for MsgDeserialize that re-inits the var object. This + * whole construct should be replaced, var is really ready to be retired. + * But as an interim help during refactoring let's introduce this function + * here (and thus NOT as method of var object!). -- rgerhads, 2012-11-06 + */ +static void +reinitVar(var_t *pVar) +{ + rsCStrDestruct(&pVar->pcsName); /* no longer needed */ + if(pVar->varType == VARTYPE_STR) { + if(pVar->val.pStr != NULL) + rsCStrDestruct(&pVar->val.pStr); + } +} +/* deserialize the message again + * we deserialize the properties in the same order that we serialized them. Except + * for some checks to cover downlevel version, we do not need to do all these + * CPU intense name checkings. + */ +#define isProp(name) !rsCStrSzStrCmp(pVar->pcsName, (uchar*) name, sizeof(name) - 1) +rsRetVal +MsgDeserialize(smsg_t * const pMsg, strm_t *pStrm) +{ + prop_t *myProp; + prop_t *propRcvFrom = NULL; + prop_t *propRcvFromIP = NULL; + struct json_tokener *tokener; + var_t *pVar = NULL; + DEFiRet; + + ISOBJ_TYPE_assert(pStrm, strm); + + CHKiRet(var.Construct(&pVar)); + CHKiRet(var.ConstructFinalize(pVar)); + + CHKiRet(objDeserializeProperty(pVar, pStrm)); + if(isProp("iProtocolVersion")) { + setProtocolVersion(pMsg, pVar->val.num); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("iSeverity")) { + pMsg->iSeverity = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("iFacility")) { + pMsg->iFacility = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("msgFlags")) { + pMsg->msgFlags = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("ttGenTime")) { + pMsg->ttGenTime = pVar->val.num; + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("tRcvdAt")) { + memcpy(&pMsg->tRcvdAt, &pVar->val.vSyslogTime, sizeof(struct syslogTime)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("tTIMESTAMP")) { + memcpy(&pMsg->tTIMESTAMP, &pVar->val.vSyslogTime, sizeof(struct syslogTime)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszTAG")) { + MsgSetTAG(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRawMsg")) { + MsgSetRawMsg(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszHOSTNAME")) { + MsgSetHOSTNAME(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszInputName")) { + /* we need to create a property */ + CHKiRet(prop.Construct(&myProp)); + CHKiRet(prop.SetString(myProp, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr))); + CHKiRet(prop.ConstructFinalize(myProp)); + MsgSetInputName(pMsg, myProp); + prop.Destruct(&myProp); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRcvFrom")) { + MsgSetRcvFromStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), &propRcvFrom); + prop.Destruct(&propRcvFrom); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRcvFromIP")) { + MsgSetRcvFromIPStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), + &propRcvFromIP); + prop.Destruct(&propRcvFromIP); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszStrucData")) { + MsgSetStructuredData(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("json")) { + tokener = json_tokener_new(); + pMsg->json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr), + cstrLen(pVar->val.pStr)); + json_tokener_free(tokener); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("localvars")) { + tokener = json_tokener_new(); + pMsg->localvars = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr), + cstrLen(pVar->val.pStr)); + json_tokener_free(tokener); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSAPPNAME")) { + MsgSetAPPNAME(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSPROCID")) { + MsgSetPROCID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pCSMSGID")) { + MsgSetMSGID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszUUID")) { + pMsg->pszUUID = ustrdup(rsCStrGetSzStrNoNULL(pVar->val.pStr)); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + if(isProp("pszRuleset")) { + MsgSetRulesetByName(pMsg, pVar->val.pStr); + reinitVar(pVar); + CHKiRet(objDeserializeProperty(pVar, pStrm)); + } + /* "offMSG" must always be our last field, so we use this as an + * indicator if the sequence is correct. This is a bit questionable, + * but on the other hand it works decently AND we will probably replace + * the whole persisted format soon in any case. -- rgerhards, 2012-11-06 + */ + if(!isProp("offMSG")) { + DBGPRINTF("error property: %s\n", rsCStrGetSzStrNoNULL(pVar->pcsName)); + ABORT_FINALIZE(RS_RET_DS_PROP_SEQ_ERR); + } + MsgSetMSGoffs(pMsg, pVar->val.num); +finalize_it: + if(pVar != NULL) + var.Destruct(&pVar); + if(Debug && iRet != RS_RET_OK) { + dbgprintf("MsgDeserialize error %d\n", iRet); + } + RETiRet; +} +#undef isProp + + +/* Increment reference count - see description of the "msg" + * structure for details. As a convenience to developers, + * this method returns the msg pointer that is passed to it. + * It is recommended that it is called as follows: + * + * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer); + */ +smsg_t *MsgAddRef(smsg_t * const pM) +{ + assert(pM != NULL); +# ifdef HAVE_ATOMIC_BUILTINS + ATOMIC_INC(&pM->iRefCount, NULL); +# else + MsgLock(pM); + pM->iRefCount++; + MsgUnlock(pM); +# endif + #if DEV_DEBUG == 1 + dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount); + #endif + return(pM); +} + + +/* This functions tries to acquire the PROCID from TAG. Its primary use is + * when a legacy syslog message has been received and should be forwarded as + * syslog-protocol (or the PROCID is requested for any other reason). + * In legacy syslog, the PROCID is considered to be the character sequence + * between the first [ and the first ]. This usually are digits only, but we + * do not check that. However, if there is no closing ], we do not assume we + * can obtain a PROCID. Take in mind that not every legacy syslog message + * actually has a PROCID. + * rgerhards, 2005-11-24 + * THIS MUST be called with the message lock locked. + */ +static rsRetVal acquirePROCIDFromTAG(smsg_t * const pM) +{ + register int i; + uchar *pszTag; + DEFiRet; + + assert(pM != NULL); + + if(pM->pCSPROCID != NULL) + return RS_RET_OK; /* we are already done ;) */ + + if(msgGetProtocolVersion(pM) != 0) + return RS_RET_OK; /* we can only emulate if we have legacy format */ + + pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG); + + /* find first '['... */ + i = 0; + while((i < pM->iLenTAG) && (pszTag[i] != '[')) + ++i; + if(!(i < pM->iLenTAG)) + return RS_RET_OK; /* no [, so can not emulate... */ + + ++i; /* skip '[' */ + + /* now obtain the PROCID string... */ + CHKiRet(cstrConstruct(&pM->pCSPROCID)); + while((i < pM->iLenTAG) && (pszTag[i] != ']')) { + CHKiRet(cstrAppendChar(pM->pCSPROCID, pszTag[i])); + ++i; + } + + if(!(i < pM->iLenTAG)) { + /* oops... it looked like we had a PROCID, but now it has + * turned out this is not true. In this case, we need to free + * the buffer and simply return. Note that this is NOT an error + * case! + */ + cstrDestruct(&pM->pCSPROCID); + FINALIZE; + } + + /* OK, finally we could obtain a PROCID. So let's use it ;) */ + cstrFinalize(pM->pCSPROCID); + +finalize_it: + RETiRet; +} + + +/* Parse and set the "programname" for a given MSG object. Programname + * is a BSD concept, it is the tag without any instance-specific information. + * Precisely, the programname is terminated by either (whichever occurs first): + * - end of tag + * - nonprintable character + * - ':' + * - '[' + * - '/' + * The above definition has been taken from the FreeBSD syslogd sources. + * + * The program name is not parsed by default, because it is infrequently-used. + * IMPORTANT: A locked message object must be provided, else a crash will occur. + * rgerhards, 2005-10-19 + */ +static rsRetVal +acquireProgramName(smsg_t * const pM) +{ + int i; + uchar *pszTag, *pszProgName; + DEFiRet; + + assert(pM != NULL); + pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG); + for( i = 0 + ; (i < pM->iLenTAG) && isprint((int) pszTag[i]) + && (pszTag[i] != '\0') && (pszTag[i] != ':') + && (pszTag[i] != '[') + && (runConf->globals.parser.bPermitSlashInProgramname || (pszTag[i] != '/')) + ; ++i) + ; /* just search end of PROGNAME */ + if(i < CONF_PROGNAME_BUFSIZE) { + pszProgName = pM->PROGNAME.szBuf; + } else { + CHKmalloc(pM->PROGNAME.ptr = malloc(i+1)); + pszProgName = pM->PROGNAME.ptr; + } + memcpy((char*)pszProgName, (char*)pszTag, i); + pszProgName[i] = '\0'; + pM->iLenPROGNAME = i; +finalize_it: + RETiRet; +} + + +/* Access methods - dumb & easy, not a comment for each ;) + */ +void setProtocolVersion(smsg_t * const pM, int iNewVersion) +{ + assert(pM != NULL); + if(iNewVersion != 0 && iNewVersion != 1) { + dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion); + iNewVersion = 0; + } + pM->iProtocolVersion = iNewVersion; +} + +/* note: string is taken from constant pool, do NOT free */ +static const char *getProtocolVersionString(smsg_t * const pM) +{ + assert(pM != NULL); + return(pM->iProtocolVersion ? "1" : "0"); +} + +void +msgSetPRI(smsg_t *const __restrict__ pMsg, syslog_pri_t pri) +{ + if(pri > LOG_MAXPRI) + pri = LOG_PRI_INVLD; + pMsg->iFacility = pri2fac(pri), + pMsg->iSeverity = pri2sev(pri); +} + +#ifdef USE_LIBUUID +/* note: libuuid seems not to be thread-safe, so we need + * to get some safeguards in place. + */ +static pthread_mutex_t mutUUID = PTHREAD_MUTEX_INITIALIZER; + +static void call_uuid_generate(uuid_t uuid) +{ + pthread_mutex_lock(&mutUUID); + pthread_cleanup_push(mutexCancelCleanup, &mutUUID); + uuid_generate(uuid); + pthread_cleanup_pop(1); +} + +static void msgSetUUID(smsg_t * const pM) +{ + size_t lenRes = sizeof(uuid_t) * 2 + 1; + char hex_char [] = "0123456789ABCDEF"; + unsigned int byte_nbr; + uuid_t uuid; + + dbgprintf("[MsgSetUUID] START, lenRes %llu\n", (long long unsigned) lenRes); + assert(pM != NULL); + + if((pM->pszUUID = (uchar*) malloc(lenRes)) == NULL) { + pM->pszUUID = (uchar *)""; + } else { + call_uuid_generate(uuid); + for (byte_nbr = 0; byte_nbr < sizeof (uuid_t); byte_nbr++) { + pM->pszUUID[byte_nbr * 2 + 0] = hex_char[uuid [byte_nbr] >> 4]; + pM->pszUUID[byte_nbr * 2 + 1] = hex_char[uuid [byte_nbr] & 15]; + } + + pM->pszUUID[lenRes-1] = '\0'; + dbgprintf("[MsgSetUUID] UUID : %s LEN: %d \n", pM->pszUUID, (int)lenRes); + } + dbgprintf("[MsgSetUUID] END\n"); +} + +static void getUUID(smsg_t * const pM, uchar **pBuf, int *piLen) +{ + dbgprintf("[getUUID] START\n"); + if(pM == NULL) { + dbgprintf("[getUUID] pM is NULL\n"); + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->pszUUID == NULL) { + dbgprintf("[getUUID] pM->pszUUID is NULL\n"); + MsgLock(pM); + /* re-query, things may have changed in the mean time... */ + if(pM->pszUUID == NULL) + msgSetUUID(pM); + MsgUnlock(pM); + } else { /* UUID already there we reuse it */ + dbgprintf("[getUUID] pM->pszUUID already exists\n"); + } + *pBuf = pM->pszUUID; + *piLen = sizeof(uuid_t) * 2; + } + dbgprintf("[getUUID] END\n"); +} +#endif + +int ATTR_NONNULL() +getRawMsgLen(const smsg_t *const pMsg) +{ + return (pMsg->pszRawMsg == NULL) ? 0 : pMsg->iLenRawMsg; +} + +void +getRawMsg(const smsg_t * const pM, uchar **pBuf, int *piLen) +{ + if(pM == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->pszRawMsg == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + *pBuf = pM->pszRawMsg; + *piLen = pM->iLenRawMsg; + } + } +} + +void +getRawMsgAfterPRI(smsg_t * const pM, uchar **pBuf, int *piLen) +{ + if(pM == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->pszRawMsg == NULL) { + *pBuf= UCHAR_CONSTANT(""); + *piLen = 0; + } else { + /* unfortunately, pM->offAfterPRI seems NOT to be + * correct/consistent in all cases. imuxsock and imudp + * seem to have other values than imptcp. Testbench + * covers some of that. As a work-around, we caluculate + * the value ourselfes here. -- rgerhards, 2015-10-09 + */ + size_t offAfterPRI = 0; + if(pM->pszRawMsg[0] == '<') { /* do we have a PRI? */ + if(pM->pszRawMsg[2] == '>') + offAfterPRI = 3; + else if(pM->pszRawMsg[3] == '>') + offAfterPRI = 4; + else if(pM->pszRawMsg[4] == '>') + offAfterPRI = 5; + } + *pBuf = pM->pszRawMsg + offAfterPRI; + *piLen = pM->iLenRawMsg - offAfterPRI; + } + } +} + + +/* note: setMSGLen() is only for friends who really know what they + * do. Setting an invalid length can be desasterous! + */ +void setMSGLen(smsg_t * const pM, int lenMsg) +{ + pM->iLenMSG = lenMsg; +} + +int getMSGLen(smsg_t * const pM) +{ + return((pM == NULL) ? 0 : pM->iLenMSG); +} + +uchar *getMSG(smsg_t * const pM) +{ + uchar *ret; + if(pM == NULL) + ret = UCHAR_CONSTANT(""); + else { + if(pM->iLenMSG == 0) + ret = UCHAR_CONSTANT(""); + else + ret = pM->pszRawMsg + pM->offMSG; + } + return ret; +} + + +/* Get PRI value as integer */ +int +getPRIi(const smsg_t * const pM) +{ + syslog_pri_t pri = (pM->iFacility << 3) + (pM->iSeverity); + if(pri > 191) + pri = LOG_PRI_INVLD; + return pri; +} + + +/* Get PRI value in text form + */ +const char * +getPRI(smsg_t * const pM) +{ + /* PRI is a number in the range 0..191. Thus, we use a simple lookup table to obtain the + * string value. It looks a bit clumpsy here in code ;) + */ + int iPRI; + + if(pM == NULL) + return ""; + + iPRI = getPRIi(pM); + return (iPRI > 191) ? "invld" : (char*)syslog_pri_names[iPRI].pszName; +} + + +static const char * +formatISOWeekOrYear(enum tplFormatTypes eFmt, struct syslogTime *pTm) +{ + if(pTm->year >= 1970 && pTm->year <= 2099) { + int isoWeekYear; + int isoWeek; + + isoWeek = getISOWeek(pTm, &isoWeekYear); + + if (eFmt == tplFmtISOWeek) { + return two_digits[isoWeek]; + } else { + return years[isoWeekYear - 1967]; + } + } else { + return "YEAR OUT OF RANGE(1970-2099)"; + } +} + +const char * +getTimeReported(smsg_t * const pM, enum tplFormatTypes eFmt) +{ + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + case tplFmtRFC3164Date: + case tplFmtRFC3164BuggyDate: + MsgLock(pM); + if(pM->pszTIMESTAMP3164 == NULL) { + pM->pszTIMESTAMP3164 = pM->pszTimestamp3164; + datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164, + (eFmt == tplFmtRFC3164BuggyDate)); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_MySQL == NULL) { + if((pM->pszTIMESTAMP_MySQL = malloc(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_PgSQL == NULL) { + if((pM->pszTIMESTAMP_PgSQL = malloc(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_PgSQL); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszTIMESTAMP3339 == NULL) { + pM->pszTIMESTAMP3339 = pM->pszTimestamp3339; + datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP3339); + case tplFmtUnixDate: + MsgLock(pM); + if(pM->pszTIMESTAMP_Unix[0] == '\0') { + datetime.formatTimestampUnix(&pM->tTIMESTAMP, pM->pszTIMESTAMP_Unix); + } + MsgUnlock(pM); + return(pM->pszTIMESTAMP_Unix); + case tplFmtSecFrac: + if(pM->pszTIMESTAMP_SecFrac[0] == '\0') { + MsgLock(pM); + /* re-check, may have changed while we did not hold lock */ + if(pM->pszTIMESTAMP_SecFrac[0] == '\0') { + datetime.formatTimestampSecFrac(&pM->tTIMESTAMP, pM->pszTIMESTAMP_SecFrac); + } + MsgUnlock(pM); + } + return(pM->pszTIMESTAMP_SecFrac); + case tplFmtWDayName: + return wdayNames[getWeekdayNbr(&pM->tTIMESTAMP)]; + case tplFmtWDay: + return one_digit[getWeekdayNbr(&pM->tTIMESTAMP)]; + case tplFmtMonth: + return two_digits[(int)pM->tTIMESTAMP.month]; + case tplFmtYear: + if(pM->tTIMESTAMP.year >= 1967 && pM->tTIMESTAMP.year <= 2099) + return years[pM->tTIMESTAMP.year - 1967]; + else + return "YEAR OUT OF RANGE(1967-2099)"; + case tplFmtDay: + return two_digits[(int)pM->tTIMESTAMP.day]; + case tplFmtHour: + return two_digits[(int)pM->tTIMESTAMP.hour]; + case tplFmtMinute: + return two_digits[(int)pM->tTIMESTAMP.minute]; + case tplFmtSecond: + return two_digits[(int)pM->tTIMESTAMP.second]; + case tplFmtTZOffsHour: + return two_digits[(int)pM->tTIMESTAMP.OffsetHour]; + case tplFmtTZOffsMin: + return two_digits[(int)pM->tTIMESTAMP.OffsetMinute]; + case tplFmtTZOffsDirection: + return (pM->tTIMESTAMP.OffsetMode == '+')? "+" : "-"; + case tplFmtOrdinal: + return daysInYear[getOrdinal(&pM->tTIMESTAMP)]; + case tplFmtWeek: + return two_digits[getWeek(&pM->tTIMESTAMP)]; + case tplFmtISOWeek: + case tplFmtISOWeekYear: + return formatISOWeekOrYear(eFmt, &pM->tTIMESTAMP); + } + return "INVALID eFmt OPTION!"; +} + + + +static const char *getTimeUTC(struct syslogTime *const __restrict__ pTmIn, + const enum tplFormatTypes eFmt, + unsigned short *const __restrict__ pbMustBeFreed) +{ + struct syslogTime tUTC; + char *retbuf = NULL; + + timeConvertToUTC(pTmIn, &tUTC); + struct syslogTime *const pTm = &tUTC; + + switch(eFmt) { + case tplFmtDefault: + if((retbuf = malloc(16)) != NULL) { + datetime.formatTimestamp3164(pTm, retbuf, 0); + } + break; + case tplFmtMySQLDate: + if((retbuf = malloc(15)) != NULL) { + datetime.formatTimestampToMySQL(pTm, retbuf); + } + break; + case tplFmtPgSQLDate: + if((retbuf = malloc(21)) != NULL) { + datetime.formatTimestampToPgSQL(pTm, retbuf); + } + break; + case tplFmtRFC3164Date: + case tplFmtRFC3164BuggyDate: + if((retbuf = malloc(16)) != NULL) { + datetime.formatTimestamp3164(pTm, retbuf, (eFmt == tplFmtRFC3164BuggyDate)); + } + break; + case tplFmtRFC3339Date: + if((retbuf = malloc(33)) != NULL) { + datetime.formatTimestamp3339(pTm, retbuf); + } + break; + case tplFmtUnixDate: + if((retbuf = malloc(12)) != NULL) { + datetime.formatTimestampUnix(pTm, retbuf); + } + break; + case tplFmtSecFrac: + if((retbuf = malloc(7)) != NULL) { + datetime.formatTimestampSecFrac(pTm, retbuf); + } + break; + case tplFmtWDayName: + retbuf = strdup(wdayNames[getWeekdayNbr(pTm)]); + break; + case tplFmtWDay: + retbuf = strdup(one_digit[getWeekdayNbr(pTm)]); + break; + case tplFmtMonth: + retbuf = strdup(two_digits[(int)pTm->month]); + break; + case tplFmtYear: + if(pTm->year >= 1967 && pTm->year <= 2099) + retbuf = strdup(years[pTm->year - 1967]); + else + retbuf = strdup("YEAR OUT OF RANGE(1967-2099)"); + break; + case tplFmtDay: + retbuf = strdup(two_digits[(int)pTm->day]); + break; + case tplFmtHour: + retbuf = strdup(two_digits[(int)pTm->hour]); + break; + case tplFmtMinute: + retbuf = strdup(two_digits[(int)pTm->minute]); + break; + case tplFmtSecond: + retbuf = strdup(two_digits[(int)pTm->second]); + break; + case tplFmtTZOffsHour: + retbuf = strdup(two_digits[(int)pTm->OffsetHour]); + break; + case tplFmtTZOffsMin: + retbuf = strdup(two_digits[(int)pTm->OffsetMinute]); + break; + case tplFmtTZOffsDirection: + retbuf = strdup((pTm->OffsetMode == '+')? "+" : "-"); + break; + case tplFmtOrdinal: + retbuf = strdup(daysInYear[getOrdinal(pTm)]); + break; + case tplFmtWeek: + retbuf = strdup(two_digits[getWeek(pTm)]); + break; + case tplFmtISOWeek: + case tplFmtISOWeekYear: + retbuf = strdup(formatISOWeekOrYear(eFmt, pTm)); + break; + } + + if(retbuf == NULL) { + retbuf = (char*)"internal error: invalid eFmt option or malloc problem"; + *pbMustBeFreed = 0; + } else { + *pbMustBeFreed = 1; + } + return retbuf; +} + +static const char * +getTimeGenerated(smsg_t *const __restrict__ pM, + const enum tplFormatTypes eFmt) +{ + struct syslogTime *const pTm = &pM->tRcvdAt; + if(pM == NULL) + return ""; + + switch(eFmt) { + case tplFmtDefault: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(pTm, pM->pszRcvdAt3164, 0); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtMySQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_MySQL == NULL) { + if((pM->pszRcvdAt_MySQL = malloc(15)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToMySQL(pTm, pM->pszRcvdAt_MySQL); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_MySQL); + case tplFmtPgSQLDate: + MsgLock(pM); + if(pM->pszRcvdAt_PgSQL == NULL) { + if((pM->pszRcvdAt_PgSQL = malloc(21)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestampToPgSQL(pTm, pM->pszRcvdAt_PgSQL); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_PgSQL); + case tplFmtRFC3164Date: + case tplFmtRFC3164BuggyDate: + MsgLock(pM); + if(pM->pszRcvdAt3164 == NULL) { + if((pM->pszRcvdAt3164 = malloc(16)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3164(pTm, pM->pszRcvdAt3164, + (eFmt == tplFmtRFC3164BuggyDate)); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3164); + case tplFmtRFC3339Date: + MsgLock(pM); + if(pM->pszRcvdAt3339 == NULL) { + if((pM->pszRcvdAt3339 = malloc(33)) == NULL) { + MsgUnlock(pM); + return ""; + } + datetime.formatTimestamp3339(pTm, pM->pszRcvdAt3339); + } + MsgUnlock(pM); + return(pM->pszRcvdAt3339); + case tplFmtUnixDate: + MsgLock(pM); + if(pM->pszRcvdAt_Unix[0] == '\0') { + datetime.formatTimestampUnix(pTm, pM->pszRcvdAt_Unix); + } + MsgUnlock(pM); + return(pM->pszRcvdAt_Unix); + case tplFmtSecFrac: + if(pM->pszRcvdAt_SecFrac[0] == '\0') { + MsgLock(pM); + /* re-check, may have changed while we did not hold lock */ + if(pM->pszRcvdAt_SecFrac[0] == '\0') { + datetime.formatTimestampSecFrac(pTm, pM->pszRcvdAt_SecFrac); + } + MsgUnlock(pM); + } + return(pM->pszRcvdAt_SecFrac); + case tplFmtWDayName: + return wdayNames[getWeekdayNbr(pTm)]; + case tplFmtWDay: + return one_digit[getWeekdayNbr(pTm)]; + case tplFmtMonth: + return two_digits[(int)pTm->month]; + case tplFmtYear: + if(pTm->year >= 1967 && pTm->year <= 2099) + return years[pTm->year - 1967]; + else + return "YEAR OUT OF RANGE(1967-2099)"; + case tplFmtDay: + return two_digits[(int)pTm->day]; + case tplFmtHour: + return two_digits[(int)pTm->hour]; + case tplFmtMinute: + return two_digits[(int)pTm->minute]; + case tplFmtSecond: + return two_digits[(int)pTm->second]; + case tplFmtTZOffsHour: + return two_digits[(int)pTm->OffsetHour]; + case tplFmtTZOffsMin: + return two_digits[(int)pTm->OffsetMinute]; + case tplFmtTZOffsDirection: + return (pTm->OffsetMode == '+')? "+" : "-"; + case tplFmtOrdinal: + return daysInYear[getOrdinal(pTm)]; + case tplFmtWeek: + return two_digits[getWeek(pTm)]; + case tplFmtISOWeek: + case tplFmtISOWeekYear: + return formatISOWeekOrYear(eFmt, pTm); + } + return "INVALID eFmt OPTION!"; +} + + +static const char *getSeverity(smsg_t * const pM) +{ + const char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iSeverity > 7) { + name = "invld"; + } else { + name = syslog_number_names[pM->iSeverity]; + } + + return name; +} + + +static const char *getSeverityStr(smsg_t * const pM) +{ + const char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iSeverity > 7) { + name = "invld"; + } else { + name = syslog_severity_names[pM->iSeverity]; + } + + return name; +} + +static const char *getFacility(smsg_t * const pM) +{ + const char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iFacility > 23) { + name = "invld"; + } else { + name = syslog_number_names[pM->iFacility]; + } + + return name; +} + +static const char *getFacilityStr(smsg_t * const pM) +{ + const char *name = NULL; + + if(pM == NULL) + return ""; + + if(pM->iFacility > 23) { + name = "invld"; + } else { + name = syslog_fac_names[pM->iFacility]; + } + + return name; +} + + +/* set flow control state (if not called, the default - NO_DELAY - is used) + * This needs no locking because it is only done while the object is + * not fully constructed (which also means you must not call this + * method after the msg has been handed over to a queue). + * rgerhards, 2008-03-14 + */ +rsRetVal +MsgSetFlowControlType(smsg_t * const pMsg, flowControl_t eFlowCtl) +{ + DEFiRet; + assert(pMsg != NULL); + assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY); + + pMsg->flowCtlType = eFlowCtl; + + RETiRet; +} + +/* set offset after which PRI in raw msg starts + * rgerhards, 2009-06-16 + */ +rsRetVal +MsgSetAfterPRIOffs(smsg_t * const pMsg, int offs) +{ + assert(pMsg != NULL); + pMsg->offAfterPRI = offs; + return RS_RET_OK; +} + + +/* rgerhards 2004-11-24: set APP-NAME in msg object + * This is not locked, because it either is called during message + * construction (where we need no locking) or later as part of a function + * which already obtained the lock. So in general, this function here must + * only be called when it it safe to do so without it aquiring a lock. + */ +rsRetVal ATTR_NONNULL(1,2) +MsgSetAPPNAME(smsg_t *__restrict__ const pMsg, const char *pszAPPNAME) +{ + DEFiRet; + assert(pMsg != NULL); + if(pszAPPNAME[0] == '\0') { + pszAPPNAME = "-"; /* RFC5424 NIL value */ + } + if(pMsg->pCSAPPNAME == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME)); + } + /* if we reach this point, we have the object */ + CHKiRet(rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME)); + cstrFinalize(pMsg->pCSAPPNAME); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2004-11-24: set PROCID in msg object + */ +rsRetVal MsgSetPROCID(smsg_t *__restrict__ const pMsg, const char* pszPROCID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSPROCID == NULL) { + /* we need to obtain the object first */ + CHKiRet(cstrConstruct(&pMsg->pCSPROCID)); + } + /* if we reach this point, we have the object */ + CHKiRet(rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID)); + cstrFinalize(pMsg->pCSPROCID); + +finalize_it: + RETiRet; +} + + +/* check if we have a procid, and, if not, try to acquire/emulate it. + * This must be called WITHOUT the message lock being held. + * rgerhards, 2009-06-26 + */ +static void preparePROCID(smsg_t * const pM, sbool bLockMutex) +{ + if(pM->pCSPROCID == NULL) { + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + /* re-query, things may have changed in the mean time... */ + if(pM->pCSPROCID == NULL) + acquirePROCIDFromTAG(pM); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + } +} + + +#if 0 +/* rgerhards, 2005-11-24 + */ +static int getPROCIDLen(smsg_t *pM, sbool bLockMutex) +{ + assert(pM != NULL); + preparePROCID(pM, bLockMutex); + return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID); +} +#endif + + +/* rgerhards, 2005-11-24 + */ +char *getPROCID(smsg_t * const pM, sbool bLockMutex) +{ + uchar *pszRet; + + ISOBJ_TYPE_assert(pM, msg); + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + preparePROCID(pM, MUTEX_ALREADY_LOCKED); + if(pM->pCSPROCID == NULL) + pszRet = UCHAR_CONSTANT("-"); + else + pszRet = rsCStrGetSzStrNoNULL(pM->pCSPROCID); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return (char*) pszRet; +} + + +/* rgerhards 2004-11-24: set MSGID in msg object + */ +rsRetVal MsgSetMSGID(smsg_t * const pMsg, const char* pszMSGID) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + if(pMsg->pCSMSGID == NULL) { + /* we need to obtain the object first */ + CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID)); + } + /* if we reach this point, we have the object */ + CHKiRet(rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID)); + cstrFinalize(pMsg->pCSMSGID); + +finalize_it: + RETiRet; +} + + +/* Return state of last parser. If it had success, "OK" is returned, else + * "FAIL". All from the constant pool. + */ +static const char *getParseSuccess(smsg_t * const pM) +{ + return (pM->bParseSuccess) ? "OK" : "FAIL"; +} + + +/* al, 2011-07-26: LockMsg to avoid race conditions + */ +static const char *getMSGID(smsg_t * const pM) +{ + if (pM->pCSMSGID == NULL) { + return "-"; + } + else { + MsgLock(pM); + char* pszreturn = (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID); + MsgUnlock(pM); + return pszreturn; + } +} + +/* rgerhards 2012-03-15: set parser success (an integer, acutally bool) + */ +void MsgSetParseSuccess(smsg_t * const pMsg, int bSuccess) +{ + assert(pMsg != NULL); + pMsg->bParseSuccess = bSuccess; +} + + +/* return full message as a json string */ +const uchar* +msgGetJSONMESG(smsg_t *__restrict__ const pMsg) +{ + struct json_object *json; + struct json_object *jval; + uchar *pRes; /* result pointer */ + rs_size_t bufLen = -1; /* length of string or -1, if not known */ + + json = json_object_new_object(); + + jval = json_object_new_string((char*)getMSG(pMsg)); + json_object_object_add(json, "msg", jval); + + getRawMsg(pMsg, &pRes, &bufLen); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "rawmsg", jval); + + pRes = (uchar*)getTimeReported(pMsg, tplFmtRFC3339Date); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "timereported", jval); + + jval = json_object_new_string(getHOSTNAME(pMsg)); + json_object_object_add(json, "hostname", jval); + + getTAG(pMsg, &pRes, &bufLen, LOCK_MUTEX); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "syslogtag", jval); + + getInputName(pMsg, &pRes, &bufLen); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "inputname", jval); + + jval = json_object_new_string((char*)getRcvFrom(pMsg)); + json_object_object_add(json, "fromhost", jval); + + jval = json_object_new_string((char*)getRcvFromIP(pMsg)); + json_object_object_add(json, "fromhost-ip", jval); + + jval = json_object_new_string(getPRI(pMsg)); + json_object_object_add(json, "pri", jval); + + jval = json_object_new_string(getFacility(pMsg)); + json_object_object_add(json, "syslogfacility", jval); + + jval = json_object_new_string(getSeverity(pMsg)); + json_object_object_add(json, "syslogseverity", jval); + + pRes = (uchar*)getTimeGenerated(pMsg, tplFmtRFC3339Date); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "timegenerated", jval); + + jval = json_object_new_string((char*)getProgramName(pMsg, LOCK_MUTEX)); + json_object_object_add(json, "programname", jval); + + jval = json_object_new_string(getProtocolVersionString(pMsg)); + json_object_object_add(json, "protocol-version", jval); + + MsgGetStructuredData(pMsg, &pRes, &bufLen); + jval = json_object_new_string((char*)pRes); + json_object_object_add(json, "structured-data", jval); + + jval = json_object_new_string(getAPPNAME(pMsg, LOCK_MUTEX)); + json_object_object_add(json, "app-name", jval); + + jval = json_object_new_string(getPROCID(pMsg, LOCK_MUTEX)); + json_object_object_add(json, "procid", jval); + + jval = json_object_new_string(getMSGID(pMsg)); + json_object_object_add(json, "msgid", jval); + +#ifdef USE_LIBUUID + if(pMsg->pszUUID == NULL) { + jval = NULL; + } else { + getUUID(pMsg, &pRes, &bufLen); + jval = json_object_new_string((char*)pRes); + } + json_object_object_add(json, "uuid", jval); +#endif + + json_object_object_add(json, "$!", json_object_get(pMsg->json)); + + pRes = (uchar*) strdup(json_object_get_string(json)); + json_object_put(json); + return pRes; +} + +/* rgerhards 2009-06-12: set associated ruleset + */ +void MsgSetRuleset(smsg_t * const pMsg, ruleset_t *pRuleset) +{ + assert(pMsg != NULL); + pMsg->pRuleset = pRuleset; +} + + +/* set TAG in msg object + * (rewritten 2009-06-18 rgerhards) + */ +void MsgSetTAG(smsg_t *__restrict__ const pMsg, const uchar* pszBuf, const size_t lenBuf) +{ + uchar *pBuf; + assert(pMsg != NULL); + + freeTAG(pMsg); + + pMsg->iLenTAG = lenBuf; + if(pMsg->iLenTAG < CONF_TAG_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pBuf = pMsg->TAG.szBuf; + } else { + if((pBuf = (uchar*) malloc(pMsg->iLenTAG + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pBuf = pMsg->TAG.szBuf; + pMsg->iLenTAG = CONF_TAG_BUFSIZE - 1; + } else { + pMsg->TAG.pszTAG = pBuf; + } + } + + memcpy(pBuf, pszBuf, pMsg->iLenTAG); + pBuf[pMsg->iLenTAG] = '\0'; /* this also works with truncation! */ +} + + +/* This function tries to emulate the TAG if none is + * set. Its primary purpose is to provide an old-style TAG + * when a syslog-protocol message has been received. Then, + * the tag is APP-NAME "[" PROCID "]". The function first checks + * if there is a TAG and, if not, if it can emulate it. + * rgerhards, 2005-11-24 + */ +static void ATTR_NONNULL(1) +tryEmulateTAG(smsg_t *const pM, const sbool bLockMutex) +{ + size_t lenTAG; + uchar bufTAG[CONF_TAG_MAXSIZE]; + assert(pM != NULL); + + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + if(pM->iLenTAG > 0) { + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return; /* done, no need to emulate */ + } + + if(msgGetProtocolVersion(pM) == 1) { + if(!strcmp(getPROCID(pM, MUTEX_ALREADY_LOCKED), "-")) { + /* no process ID, use APP-NAME only */ + MsgSetTAG(pM, (uchar*) getAPPNAME(pM, MUTEX_ALREADY_LOCKED), + getAPPNAMELen(pM, MUTEX_ALREADY_LOCKED)); + } else { + /* now we can try to emulate */ + lenTAG = snprintf((char*)bufTAG, CONF_TAG_MAXSIZE, "%s[%s]", + getAPPNAME(pM, MUTEX_ALREADY_LOCKED), getPROCID(pM, MUTEX_ALREADY_LOCKED)); + bufTAG[sizeof(bufTAG)-1] = '\0'; /* just to make sure... */ + MsgSetTAG(pM, bufTAG, lenTAG); + } + /* Signal change in TAG for acquireProgramName */ + pM->iLenPROGNAME = -1; + } + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); +} + + +void ATTR_NONNULL(2,3) +getTAG(smsg_t * const pM, uchar **const ppBuf, int *const piLen, const sbool bLockMutex) +{ + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + + if(pM == NULL) { + *ppBuf = UCHAR_CONSTANT(""); + *piLen = 0; + } else { + if(pM->iLenTAG == 0) + tryEmulateTAG(pM, MUTEX_ALREADY_LOCKED); + if(pM->iLenTAG == 0) { + *ppBuf = UCHAR_CONSTANT(""); + *piLen = 0; + } else { + *ppBuf = (pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG; + *piLen = pM->iLenTAG; + } + } + + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); +} + + +int getHOSTNAMELen(smsg_t * const pM) +{ + if(pM == NULL) + return 0; + else + if(pM->pszHOSTNAME == NULL) { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) + return 0; + else + return prop.GetStringLen(pM->rcvFrom.pRcvFrom); + } else + return pM->iLenHOSTNAME; +} + + +const char *getHOSTNAME(smsg_t * const pM) +{ + if(pM == NULL) + return ""; + else + if(pM->pszHOSTNAME == NULL) { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) { + return ""; + } else { + uchar *psz; + int len; + prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len); + return (char*) psz; + } + } else { + return (char*) pM->pszHOSTNAME; + } +} + + +uchar *getRcvFrom(smsg_t * const pM) +{ + uchar *psz; + int len; + + if(pM == NULL) { + psz = UCHAR_CONSTANT(""); + } else { + resolveDNS(pM); + if(pM->rcvFrom.pRcvFrom == NULL) + psz = UCHAR_CONSTANT(""); + else + prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len); + } + return psz; +} + + +/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object + */ +rsRetVal MsgSetStructuredData(smsg_t * const pMsg, const char* pszStrucData) +{ + DEFiRet; + ISOBJ_TYPE_assert(pMsg, msg); + free(pMsg->pszStrucData); + CHKmalloc(pMsg->pszStrucData = (uchar*)strdup(pszStrucData)); + pMsg->lenStrucData = strlen(pszStrucData); +finalize_it: + RETiRet; +} + + +/* get the "STRUCTURED-DATA" as sz string, including length */ +void +MsgGetStructuredData(smsg_t * const pM, uchar **pBuf, rs_size_t *len) +{ + MsgLock(pM); + if(pM->pszStrucData == NULL) { + *pBuf = UCHAR_CONSTANT("-"), + *len = 1; + } else { + *pBuf = pM->pszStrucData, + *len = pM->lenStrucData; + } + MsgUnlock(pM); +} + +/* get the "programname" as sz string + * rgerhards, 2005-10-19 + */ +uchar * ATTR_NONNULL(1) +getProgramName(smsg_t *const pM, const sbool bLockMutex) +{ + if(bLockMutex == LOCK_MUTEX) { + MsgLock(pM); + } + + if(pM->iLenPROGNAME == -1) { + if(pM->iLenTAG == 0) { + uchar *pRes; + rs_size_t bufLen = -1; + getTAG(pM, &pRes, &bufLen, MUTEX_ALREADY_LOCKED); + } + acquireProgramName(pM); + } + + if(bLockMutex == LOCK_MUTEX) { + MsgUnlock(pM); + } + return (pM->iLenPROGNAME < CONF_PROGNAME_BUFSIZE) ? pM->PROGNAME.szBuf + : pM->PROGNAME.ptr; +} + + + +/* check if we have a APPNAME, and, if not, try to acquire/emulate it. + * rgerhards, 2009-06-26 + */ +static void ATTR_NONNULL(1) +prepareAPPNAME(smsg_t *const pM, const sbool bLockMutex) +{ + if(pM->pCSAPPNAME == NULL) { + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + + /* re-query as things might have changed during locking */ + if(pM->pCSAPPNAME == NULL) { + if(msgGetProtocolVersion(pM) == 0) { + /* only then it makes sense to emulate */ + MsgSetAPPNAME(pM, (char*)getProgramName(pM, MUTEX_ALREADY_LOCKED)); + } + } + + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + } +} + +/* rgerhards, 2005-11-24 + */ +char *getAPPNAME(smsg_t * const pM, const sbool bLockMutex) +{ + uchar *pszRet; + + assert(pM != NULL); + if(bLockMutex == LOCK_MUTEX) + MsgLock(pM); + prepareAPPNAME(pM, MUTEX_ALREADY_LOCKED); + if(pM->pCSAPPNAME == NULL) + pszRet = UCHAR_CONSTANT(""); + else + pszRet = rsCStrGetSzStrNoNULL(pM->pCSAPPNAME); + if(bLockMutex == LOCK_MUTEX) + MsgUnlock(pM); + return (char*)pszRet; +} + +/* rgerhards, 2005-11-24 + */ +static int getAPPNAMELen(smsg_t * const pM, const sbool bLockMutex) +{ + assert(pM != NULL); + prepareAPPNAME(pM, bLockMutex); + return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME); +} + +/* rgerhards 2008-09-10: set pszInputName in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-16 + */ +void MsgSetInputName(smsg_t *pThis, prop_t *inputName) +{ + assert(pThis != NULL); + + prop.AddRef(inputName); + if(pThis->pInputName != NULL) + prop.Destruct(&pThis->pInputName); + pThis->pInputName = inputName; +} + +/* Set default TZ. Note that at most 7 chars are set, as we would + * otherwise overrun our buffer! + */ +void MsgSetDfltTZ(smsg_t *pThis, char *tz) +{ + strncpy(pThis->dfltTZ, tz, 7); + pThis->dfltTZ[7] = '\0'; /* ensure 0-Term in case of overflow! */ +} + + +/* Set the pfrominet socket store, so that we can obtain the peer at some + * later time. Note that we do not check if pRcvFrom is already set, so this + * function must only be called during message creation. + * NOTE: msgFlags is NOT set. While this is somewhat a violation of layers, + * it is done because it gains us some performance. So the caller must make + * sure the message flags are properly maintained. For all current callers, + * this is always the case and without extra effort required. + * rgerhards, 2009-11-17 + */ +rsRetVal +msgSetFromSockinfo(smsg_t *pThis, struct sockaddr_storage *sa){ + DEFiRet; + assert(pThis->rcvFrom.pRcvFrom == NULL); + + CHKmalloc(pThis->rcvFrom.pfrominet = malloc(sizeof(struct sockaddr_storage))); + memcpy(pThis->rcvFrom.pfrominet, sa, sizeof(struct sockaddr_storage)); + +finalize_it: + RETiRet; +} + +/* rgerhards 2008-09-10: set RcvFrom name in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-30 + */ +void MsgSetRcvFrom(smsg_t *pThis, prop_t *new) +{ + prop.AddRef(new); + MsgSetRcvFromWithoutAddRef(pThis, new); +} + + +/* This is used to set the property via a string. This function should not be + * called if there is a reliable way for a caller to make sure that the + * same name can be used across multiple messages. However, if it can not + * ensure that, calling this function is the second best thing, because it + * will re-use the previously created property if it contained the same + * name (but it works only for the immediate previous). + * rgerhards, 2009-06-31 + */ +void MsgSetRcvFromStr(smsg_t * const pThis, const uchar *psz, const int len, prop_t **ppProp) +{ + assert(pThis != NULL); + assert(ppProp != NULL); + + prop.CreateOrReuseStringProp(ppProp, psz, len); + MsgSetRcvFrom(pThis, *ppProp); +} + + +/* set RcvFromIP name in msg object. This calls AddRef() + * on the property, because this must be done in all current cases and there + * is no case expected where this may not be necessary. + * rgerhards, 2009-06-30 + */ +rsRetVal MsgSetRcvFromIP(smsg_t *pThis, prop_t *new) +{ + assert(pThis != NULL); + + prop.AddRef(new); + MsgSetRcvFromIPWithoutAddRef(pThis, new); + return RS_RET_OK; +} + + +/* This is used to set the property via a string. This function should not be + * called if there is a reliable way for a caller to make sure that the + * same name can be used across multiple messages. However, if it can not + * ensure that, calling this function is the second best thing, because it + * will re-use the previously created property if it contained the same + * name (but it works only for the immediate previous). + * rgerhards, 2009-06-31 + */ +rsRetVal MsgSetRcvFromIPStr(smsg_t *const pThis, const uchar *psz, const int len, prop_t **ppProp) +{ + DEFiRet; + assert(pThis != NULL); + + CHKiRet(prop.CreateOrReuseStringProp(ppProp, psz, len)); + MsgSetRcvFromIP(pThis, *ppProp); + +finalize_it: + RETiRet; +} + + +/* rgerhards 2004-11-09: set HOSTNAME in msg object + * rgerhards, 2007-06-21: + * Does not return anything. If an error occurs, the hostname is + * simply not set. I have changed this behaviour. The only problem + * we can run into is memory shortage. If we have such, it is better + * to loose the hostname than the full message. So we silently ignore + * that problem and hope that memory will be available the next time + * we need it. The rest of the code already knows how to handle an + * unset HOSTNAME. + */ +void MsgSetHOSTNAME(smsg_t *pThis, const uchar* pszHOSTNAME, const int lenHOSTNAME) +{ + assert(pThis != NULL); + + freeHOSTNAME(pThis); + + pThis->iLenHOSTNAME = lenHOSTNAME; + if(pThis->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pThis->pszHOSTNAME = pThis->szHOSTNAME; + } else if((pThis->pszHOSTNAME = (uchar*) malloc(pThis->iLenHOSTNAME + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pThis->pszHOSTNAME = pThis->szHOSTNAME; + pThis->iLenHOSTNAME = CONF_HOSTNAME_BUFSIZE - 1; + } + + memcpy(pThis->pszHOSTNAME, pszHOSTNAME, pThis->iLenHOSTNAME); + pThis->pszHOSTNAME[pThis->iLenHOSTNAME] = '\0'; /* this also works with truncation! */ +} + + +/* set the offset of the MSG part into the raw msg buffer + * Note that the offset may be higher than the length of the raw message + * (exactly by one). This can happen if we have a message that does not + * contain any MSG part. + */ +void MsgSetMSGoffs(smsg_t * const pMsg, int offs) +{ + ISOBJ_TYPE_assert(pMsg, msg); + pMsg->offMSG = offs; + if(offs > pMsg->iLenRawMsg) { + assert((int)offs - 1 == pMsg->iLenRawMsg); + pMsg->iLenMSG = 0; + } else { + pMsg->iLenMSG = pMsg->iLenRawMsg - offs; + } +} + + +/* replace the MSG part of a message. The update actually takes place inside + * rawmsg. + * There are two cases: either the new message will be larger than the new msg + * or it will be less than or equal. If it is less than or equal, we can utilize + * the previous message buffer. If it is larger, we can utilize the smsg_t-included + * message buffer if it fits in there. If this is not the case, we need to alloc + * a new, larger, chunk and copy over the data to it. Note that this function is + * (hopefully) relatively seldom being called, so some performance impact is + * uncritical. In any case, pszMSG is copied, so if it was dynamically allocated, + * the caller is responsible for freeing it. + * rgerhards, 2009-06-23 + */ +rsRetVal MsgReplaceMSG(smsg_t *pThis, const uchar* pszMSG, int lenMSG) +{ + int lenNew; + uchar *bufNew; + DEFiRet; + ISOBJ_TYPE_assert(pThis, msg); + assert(pszMSG != NULL); + + lenNew = pThis->iLenRawMsg + lenMSG - pThis->iLenMSG; + if(lenMSG > pThis->iLenMSG && lenNew >= CONF_RAWMSG_BUFSIZE) { + /* we have lost our "bet" and need to alloc a new buffer ;) */ + CHKmalloc(bufNew = malloc(lenNew + 1)); + memcpy(bufNew, pThis->pszRawMsg, pThis->offMSG); + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + pThis->pszRawMsg = bufNew; + } + + if(lenMSG > 0) + memcpy(pThis->pszRawMsg + pThis->offMSG, pszMSG, lenMSG); + pThis->pszRawMsg[lenNew] = '\0'; /* this also works with truncation! */ + pThis->iLenRawMsg = lenNew; + pThis->iLenMSG = lenMSG; + +finalize_it: + RETiRet; +} + +/* truncate the (raw) message to configured max size. + * The function makes sure that the stored rawmsg remains + * properly terminated by '\0'. + */ +void ATTR_NONNULL() +MsgTruncateToMaxSize(smsg_t *const pThis) +{ + ISOBJ_TYPE_assert(pThis, msg); + const int maxMsgSize = glblGetMaxLine(runConf); + assert(pThis->iLenRawMsg > maxMsgSize); + + const int deltaSize = pThis->iLenRawMsg - maxMsgSize; + pThis->pszRawMsg[maxMsgSize] = '\0'; + pThis->iLenRawMsg = maxMsgSize; + if(pThis->iLenMSG < deltaSize) { + pThis->iLenMSG = 0; + } else { + pThis->iLenMSG -= deltaSize; + } +} + +/* set raw message in message object. Size of message is provided. + * The function makes sure that the stored rawmsg is properly + * terminated by '\0'. + * rgerhards, 2009-06-16 + */ +void ATTR_NONNULL() +MsgSetRawMsg(smsg_t *const pThis, const char*const pszRawMsg, const size_t lenMsg) +{ + ISOBJ_TYPE_assert(pThis, msg); + int deltaSize; + if(pThis->pszRawMsg != pThis->szRawMsg) + free(pThis->pszRawMsg); + + deltaSize = (int) lenMsg - pThis->iLenRawMsg; /* value < 0 in truncation case! */ + pThis->iLenRawMsg = lenMsg; + if(pThis->iLenRawMsg < CONF_RAWMSG_BUFSIZE) { + /* small enough: use fixed buffer (faster!) */ + pThis->pszRawMsg = pThis->szRawMsg; + } else if((pThis->pszRawMsg = (uchar*) malloc(pThis->iLenRawMsg + 1)) == NULL) { + /* truncate message, better than completely loosing it... */ + pThis->pszRawMsg = pThis->szRawMsg; + pThis->iLenRawMsg = CONF_RAWMSG_BUFSIZE - 1; + } + + memcpy(pThis->pszRawMsg, pszRawMsg, pThis->iLenRawMsg); + pThis->pszRawMsg[pThis->iLenRawMsg] = '\0'; /* this also works with truncation! */ + /* correct other information */ + if(pThis->iLenRawMsg > pThis->offMSG) + pThis->iLenMSG += deltaSize; + else + pThis->iLenMSG = 0; +} + + +/* set raw message in message object. Size of message is not provided. This + * function should only be used when it is unavoidable (and over time we should + * try to remove it altogether). + * rgerhards, 2009-06-16 + */ +void MsgSetRawMsgWOSize(smsg_t * const pMsg, char* pszRawMsg) +{ + MsgSetRawMsg(pMsg, pszRawMsg, strlen(pszRawMsg)); +} + + +/* create textual representation of facility and severity. + * The variable pRes must point to a user-supplied buffer of + * at least 20 characters. + */ +static uchar * +textpri(const smsg_t *const __restrict__ pMsg) +{ + int lenfac = len_syslog_fac_names[pMsg->iFacility]; + int lensev = len_syslog_severity_names[pMsg->iSeverity]; + int totlen = lenfac + 1 + lensev + 1; + char *pRes = malloc(totlen); + if(pRes != NULL) { + memcpy(pRes, syslog_fac_names[pMsg->iFacility], lenfac); + pRes[lenfac] = '.'; + memcpy(pRes+lenfac+1, syslog_severity_names[pMsg->iSeverity], lensev+1 /* for \0! */); + } + return (uchar*)pRes; +} + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, + NOW_HHOUR, NOW_QHOUR, NOW_MINUTE, NOW_WDAY } eNOWType; +#define tmpBUFSIZE 16 /* size of formatting buffer */ +static uchar *getNOW(eNOWType eNow, struct syslogTime *t, const int inUTC) +{ + uchar *pBuf; + struct syslogTime tt; + + if((pBuf = (uchar*) malloc(tmpBUFSIZE)) == NULL) { + return NULL; + } + + if(t == NULL) { /* can happen if called via script engine */ + datetime.getCurrTime(&tt, NULL, inUTC); + t = &tt; + } + + if(t->year == 0 || t->inUTC != inUTC) { /* not yet set! */ + datetime.getCurrTime(t, NULL, inUTC); + } + + switch(eNow) { + case NOW_NOW: + memcpy(pBuf, two_digits[t->year/100], 2); + memcpy(pBuf+2, two_digits[t->year%100], 2); + pBuf[4] = '-'; + memcpy(pBuf+5, two_digits[(int)t->month], 2); + pBuf[7] = '-'; + memcpy(pBuf+8, two_digits[(int)t->day], 3); + break; + case NOW_YEAR: + memcpy(pBuf, two_digits[t->year/100], 2); + memcpy(pBuf+2, two_digits[t->year%100], 3); + break; + case NOW_MONTH: + memcpy(pBuf, two_digits[(int)t->month], 3); + break; + case NOW_DAY: + memcpy(pBuf, two_digits[(int)t->day], 3); + break; + case NOW_HOUR: + memcpy(pBuf, two_digits[(int)t->hour], 3); + break; + case NOW_HHOUR: + memcpy(pBuf, two_digits[t->minute/30], 3); + break; + case NOW_QHOUR: + memcpy(pBuf, two_digits[t->minute/15], 3); + break; + case NOW_MINUTE: + memcpy(pBuf, two_digits[(int)t->minute], 3); + break; + case NOW_WDAY: + memcpy(pBuf, one_digit[(int)t->wday], 2); + break; + } + + return(pBuf); +} +#undef tmpBUFSIZE /* clean up */ + + +/* helper function to obtain correct JSON root and mutex depending on + * property type (essentially based on the property id. If a non-json + * property id is given the function errors out. + * Note well: jroot points to a pointer to a (ptr to a) json object. + * This is necessary because the caller needs a pointer to where the + * json object pointer is stored, that in turn is necessary because + * while the address of the actual pointer stays stable, the actual + * content is volatile until the caller has locked the variable tree, + * which we DO NOT do to keep calling semantics simple. + */ +static rsRetVal ATTR_NONNULL() +getJSONRootAndMutex(smsg_t *const pMsg, const propid_t id, + struct json_object ***const jroot, pthread_mutex_t **const mut) +{ + DEFiRet; + assert(jroot != NULL); /* asserts also help static analyzer! */ + assert(mut != NULL); + assert(*mut == NULL); /* caller shall have initialized this one! */ + assert(id == PROP_CEE || id == PROP_LOCAL_VAR || id == PROP_GLOBAL_VAR); + + if(id == PROP_CEE) { + *mut = &pMsg->mut; + *jroot = &pMsg->json; + } else if(id == PROP_LOCAL_VAR) { + *mut = &pMsg->mut; + *jroot = &pMsg->localvars; + } else if(id == PROP_GLOBAL_VAR) { + *mut = &glblVars_lock; + *jroot = &global_var_root; + } else { + LogError(0, RS_RET_NON_JSON_PROP, "internal error: " + "getJSONRootAndMutex; invalid property id %d", id); + iRet = RS_RET_NON_JSON_PROP; + } + + RETiRet; +} + +/* basically same function, but does not use property id, but the the + * variable name type indicator (char after starting $, e.g. $!myvar --> CEE) + */ +static rsRetVal ATTR_NONNULL() +getJSONRootAndMutexByVarChar(smsg_t *const pMsg, const char c, + struct json_object ***const jroot, pthread_mutex_t **const mut) +{ + DEFiRet; + propid_t id; + assert(c == '!' || c == '.' || c == '/'); + + switch(c) { + case '!': + id = PROP_CEE; + break; + case '.': + id = PROP_LOCAL_VAR; + break; + case '/': + id = PROP_GLOBAL_VAR; + break; + default: + LogError(0, RS_RET_NON_JSON_PROP, "internal error: " + "getJSONRootAndMutex; invalid indicator char %c(%2.2x)", c, c); + ABORT_FINALIZE(RS_RET_NON_JSON_PROP); + break; + } + iRet = getJSONRootAndMutex(pMsg, id, jroot, mut); + +finalize_it: + RETiRet; +} + + +/* Get a JSON-Property as string value (used for various types of JSON-based vars) */ +rsRetVal +getJSONPropVal(smsg_t * const pMsg, msgPropDescr_t *pProp, uchar **pRes, rs_size_t *buflen, + unsigned short *pbMustBeFreed) +{ + uchar *leaf; + struct json_object **jroot; + struct json_object *parent; + struct json_object *field; + pthread_mutex_t *mut = NULL; + DEFiRet; + + *pRes = NULL; + CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut)); + pthread_mutex_lock(mut); + + if(*jroot == NULL) FINALIZE; + + if(!strcmp((char*)pProp->name, "!")) { + field = *jroot; + } else { + leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen); + CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0)); + if(jsonVarExtract(parent, (char*)leaf, &field) == FALSE) + field = NULL; + } + if(field != NULL) { + *pRes = (uchar*) strdup(json_object_get_string(field)); + *buflen = (int) ustrlen(*pRes); + *pbMustBeFreed = 1; + } + +finalize_it: + if(mut != NULL) + pthread_mutex_unlock(mut); + if(*pRes == NULL) { + /* could not find any value, so set it to empty */ + *pRes = (unsigned char*)""; + *pbMustBeFreed = 0; + } + RETiRet; +} + + +/* Get a JSON-based-variable as native json object, except + * when it is string type, in which case a string is returned. + * This is an optimization to not use JSON when not strictly + * necessary. This in turn is helpful, as calling json-c is + * *very* expensive due to our need for locking and deep + * copies. + * The caller needs to check pjson and pcstr: one of them + * is non-NULL and contains the return value. Note that + * the caller is responsible for freeing the string pointer + * it if is being returned. + */ +rsRetVal +msgGetJSONPropJSONorString(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson, + uchar **pcstr) +{ + struct json_object **jroot; + uchar *leaf; + struct json_object *parent; + pthread_mutex_t *mut = NULL; + DEFiRet; + + *pjson = NULL, *pcstr = NULL; + + CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut)); + pthread_mutex_lock(mut); + if(!strcmp((char*)pProp->name, "!")) { + *pjson = *jroot; + FINALIZE; + } + if(*jroot == NULL) { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen); + CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0)); + if(jsonVarExtract(parent, (char*)leaf, pjson) == FALSE) { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + if(*pjson == NULL) { + /* we had a NULL json object and represent this as empty string */ + *pcstr = (uchar*) strdup(""); + } else { + if(json_object_get_type(*pjson) == json_type_string) { + *pcstr = (uchar*) strdup(json_object_get_string(*pjson)); + *pjson = NULL; + } + } + +finalize_it: + /* we need a deep copy, as another thread may modify the object */ + if(*pjson != NULL) + *pjson = jsonDeepCopy(*pjson); + if(mut != NULL) + pthread_mutex_unlock(mut); + RETiRet; +} + + + +/* Get a JSON-based-variable as native json object */ +rsRetVal +msgGetJSONPropJSON(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson) +{ + struct json_object **jroot; + uchar *leaf; + struct json_object *parent; + pthread_mutex_t *mut = NULL; + DEFiRet; + + *pjson = NULL; + + CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut)); + pthread_mutex_lock(mut); + + if(!strcmp((char*)pProp->name, "!")) { + *pjson = *jroot; + FINALIZE; + } + leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen); + CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0)); + if(jsonVarExtract(parent, (char*)leaf, pjson) == FALSE) { + ABORT_FINALIZE(RS_RET_NOT_FOUND); + } + +finalize_it: + /* we need a deep copy, as another thread may modify the object */ + if(*pjson != NULL) + *pjson = jsonDeepCopy(*pjson); + if(mut != NULL) + pthread_mutex_unlock(mut); + RETiRet; +} + + +/* Helper for jsonAddVal(), to be called onces we know there are actually + * json escapes inside the string. If so, this function takes over. + * Splitting the functions permits us to make some performance optimizations. + * For further details, see jsonAddVal(). + */ +static rsRetVal ATTR_NONNULL(1, 4) +jsonAddVal_escaped(uchar *const pSrc, + const unsigned buflen, + const unsigned len_none_escaped_head, + es_str_t **dst, + const int escapeAll) +{ + unsigned char c; + es_size_t i; + char numbuf[4]; + unsigned ni; + unsigned char nc; + int j; + uchar wrkbuf[100000]; + size_t dst_realloc_size; + size_t dst_size; + uchar *dst_base; + uchar *dst_w; + uchar *newbuf; + DEFiRet; + + assert(len_none_escaped_head <= buflen); + /* first copy over unescaped head string */ + if(len_none_escaped_head+10 > sizeof(wrkbuf)) { + dst_size = 2 * len_none_escaped_head; + CHKmalloc(dst_base = malloc(dst_size)); + } else { + dst_size = sizeof(wrkbuf); + dst_base = wrkbuf; + } + dst_realloc_size = dst_size - 10; /* some buffer for escaping */ + dst_w = dst_base; + memcpy(dst_w, pSrc, len_none_escaped_head); + dst_w += len_none_escaped_head; + + /* now do the escaping */ + for(i = len_none_escaped_head ; i < buflen ; ++i) { + const size_t dst_offset = dst_w - dst_base; + if(dst_offset >= dst_realloc_size) { + const size_t new_size = 2 * dst_size; + if(dst_base == wrkbuf) { + CHKmalloc(newbuf = malloc(new_size)); + memcpy(newbuf, dst_base, dst_offset); + } else { + CHKmalloc(newbuf = realloc(dst_base, new_size)); + } + dst_size = new_size; + dst_realloc_size = new_size - 10; /* some buffer for escaping */ + dst_base = newbuf; + dst_w = dst_base + dst_offset; + } + c = pSrc[i]; + if( (c >= 0x30 && c <= 0x5b) + || (c >= 0x23 && c <= 0x2e) + || (c >= 0x5d /* && c <= 0x10FFFF*/) + || c == 0x20 || c == 0x21) { + /* no need to escape */ + *dst_w++ = c; + } else { + /* we must escape, try RFC4627-defined special sequences first */ + switch(c) { + case '\0': + *dst_w++ = '\\'; + *dst_w++ = 'u'; + *dst_w++ = '0'; + *dst_w++ = '0'; + *dst_w++ = '0'; + *dst_w++ = '0'; + break; + case '\"': + *dst_w++ = '\\'; + *dst_w++ = '"'; + break; + case '/': + *dst_w++ = '\\'; + *dst_w++ = '/'; + break; + case '\\': + if (escapeAll == RSFALSE) { + ni = i + 1; + if (ni <= buflen) { + nc = pSrc[ni]; + + /* Attempt to not double encode */ + if ( nc == '"' || nc == '/' || nc == '\\' || nc == 'b' || nc == 'f' + || nc == 'n' || nc == 'r' || nc == 't' || nc == 'u') { + *dst_w++ = c; + *dst_w++ = nc; + i = ni; + break; + } + } + } + *dst_w++ = '\\'; + *dst_w++ = '\\'; + break; + case '\010': + *dst_w++ = '\\'; + *dst_w++ = 'b'; + break; + case '\014': + *dst_w++ = '\\'; + *dst_w++ = 'f'; + break; + case '\n': + *dst_w++ = '\\'; + *dst_w++ = 'n'; + break; + case '\r': + *dst_w++ = '\\'; + *dst_w++ = 'r'; + break; + case '\t': + *dst_w++ = '\\'; + *dst_w++ = 't'; + break; + default: + /* TODO : proper Unicode encoding (see header comment) */ + for(j = 0 ; j < 4 ; ++j) { + numbuf[3-j] = hexdigit[c % 16]; + c = c / 16; + } + *dst_w++ = '\\'; + *dst_w++ = 'u'; + *dst_w++ = numbuf[0]; + *dst_w++ = numbuf[1]; + *dst_w++ = numbuf[2]; + *dst_w++ = numbuf[3]; + break; + } + } + } + if(*dst == NULL) { + *dst = es_newStrFromBuf((char *) dst_base, dst_w - dst_base); + } else { + es_addBuf(dst, (const char *) dst_base, dst_w - dst_base); + } +finalize_it: + if(dst_base != wrkbuf) { + free(dst_base); + } + RETiRet; +} + + +/* Encode a JSON value and add it to provided string. Note that + * the string object may be NULL. In this case, it is created + * if and only if escaping is needed. if escapeAll is false, previously + * escaped strings are left as is + */ +static rsRetVal ATTR_NONNULL(1, 3) +jsonAddVal(uchar *const pSrc, const unsigned buflen, es_str_t **dst, const int escapeAll) +{ + es_size_t i; + DEFiRet; + + for(i = 0 ; i < buflen ; ++i) { + const uchar c = pSrc[i]; + if(! ( (c >= 0x30 && c <= 0x5b) + || (c >= 0x23 && c <= 0x2e) + || (c >= 0x5d /* && c <= 0x10FFFF*/) + || c == 0x20 || c == 0x21) + ) { + iRet = jsonAddVal_escaped(pSrc, buflen, i, dst, escapeAll); + FINALIZE; + } + } + if(*dst != NULL) { + es_addBuf(dst, (const char *) pSrc, buflen); + } +finalize_it: + RETiRet; +} + + +/* encode a property in JSON escaped format. This is a helper + * to MsgGetProp. It needs to update all provided parameters. + * For performance reasons, we begin to copy the string only + * when we recognice that we actually need to do some escaping. + * rgerhards, 2012-03-16 + */ +static rsRetVal +jsonEncode(uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen, int escapeAll) +{ + unsigned buflen; + uchar *pSrc; + es_str_t *dst = NULL; + DEFiRet; + + pSrc = *ppRes; + buflen = (*pBufLen == -1) ? (int) ustrlen(pSrc) : *pBufLen; + CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll)); + + if(dst != NULL) { + /* we updated the string and need to replace the + * previous data. + */ + if(*pbMustBeFreed) + free(*ppRes); + *ppRes = (uchar*)es_str2cstr(dst, NULL); + *pbMustBeFreed = 1; + *pBufLen = -1; + es_deleteStr(dst); + } + +finalize_it: + RETiRet; +} + + +/* Format a property as JSON field, that means + * "name"="value" + * where value is JSON-escaped (here we assume that the name + * only contains characters from the valid character set). + * Note: this function duplicates code from jsonEncode(). + * TODO: these two functions should be combined, at least if + * that makes any sense from a performance PoV - definitely + * something to consider at a later stage. rgerhards, 2012-04-19 + */ +static rsRetVal ATTR_NONNULL() +jsonField(const struct templateEntry *const pTpe, + uchar **const ppRes, + unsigned short *const pbMustBeFreed, + int *const pBufLen, + int escapeAll) +{ + unsigned buflen; + uchar *pSrc; + es_str_t *dst = NULL; + int is_numeric = 1; + DEFiRet; + + pSrc = *ppRes; + buflen = (*pBufLen == -1) ? (int) ustrlen(pSrc) : *pBufLen; +dbgprintf("jsonEncode: datatype: %u, onEmpty: %u val %*s\n", (unsigned) pTpe->data.field.options.dataType, +(unsigned) pTpe->data.field.options.onEmpty, buflen, pSrc); + if(buflen == 0) { + if(pTpe->data.field.options.onEmpty == TPE_DATAEMPTY_SKIP) { + FINALIZE; + } + is_numeric = 0; + } + /* we hope we have only few escapes... */ + dst = es_newStr(buflen+pTpe->lenFieldName+15); + es_addChar(&dst, '"'); + es_addBuf(&dst, (char*)pTpe->fieldName, pTpe->lenFieldName); + es_addBufConstcstr(&dst, "\":"); + if(buflen == 0 && pTpe->data.field.options.onEmpty == TPE_DATAEMPTY_NULL) { + es_addBufConstcstr(&dst, "null"); + } else { + if(pTpe->data.field.options.dataType == TPE_DATATYPE_AUTO) { + for(unsigned i = 0 ; i < buflen ; ++i) { + if(pSrc[i] < '0' || pSrc[i] > '9') { + is_numeric = 0; + break; + } + } + if(!is_numeric) { + es_addChar(&dst, '"'); + } + CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll)); + if(!is_numeric) { + es_addChar(&dst, '"'); + } + } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_STRING) { + es_addChar(&dst, '"'); + CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll)); + es_addChar(&dst, '"'); + } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_NUMBER) { + if(buflen == 0) { + es_addChar(&dst, '0'); + } else { + CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll)); + } + } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_BOOL) { + if(buflen == 1 && *pSrc == '0') { + es_addBufConstcstr(&dst, "false"); + } else { + es_addBufConstcstr(&dst, "true"); + } + } + } + + if(*pbMustBeFreed) + free(*ppRes); + /* we know we do not have \0 chars - so the size does not change */ + *pBufLen = es_strlen(dst); + *ppRes = (uchar*)es_str2cstr(dst, NULL); + *pbMustBeFreed = 1; + es_deleteStr(dst); + +finalize_it: + RETiRet; +} + + +/* This function returns a string-representation of the + * requested message property. This is a generic function used + * to abstract properties so that these can be easier + * queried. Returns NULL if property could not be found. + * Actually, this function is a big if..elseif. What it does + * is simply to map property names (from MonitorWare) to the + * message object data fields. + * + * In case we need string forms of propertis we do not + * yet have in string form, we do a memory allocation that + * is sufficiently large (in all cases). Once the string + * form has been obtained, it is saved until the Msg object + * is finally destroyed. This is so that we save the processing + * time in the (likely) case that this property is requested + * again. It also saves us a lot of dynamic memory management + * issues in the upper layers, because we so can guarantee that + * the buffer will remain static AND available during the lifetime + * of the object. Please note that both the max size allocation as + * well as keeping things in memory might like look like a + * waste of memory (some might say it actually is...) - we + * deliberately accept this because performance is more important + * to us ;) + * rgerhards 2004-11-18 + * Parameter "bMustBeFreed" is set by this function. It tells the + * caller whether or not the string returned must be freed by the + * caller itself. It is is 0, the caller MUST NOT free it. If it is + * 1, the caller MUST free it. Handling this wrongly leads to either + * a memory leak of a program abort (do to double-frees or frees on + * the constant memory pool). So be careful to do it right. + * rgerhards 2004-11-23 + * regular expression support contributed by Andres Riancho merged + * on 2005-09-13 + * changed so that it now an be called without a template entry (NULL). + * In this case, only the (unmodified) property is returned. This will + * be used in selector line processing. + * rgerhards 2005-09-15 + */ +/* a quick helper to save some writing: */ +#define RET_OUT_OF_MEMORY { *pbMustBeFreed = 0;\ + *pPropLen = sizeof("**OUT OF MEMORY**") - 1; \ + return(UCHAR_CONSTANT("**OUT OF MEMORY**"));} +uchar *MsgGetProp(smsg_t *__restrict__ const pMsg, struct templateEntry *__restrict__ const pTpe, + msgPropDescr_t *pProp, rs_size_t *__restrict__ const pPropLen, + unsigned short *__restrict__ const pbMustBeFreed, struct syslogTime * const ttNow) +{ + uchar *pRes; /* result pointer */ + rs_size_t bufLen = -1; /* length of string or -1, if not known */ + uchar *pBufStart; + uchar *pBuf; + int iLen; + short iOffs; + enum tplFormatTypes datefmt; + int bDateInUTC; + + assert(pMsg != NULL); + assert(pbMustBeFreed != NULL); + +#ifdef FEATURE_REGEXP + /* Variables necessary for regular expression matching */ + size_t nmatch = 10; + regmatch_t pmatch[10]; +#endif + + *pbMustBeFreed = 0; + + switch(pProp->id) { + case PROP_MSG: + pRes = getMSG(pMsg); + bufLen = getMSGLen(pMsg); + break; + case PROP_TIMESTAMP: + if(pTpe != NULL) { + datefmt = pTpe->data.field.eDateFormat; + bDateInUTC = pTpe->data.field.options.bDateInUTC; + } else { + datefmt = tplFmtDefault; + bDateInUTC = 0; + } + if(bDateInUTC) { + pRes = (uchar*)getTimeUTC(&pMsg->tTIMESTAMP, datefmt, pbMustBeFreed); + } else { + pRes = (uchar*)getTimeReported(pMsg, datefmt); + } + break; + case PROP_HOSTNAME: + pRes = (uchar*)getHOSTNAME(pMsg); + bufLen = getHOSTNAMELen(pMsg); + break; + case PROP_SYSLOGTAG: + getTAG(pMsg, &pRes, &bufLen, LOCK_MUTEX); + break; + case PROP_RAWMSG: + getRawMsg(pMsg, &pRes, &bufLen); + break; + case PROP_RAWMSG_AFTER_PRI: + getRawMsgAfterPRI(pMsg, &pRes, &bufLen); + break; + case PROP_INPUTNAME: + getInputName(pMsg, &pRes, &bufLen); + break; + case PROP_FROMHOST: + pRes = getRcvFrom(pMsg); + break; + case PROP_FROMHOST_IP: + pRes = getRcvFromIP(pMsg); + break; + case PROP_PRI: + pRes = (uchar*)getPRI(pMsg); + break; + case PROP_PRI_TEXT: + pRes = textpri(pMsg); + if(pRes == NULL) + RET_OUT_OF_MEMORY; + *pbMustBeFreed = 1; + break; + case PROP_IUT: + pRes = UCHAR_CONSTANT("1"); /* always 1 for syslog messages (a MonitorWare thing;)) */ + bufLen = 1; + break; + case PROP_SYSLOGFACILITY: + pRes = (uchar*)getFacility(pMsg); + break; + case PROP_SYSLOGFACILITY_TEXT: + pRes = (uchar*)getFacilityStr(pMsg); + break; + case PROP_SYSLOGSEVERITY: + pRes = (uchar*)getSeverity(pMsg); + break; + case PROP_SYSLOGSEVERITY_TEXT: + pRes = (uchar*)getSeverityStr(pMsg); + break; + case PROP_TIMEGENERATED: + if(pTpe != NULL) { + datefmt = pTpe->data.field.eDateFormat; + bDateInUTC = pTpe->data.field.options.bDateInUTC; + } else { + datefmt = tplFmtDefault; + bDateInUTC = 0; + } + if(bDateInUTC) { + pRes = (uchar*)getTimeUTC(&pMsg->tRcvdAt, datefmt, pbMustBeFreed); + } else { + pRes = (uchar*)getTimeGenerated(pMsg, datefmt); + } + break; + case PROP_PROGRAMNAME: + pRes = getProgramName(pMsg, LOCK_MUTEX); + break; + case PROP_PROTOCOL_VERSION: + pRes = (uchar*)getProtocolVersionString(pMsg); + break; + case PROP_STRUCTURED_DATA: + MsgGetStructuredData(pMsg, &pRes, &bufLen); + break; + case PROP_APP_NAME: + pRes = (uchar*)getAPPNAME(pMsg, LOCK_MUTEX); + break; + case PROP_PROCID: + pRes = (uchar*)getPROCID(pMsg, LOCK_MUTEX); + break; + case PROP_MSGID: + pRes = (uchar*)getMSGID(pMsg); + break; + case PROP_JSONMESG: + pRes = (uchar*)msgGetJSONMESG(pMsg); + *pbMustBeFreed = 1; + break; +#ifdef USE_LIBUUID + case PROP_UUID: + getUUID(pMsg, &pRes, &bufLen); + break; +#endif + case PROP_PARSESUCCESS: + pRes = (uchar*)getParseSuccess(pMsg); + break; + case PROP_SYS_NOW: + if((pRes = getNOW(NOW_NOW, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 10; + } + break; + case PROP_SYS_YEAR: + if((pRes = getNOW(NOW_YEAR, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 4; + } + break; + case PROP_SYS_MONTH: + if((pRes = getNOW(NOW_MONTH, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_DAY: + if((pRes = getNOW(NOW_DAY, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HOUR: + if((pRes = getNOW(NOW_HOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HHOUR: + if((pRes = getNOW(NOW_HHOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_QHOUR: + if((pRes = getNOW(NOW_QHOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_MINUTE: + if((pRes = getNOW(NOW_MINUTE, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_NOW_UTC: + if((pRes = getNOW(NOW_NOW, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 10; + } + break; + case PROP_SYS_YEAR_UTC: + if((pRes = getNOW(NOW_YEAR, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 4; + } + break; + case PROP_SYS_MONTH_UTC: + if((pRes = getNOW(NOW_MONTH, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_DAY_UTC: + if((pRes = getNOW(NOW_DAY, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HOUR_UTC: + if((pRes = getNOW(NOW_HOUR, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_HHOUR_UTC: + if((pRes = getNOW(NOW_HHOUR, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_QHOUR_UTC: + if((pRes = getNOW(NOW_QHOUR, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_MINUTE_UTC: + if((pRes = getNOW(NOW_MINUTE, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 2; + } + break; + case PROP_SYS_WDAY: + if((pRes = getNOW(NOW_WDAY, ttNow, TIME_IN_LOCALTIME)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 1; + } + break; + case PROP_SYS_WDAY_UTC: + if((pRes = getNOW(NOW_WDAY, ttNow, TIME_IN_UTC)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + *pbMustBeFreed = 1; + bufLen = 1; + } + break; + case PROP_SYS_NOW_UXTIMESTAMP: + if((pRes = malloc(16)) == NULL) { + RET_OUT_OF_MEMORY; + } else { + snprintf((char*) pRes, 16-1, "%lld", (long long) getTime(NULL)); + pRes[16-1] = '\0'; + *pbMustBeFreed = 1; + bufLen = -1; + } + break; + case PROP_SYS_MYHOSTNAME: + pRes = glbl.GetLocalHostName(); + break; + case PROP_CEE_ALL_JSON: + case PROP_CEE_ALL_JSON_PLAIN: + if(pMsg->json == NULL) { + pRes = (uchar*) "{}"; + bufLen = 2; + *pbMustBeFreed = 0; + } else { + const char *jstr; + MsgLock(pMsg); + int jflag = 0; + if(pProp->id == PROP_CEE_ALL_JSON) { + jflag = JSON_C_TO_STRING_SPACED; + } else if(pProp->id == PROP_CEE_ALL_JSON_PLAIN) { + jflag = JSON_C_TO_STRING_PLAIN; + } + jstr = json_object_to_json_string_ext(pMsg->json, jflag); + MsgUnlock(pMsg); + if(jstr == NULL) { + RET_OUT_OF_MEMORY; + } + pRes = (uchar*)strdup(jstr); + if(pRes == NULL) { + RET_OUT_OF_MEMORY; + } + *pbMustBeFreed = 1; + } + break; + case PROP_CEE: + case PROP_LOCAL_VAR: + case PROP_GLOBAL_VAR: + getJSONPropVal(pMsg, pProp, &pRes, &bufLen, pbMustBeFreed); + break; + case PROP_SYS_BOM: + pRes = (uchar*) "\xEF\xBB\xBF"; + *pbMustBeFreed = 0; + break; + case PROP_SYS_UPTIME: +# ifndef HAVE_SYSINFO_UPTIME + /* An alternative on some systems (eg Solaris) is to scan + * /var/adm/utmpx for last boot time. + */ + pRes = (uchar*) "UPTIME NOT available on this system"; + *pbMustBeFreed = 0; + +# elif defined(__FreeBSD__) + + { + struct timespec tp; + + if((pRes = (uchar*) malloc(32)) == NULL) { + RET_OUT_OF_MEMORY; + } + + if(clock_gettime(CLOCK_UPTIME, &tp) == -1) { + free(pRes); + *pPropLen = sizeof("**SYSCALL FAILED**") - 1; + return(UCHAR_CONSTANT("**SYSCALL FAILED**")); + } + + *pbMustBeFreed = 1; + + snprintf((char*) pRes, 32, "%ld", tp.tv_sec); + } + +# else + + { + struct sysinfo s_info; + + if((pRes = (uchar*) malloc(32)) == NULL) { + RET_OUT_OF_MEMORY; + } + + if(sysinfo(&s_info) < 0) { + free(pRes); + *pPropLen = sizeof("**SYSCALL FAILED**") - 1; + return(UCHAR_CONSTANT("**SYSCALL FAILED**")); + } + + *pbMustBeFreed = 1; + + snprintf((char*) pRes, 32, "%ld", s_info.uptime); + } +# endif + break; + default: + /* there is no point in continuing, we may even otherwise render the + * error message unreadable. rgerhards, 2007-07-10 + */ + dbgprintf("invalid property id: '%d'\n", pProp->id); + *pbMustBeFreed = 0; + *pPropLen = sizeof("**INVALID PROPERTY NAME**") - 1; + return UCHAR_CONSTANT("**INVALID PROPERTY NAME**"); + } + + /* If we did not receive a template pointer, we are already done... */ + if(pTpe == NULL || !pTpe->bComplexProcessing) { + *pPropLen = (bufLen == -1) ? (int) ustrlen(pRes) : bufLen; + return pRes; + } + + /* Now check if we need to make "temporary" transformations (these + * are transformations that do not go back into the message - + * memory must be allocated for them!). + */ + + /* substring extraction */ + /* first we check if we need to extract by field number + * rgerhards, 2005-12-22 + */ + if(pTpe->data.field.has_fields == 1) { + size_t iCurrFld; + uchar *pFld; + uchar *pFldEnd; + /* first, skip to the field in question. The field separator + * is always one character and is stored in the template entry. + */ + iCurrFld = 1; + pFld = pRes; + while(*pFld && iCurrFld < pTpe->data.field.iFieldNr) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) + ++pFld; /* skip to field terminator */ + if(*pFld == pTpe->data.field.field_delim) { + ++pFld; /* eat it */ +#ifdef STRICT_GPLV3 + if (pTpe->data.field.field_expand != 0) { + while (*pFld == pTpe->data.field.field_delim) { + ++pFld; + } + } +#endif + ++iCurrFld; + } + } + dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iFieldNr, (int) iCurrFld); + + if(iCurrFld == pTpe->data.field.iFieldNr) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + /* TODO: code copied from below, this is a candidate for a separate function */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc(iLen + 1); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + /* now copy */ + memcpy(pBuf, pFld, iLen); + bufLen = iLen; + pBuf[iLen] = '\0'; /* terminate it */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + } else { + /* field not found, return error */ + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + *pPropLen = sizeof("**FIELD NOT FOUND**") - 1; + return UCHAR_CONSTANT("**FIELD NOT FOUND**"); + } +#ifdef FEATURE_REGEXP + } else { + /* Check for regular expressions */ + if (pTpe->data.field.has_regex != 0) { + if (pTpe->data.field.has_regex == 2) { + /* Could not compile regex before! */ + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + *pPropLen = sizeof("**NO MATCH** **BAD REGULAR EXPRESSION**") - 1; + return UCHAR_CONSTANT("**NO MATCH** **BAD REGULAR EXPRESSION**"); + } + + dbgprintf("string to match for regex is: %s\n", pRes); + + if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { + short iTry = 0; + uchar bFound = 0; + iOffs = 0; + /* first see if we find a match, iterating through the series of + * potential matches over the string. + */ + while(!bFound) { + int iREstat; + iREstat = regexp.regexec(&pTpe->data.field.re, (char*)(pRes + iOffs), + nmatch, pmatch, 0); + dbgprintf("regexec return is %d\n", iREstat); + if(iREstat == 0) { + if(pmatch[0].rm_so == -1) { + dbgprintf("oops ... start offset of successful " + "regexec is -1\n"); + break; + } + if(iTry == pTpe->data.field.iMatchToUse) { + bFound = 1; + } else { + dbgprintf("regex found at offset %d, new offset %d, " + "tries %d\n", iOffs, + (int) (iOffs + pmatch[0].rm_eo), iTry); + iOffs += pmatch[0].rm_eo; + ++iTry; + } + } else { + break; + } + } + dbgprintf("regex: end search, found %d\n", bFound); + if(!bFound) { + /* we got no match! */ + if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) { + bufLen = sizeof("**NO MATCH**") - 1; + pRes = UCHAR_CONSTANT("**NO MATCH**"); + } else if(pTpe->data.field.nomatchAction == + TPL_REGEX_NOMATCH_USE_ZERO) { + bufLen = 1; + pRes = UCHAR_CONSTANT("0"); + } else { + bufLen = 0; + pRes = UCHAR_CONSTANT(""); + } + } + } else { + /* Match- but did it match the one we wanted? */ + /* we got no match! */ + if(pmatch[pTpe->data.field.iSubMatchToUse].rm_so == -1) { + if(pTpe->data.field.nomatchAction != + TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) { + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + if(pTpe->data.field.nomatchAction == + TPL_REGEX_NOMATCH_USE_DFLTSTR) { + bufLen = sizeof("**NO MATCH**") - 1; + pRes = UCHAR_CONSTANT("**NO MATCH**"); + } else if(pTpe->data.field.nomatchAction == + TPL_REGEX_NOMATCH_USE_ZERO) { + bufLen = 1; + pRes = UCHAR_CONSTANT("0"); + } else { + bufLen = 0; + pRes = UCHAR_CONSTANT(""); + } + } + } + /* OK, we have a usable match - we now need to malloc pB */ + int iLenBuf; + uchar *pB; + + iLenBuf = pmatch[pTpe->data.field.iSubMatchToUse].rm_eo + - pmatch[pTpe->data.field.iSubMatchToUse].rm_so; + pB = malloc(iLenBuf + 1); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + iOffs + pmatch[pTpe->data.field.iSubMatchToUse].rm_so, + iLenBuf); + bufLen = iLenBuf; + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ + + if (*pbMustBeFreed == 1) + free(pRes); + pRes = pB; + *pbMustBeFreed = 1; + } + } else { + /* we could not load regular expression support. This is quite unexpected at + * this stage of processing (after all, the config parser found it), but so + * it is. We return an error in that case. -- rgerhards, 2008-03-07 + */ + dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n"); + if (*pbMustBeFreed == 1) { + free(pRes); + *pbMustBeFreed = 0; + } + *pPropLen = sizeof("***REGEXP NOT AVAILABLE***") - 1; + return UCHAR_CONSTANT("***REGEXP NOT AVAILABLE***"); + } + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + /* we need to obtain a private copy */ + int iFrom, iTo; + uchar *pSb; + iFrom = pTpe->data.field.iFromPos; + iTo = pTpe->data.field.iToPos; + if(bufLen == -1) + bufLen = ustrlen(pRes); + if(pTpe->data.field.options.bFromPosEndRelative) { + iFrom = (bufLen < iFrom) ? 0 : bufLen - iFrom; + iTo = (bufLen < iTo)? 0 : bufLen - iTo; + } else { + /* need to zero-base to and from (they are 1-based!) */ + if(iFrom > 0) + --iFrom; + if(iTo > 0) { + --iTo; + } else if(iTo < 0) { + /* note: we ADD negative value, 0-based (-1)! */ + iTo = bufLen - 1 + iTo; + if(iTo < 0) { + iTo = 0; + } + } + } + if(iFrom >= bufLen) { + DBGPRINTF("msgGetProp: iFrom %d >= buflen %d, returning empty string\n", + iFrom, bufLen); + if(*pbMustBeFreed == 1) + free(pRes); + pRes = (uchar*) ""; + *pbMustBeFreed = 0; + bufLen = 0; + } else if(iFrom == 0 && iTo >= bufLen && pTpe->data.field.options.bFixedWidth == 0) { + /* in this case, the requested string is a superset of what we already have, + * so there is no need to do any processing. This is a frequent case for size-limited + * fields like TAG in the default forwarding template (so it is a useful optimization + * to check for this condition ;)). -- rgerhards, 2009-07-09 + */ + ; /*DO NOTHING*/ + } else { + if(iTo >= bufLen) /* iTo is very large, if no to-position is set in the template! */ + if (pTpe->data.field.options.bFixedWidth == 0) + iTo = bufLen - 1; + + iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc(iLen + 1); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSb = pRes; + if(iFrom) { + /* skip to the start of the substring (can't do pointer arithmetic + * because the whole string might be smaller!!) + */ + while(*pSb && iFrom) { + --iFrom; + ++pSb; + } + } + /* OK, we are at the begin - now let's copy... */ + bufLen = iLen; + while(iLen) { + if (*pSb) { + *pBuf++ = *pSb; + ++pSb; + } else { + *pBuf++ = ' '; + } + --iLen; + } + *pBuf = '\0'; + bufLen -= iLen; /* subtract remaining length if the string was smaller! */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + } + } + + /* now check if we need to do our "SP if first char is non-space" hack logic */ + if(*pRes && pTpe->data.field.options.bSPIffNo1stSP) { + /* here, we always destruct the buffer and return a new one */ + uchar cFirst = *pRes; /* save first char */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = (cFirst == ' ') ? UCHAR_CONSTANT("") : UCHAR_CONSTANT(" "); + bufLen = (cFirst == ' ') ? 0 : 1; + *pbMustBeFreed = 0; + } + + if(*pRes) { + /* case conversations (should go after substring, because so we are able to + * work on the smallest possible buffer). + */ + if(pTpe->data.field.eCaseConv != tplCaseConvNo) { + /* we need to obtain a private copy */ + if(bufLen == -1) + bufLen = ustrlen(pRes); + uchar *pBStart; + uchar *pB; + uchar *pSrc; + pBStart = pB = malloc(bufLen + 1); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSrc = pRes; + while(*pSrc) { + *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? + (uchar)toupper((int)*pSrc) : (uchar)tolower((int)*pSrc); + /* currently only these two exist */ + ++pSrc; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + + /* now do control character dropping/escaping/replacement + * Only one of these can be used. If multiple options are given, the + * result is random (though currently there obviously is an order of + * preferrence, see code below. But this is NOT guaranteed. + * RGerhards, 2006-11-17 + * We must copy the strings if we modify them, because they may either + * point to static memory or may point into the message object, in which + * case we would actually modify the original property (which of course + * is wrong). + * This was found and fixed by varmojefkoj on 2007-09-11 + */ + if(pTpe->data.field.options.bDropCC) { + int iLenBuf = 0; + uchar *pSrc = pRes; + uchar *pDstStart; + uchar *pDst; + uchar bDropped = 0; + + while(*pSrc) { + if(!iscntrl((int) *pSrc++)) + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(!iscntrl((int) *pSrc)) + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + bufLen = iLenBuf; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bSpaceCC) { + uchar *pSrc; + uchar *pDstStart; + uchar *pDst; + + if(*pbMustBeFreed == 1) { + /* in this case, we already work on dynamic + * memory, so there is no need to copy it - we can + * modify it in-place without any harm. This is a + * performance optiomization. + */ + for(pDst = pRes; *pDst; pDst++) { + if(iscntrl((int) *pDst)) + *pDst = ' '; + } + } else { + if(bufLen == -1) + bufLen = ustrlen(pRes); + pDst = pDstStart = malloc(bufLen + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(iscntrl((int) *pSrc)) + *pDst++ = ' '; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } else if(pTpe->data.field.options.bEscapeCC) { + /* we must first count how many control charactes are + * present, because we need this to compute the new string + * buffer length. While doing so, we also compute the string + * length. + */ + int iNumCC = 0; + int iLenBuf = 0; + uchar *pSrc; + uchar *pB; + + for(pB = pRes ; *pB ; ++pB) { + ++iLenBuf; + if(iscntrl((int) *pB)) + ++iNumCC; + } + + if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ + /* OK, let's do the escaping... */ + uchar *pBStart; + uchar szCCEsc[8]; /* buffer for escape sequence */ + int i; + + iLenBuf += iNumCC * 4; + pBStart = pB = malloc(iLenBuf + 1); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(iscntrl((int) *pSrc)) { + snprintf((char*)szCCEsc, sizeof(szCCEsc), "#%3.3d", *pSrc); + for(i = 0 ; i < 4 ; ++i) + *pB++ = szCCEsc[i]; + } else { + *pB++ = *pSrc; + } + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + bufLen = -1; + *pbMustBeFreed = 1; + } + } + } + + /* Take care of spurious characters to make the property safe + * for a path definition + */ + if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) { + if(pTpe->data.field.options.bSecPathDrop) { + int iLenBuf = 0; + uchar *pSrc = pRes; + uchar *pDstStart; + uchar *pDst; + uchar bDropped = 0; + + while(*pSrc) { + if(*pSrc++ != '/') + iLenBuf++; + else + bDropped = 1; + } + + if(bDropped) { + pDst = pDstStart = malloc(iLenBuf + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc != '/') + *pDst++ = *pSrc; + } + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pDstStart; + bufLen = -1; /* TODO: can we do better? */ + *pbMustBeFreed = 1; + } + } else { + uchar *pSrc; + uchar *pDstStart; + uchar *pDst; + + if(*pbMustBeFreed == 1) { + /* here, again, we can modify the string as we already obtained + * a private buffer. As we do not change the size of that buffer, + * in-place modification is possible. This is a performance + * enhancement. + */ + for(pDst = pRes; *pDst; pDst++) { + if(*pDst == '/') + *pDst++ = '_'; + } + } else { + if(bufLen == -1) + bufLen = ustrlen(pRes); + pDst = pDstStart = malloc(bufLen + 1); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + for(pSrc = pRes; *pSrc; pSrc++) { + if(*pSrc == '/') + *pDst++ = '_'; + else + *pDst++ = *pSrc; + } + *pDst = '\0'; + /* we must NOT check if it needs to be freed, because we have done + * this in the if above. So if we come to hear, the pSrc string needs + * not to be freed (and we do not need to care about it). + */ + pRes = pDstStart; + *pbMustBeFreed = 1; + } + } + + /* check for "." and ".." (note the parenthesis in the if condition!) */ + if(*pRes == '\0') { + if(*pbMustBeFreed == 1) + free(pRes); + pRes = UCHAR_CONSTANT("_"); + bufLen = 1; + *pbMustBeFreed = 0; + } else if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) { + uchar *pTmp = pRes; + + if(*(pRes + 1) == '\0') + pRes = UCHAR_CONSTANT("_"); + else + pRes = UCHAR_CONSTANT("_.");; + if(*pbMustBeFreed == 1) + free(pTmp); + *pbMustBeFreed = 0; + } + } + + /* Now drop last LF if present (pls note that this must not be done + * if bEscapeCC was set)! + */ + if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { + int iLn; + uchar *pB; + if(bufLen == -1) + bufLen = ustrlen(pRes); + iLn = bufLen; + if(iLn > 0 && *(pRes + iLn - 1) == '\n') { + /* we have a LF! */ + /* check if we need to obtain a private copy */ + if(*pbMustBeFreed == 0) { + /* ok, original copy, need a private one */ + pB = malloc(iLn + 1); + if(pB == NULL) { + RET_OUT_OF_MEMORY; + } + memcpy(pB, pRes, iLn - 1); + pRes = pB; + *pbMustBeFreed = 1; + } + *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ + --bufLen; + } + } + + /* Now everything is squased as much as possible and more or less ready to + * go. This is the perfect place to compress any remaining spaces, if so + * instructed by the user/config. + */ + if(pTpe->data.field.options.bCompressSP) { + int needCompress = 0; + int hadSP = 0; + uchar *pB; + if(*pbMustBeFreed == 0) { + for(pB = pRes ; *pB && needCompress == 0 ; ++pB) { + if(*pB == ' ') { + if(hadSP) { + uchar *const tmp = ustrdup(pRes); + if(tmp == NULL) + /* better not compress than + * loose message. */ + break; + *pbMustBeFreed = 1; + pRes = tmp; + needCompress = 1; + } else { + hadSP = 1; + } + } + } + } else { + /* If we can modify the buffer in any case, we + * do NOT check if we actually need to compress, + * but "just do it" - that's the quickest way + * to get it done. + */ + needCompress = 1; + } + if(needCompress) { + hadSP = 0; + uchar *pDst = pRes; + int needCopy = 0; + for(pB = pRes ; *pB ; ++pB) { + if(*pB == ' ') { + if(hadSP) { + needCopy = 1; + } else { + hadSP = 1; + if(needCopy) + *pDst = *pB; + ++pDst; + } + } else { + hadSP = 0; + if(needCopy) + *pDst = *pB; + ++pDst; + } + } + *pDst = '\0'; + bufLen = pDst - pRes; + } + } + + /* finally, we need to check if the property should be formatted in CSV or JSON. + * For CSV we use RFC 4180, and always use double quotes. As of this writing, + * this should be the last action carried out on the property, but in the + * future there may be reasons to change that. -- rgerhards, 2009-04-02 + */ + if(pTpe->data.field.options.bCSV) { + /* we need to obtain a private copy, as we need to at least add the double quotes */ + int iBufLen; + uchar *pBStart; + uchar *pDst; + uchar *pSrc; + if(bufLen == -1) + bufLen = ustrlen(pRes); + iBufLen = bufLen; + /* the malloc may be optimized, we currently use the worst case... */ + pBStart = pDst = malloc(2 * iBufLen + 3); + if(pDst == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + RET_OUT_OF_MEMORY; + } + pSrc = pRes; + *pDst++ = '"'; /* starting quote */ + while(*pSrc) { + if(*pSrc == '"') + *pDst++ = '"'; /* need to add double double quote (see RFC4180) */ + *pDst++ = *pSrc++; + } + *pDst++ = '"'; /* ending quote */ + *pDst = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + bufLen = -1; + *pbMustBeFreed = 1; + } else if(pTpe->data.field.options.bJSON) { + jsonEncode(&pRes, pbMustBeFreed, &bufLen, RSTRUE); + } else if(pTpe->data.field.options.bJSONf) { + jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen, RSTRUE); + } else if(pTpe->data.field.options.bJSONr) { + jsonEncode(&pRes, pbMustBeFreed, &bufLen, RSFALSE); + } else if(pTpe->data.field.options.bJSONfr) { + jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen, RSFALSE); + } + + *pPropLen = (bufLen == -1) ? (int) ustrlen(pRes) : bufLen; + + return(pRes); +} + +/* Set a single property based on the JSON object provided. The + * property name is extracted from the JSON object. + */ +static rsRetVal +msgSetPropViaJSON(smsg_t *__restrict__ const pMsg, const char *name, struct json_object *json, int sharedReference) +{ + const char *psz; + int val; + prop_t *propFromHost = NULL; + prop_t *propRcvFromIP = NULL; + int bNeedFree = 1; + DEFiRet; + + /* note: json_object_get_string() manages the memory of the returned + * string. So we MUST NOT free it! + */ + dbgprintf("DDDD: msgSetPropViaJSON key: '%s'\n", name); + if(!strcmp(name, "rawmsg")) { + psz = json_object_get_string(json); + MsgSetRawMsg(pMsg, psz, strlen(psz)); + } else if(!strcmp(name, "msg")) { + psz = json_object_get_string(json); + MsgReplaceMSG(pMsg, (const uchar*)psz, strlen(psz)); + } else if(!strcmp(name, "syslogtag")) { + psz = json_object_get_string(json); + MsgSetTAG(pMsg, (const uchar*)psz, strlen(psz)); + } else if(!strcmp(name, "pri")) { + val = json_object_get_int(json); + msgSetPRI(pMsg, val); + } else if(!strcmp(name, "syslogfacility")) { + val = json_object_get_int(json); + if(val >= 0 && val <= 24) + pMsg->iFacility = val; + else + DBGPRINTF("mmexternal: invalid fac %d requested -- ignored\n", val); + } else if(!strcmp(name, "syslogseverity")) { + val = json_object_get_int(json); + if(val >= 0 && val <= 7) + pMsg->iSeverity = val; + else + DBGPRINTF("mmexternal: invalid fac %d requested -- ignored\n", val); + } else if(!strcmp(name, "procid")) { + psz = json_object_get_string(json); + MsgSetPROCID(pMsg, psz); + } else if(!strcmp(name, "msgid")) { + psz = json_object_get_string(json); + MsgSetMSGID(pMsg, psz); + } else if(!strcmp(name, "structured-data")) { + psz = json_object_get_string(json); + MsgSetStructuredData(pMsg, psz); + } else if(!strcmp(name, "hostname") || !strcmp(name, "source")) { + psz = json_object_get_string(json); + MsgSetHOSTNAME(pMsg, (const uchar*)psz, strlen(psz)); + } else if(!strcmp(name, "fromhost")) { + psz = json_object_get_string(json); + MsgSetRcvFromStr(pMsg, (const uchar*) psz, strlen(psz), &propFromHost); + prop.Destruct(&propFromHost); + } else if(!strcmp(name, "fromhost-ip")) { + psz = json_object_get_string(json); + MsgSetRcvFromIPStr(pMsg, (const uchar*)psz, strlen(psz), &propRcvFromIP); + prop.Destruct(&propRcvFromIP); + } else if(!strcmp(name, "$!")) { + /* msgAddJSON expects that it can keep the object without incremeting + * the json reference count. So we MUST NOT free (_put) the object in + * this case. -- rgerhards, 2018-09-14 + */ + bNeedFree = 0; + msgAddJSON(pMsg, (uchar*)"!", json, 0, sharedReference); + } else { + /* we ignore unknown properties */ + DBGPRINTF("msgSetPropViaJSON: unkonwn property ignored: %s\n", + name); + } + + if(bNeedFree) { + json_object_put(json); + } + + RETiRet; +} + + +/* set message properties based on JSON string. This function does it all, + * including parsing the JSON string. If an error is detected, the operation + * is aborted at the time of error. Any modifications made before the + * error ocurs are still PERSISTED. + * This function is meant to support the external message modifiction module + * interface. As such, replacing properties is expressively permited. Note that + * properties which were derived from the message during parsing are NOT + * updated if the underlying (raw)msg property is changed. + */ +rsRetVal +MsgSetPropsViaJSON(smsg_t *__restrict__ const pMsg, const uchar *__restrict__ const jsonstr) +{ + struct json_tokener *tokener = NULL; + struct json_object *json; + const char *errMsg; + DEFiRet; + + DBGPRINTF("DDDDDD: JSON string for message mod: '%s'\n", jsonstr); + if(!strcmp((char*)jsonstr, "{}")) /* shortcut for a common case */ + FINALIZE; + + tokener = json_tokener_new(); + + json = json_tokener_parse_ex(tokener, (char*)jsonstr, ustrlen(jsonstr)); + if(Debug) { + errMsg = NULL; + if(json == NULL) { + enum json_tokener_error err; + + err = tokener->err; + if(err != json_tokener_continue) + errMsg = json_tokener_error_desc(err); + else + errMsg = "Unterminated input"; + } else if(!json_object_is_type(json, json_type_object)) + errMsg = "JSON value is not an object"; + if(errMsg != NULL) { + DBGPRINTF("MsgSetPropsViaJSON: Error parsing JSON '%s': %s\n", + jsonstr, errMsg); + } + } + if(json == NULL || !json_object_is_type(json, json_type_object)) { + ABORT_FINALIZE(RS_RET_JSON_UNUSABLE); + } + MsgSetPropsViaJSON_Object(pMsg, json); + +finalize_it: + if(tokener != NULL) + json_tokener_free(tokener); + RETiRet; +} + + +/* Used by MsgSetPropsViaJSON to set properties. + * The same as MsgSetPropsViaJSON only that a json object is given and not a string + */ +rsRetVal +MsgSetPropsViaJSON_Object(smsg_t *__restrict__ const pMsg, struct json_object *json) +{ + DEFiRet; + if(json == NULL || !json_object_is_type(json, json_type_object)) { + DBGPRINTF("MsgSetPropsViaJSON_Object: json NULL or not object type\n"); + ABORT_FINALIZE(RS_RET_JSON_UNUSABLE); + } + struct json_object_iterator it = json_object_iter_begin(json); + struct json_object_iterator itEnd = json_object_iter_end(json); + while (!json_object_iter_equal(&it, &itEnd)) { + struct json_object *child = json_object_iter_peek_value(&it); + json_object_get(child); + msgSetPropViaJSON(pMsg, json_object_iter_peek_name(&it), + child, 0); + json_object_iter_next(&it); + } + json_object_put(json); + +finalize_it: + RETiRet; +} + + +/* get the severity - this is an entry point that + * satisfies the base object class getSeverity semantics. + * rgerhards, 2008-01-14 + */ +rsRetVal +MsgGetSeverity(smsg_t * const pMsg, int *piSeverity) +{ + *piSeverity = pMsg->iSeverity; + return RS_RET_OK; +} + + +static uchar * +jsonPathGetLeaf(uchar *name, int lenName) +{ + int i; + for(i = lenName ; i >= 0 ; --i) + if(i == 0) { + if(name[0] == '!' || name[0] == '.' || name[0] == '/') + break; + } else { + if(name[i] == '!') + break; + } + if(name[i] == '!' || name[i] == '.' || name[i] == '/') + ++i; + return name + i; +} + +static json_bool jsonVarExtract(struct json_object* root, const char *key, struct json_object **value) { + char namebuf[MAX_VARIABLE_NAME_LEN]; + int key_len = strlen(key); + char *array_idx_start = strstr(key, "["); + char *array_idx_end = NULL; + char *array_idx_num_end_discovered = NULL; + struct json_object *arr = NULL; + if (array_idx_start != NULL) { + array_idx_end = strstr(array_idx_start, "]"); + } + if (array_idx_end != NULL && (array_idx_end - key + 1) == key_len) { + errno = 0; + int idx = (int) strtol(array_idx_start + 1, &array_idx_num_end_discovered, 10); + if (errno == 0 && array_idx_num_end_discovered == array_idx_end) { + memcpy(namebuf, key, array_idx_start - key); + namebuf[array_idx_start - key] = '\0'; + json_bool found_obj = json_object_object_get_ex(root, namebuf, &arr); + if (found_obj && json_object_is_type(arr, json_type_array)) { + int len = json_object_array_length(arr); + if (len > idx) { + *value = json_object_array_get_idx(arr, idx); + if (*value != NULL) return TRUE; + } + return FALSE; + } + } + } + return json_object_object_get_ex(root, key, value); +} + + +static rsRetVal +jsonPathFindNext(struct json_object *root, uchar *namestart, uchar **name, uchar *leaf, + struct json_object **found, int bCreate) +{ + uchar namebuf[MAX_VARIABLE_NAME_LEN]; + struct json_object *json; + size_t i; + uchar *p = *name; + DEFiRet; + + if(*p == '!' || (*name == namestart && (*p == '.' || *p == '/'))) + ++p; + for(i = 0 ; *p && !(p == namestart && (*p == '.' || *p == '/')) && *p != '!' + && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p) + namebuf[i] = *p; + if(i > 0) { + namebuf[i] = '\0'; + if(jsonVarExtract(root, (char*)namebuf, &json) == FALSE) { + json = NULL; + } + } else + json = root; + if(json == NULL) { + if(!bCreate) { + ABORT_FINALIZE(RS_RET_JNAME_INVALID); + } else { + if (json_object_get_type(root) != json_type_object) { + DBGPRINTF("jsonPathFindNext with bCreate: not a container in json path, " + "name is '%s'\n", namestart); + ABORT_FINALIZE(RS_RET_INVLD_SETOP); + } + json = json_object_new_object(); + json_object_object_add(root, (char*)namebuf, json); + } + } + + *name = p; + *found = json; +finalize_it: + RETiRet; +} + +static rsRetVal +jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf, struct json_object **parent, + const int bCreate) +{ + uchar *namestart; + DEFiRet; + namestart = name; + *parent = jroot; + while(name < leaf-1) { + CHKiRet(jsonPathFindNext(*parent, namestart, &name, leaf, parent, bCreate)); + } + if(*parent == NULL) + ABORT_FINALIZE(RS_RET_NOT_FOUND); +finalize_it: + RETiRet; +} + +static rsRetVal +jsonMerge(struct json_object *existing, struct json_object *json) +{ + /* TODO: check & handle duplicate names */ + DEFiRet; + + struct json_object_iterator it = json_object_iter_begin(json); + struct json_object_iterator itEnd = json_object_iter_end(json); + while (!json_object_iter_equal(&it, &itEnd)) { + json_object_object_add(existing, json_object_iter_peek_name(&it), + json_object_get(json_object_iter_peek_value(&it))); + json_object_iter_next(&it); + } + /* note: json-c does ref counting. We added all descandants refcounts + * in the loop above. So when we now free(_put) the root object, only + * root gets freed(). + */ + json_object_put(json); + RETiRet; +} + +/* find a JSON structure element (field or container doesn't matter). */ +rsRetVal +jsonFind(smsg_t *const pMsg, msgPropDescr_t *pProp, struct json_object **jsonres) +{ + uchar *leaf; + struct json_object *parent; + struct json_object *field; + struct json_object **jroot = NULL; + pthread_mutex_t *mut = NULL; + DEFiRet; + + CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut)); + pthread_mutex_lock(mut); + + if(*jroot == NULL) { + field = NULL; + goto finalize_it; + } + + if(!strcmp((char*)pProp->name, "!")) { + field = *jroot; + } else if(!strcmp((char*)pProp->name, ".")) { + field = *jroot; + } else { + leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen); + CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0)); + if(jsonVarExtract(parent, (char*)leaf, &field) == FALSE) + field = NULL; + } + *jsonres = field; + +finalize_it: + if(mut != NULL) + pthread_mutex_unlock(mut); + RETiRet; +} + +/* check if JSON variable exists (works on terminal var and container) */ +rsRetVal ATTR_NONNULL() +msgCheckVarExists(smsg_t *const pMsg, msgPropDescr_t *pProp) +{ + struct json_object *jsonres = NULL; + DEFiRet; + + CHKiRet(jsonFind(pMsg, pProp, &jsonres)); + if(jsonres == NULL) { + iRet = RS_RET_NOT_FOUND; + } + +finalize_it: + RETiRet; +} + +rsRetVal +msgAddJSON(smsg_t * const pM, uchar *name, struct json_object *json, int force_reset, int sharedReference) +{ + /* TODO: error checks! This is a quick&dirty PoC! */ + struct json_object **jroot; + struct json_object *parent, *leafnode; + struct json_object *given = NULL; + uchar *leaf; + pthread_mutex_t *mut = NULL; + DEFiRet; + + CHKiRet(getJSONRootAndMutexByVarChar(pM, name[0], &jroot, &mut)); + pthread_mutex_lock(mut); + + if(name[0] == '/') { /* globl var special handling */ + if (sharedReference) { + given = json; + json = jsonDeepCopy(json); + json_object_put(given); + } + } + + if(name[1] == '\0') { /* full tree? */ + if(*jroot == NULL) + *jroot = json; + else + CHKiRet(jsonMerge(*jroot, json)); + } else { + if(*jroot == NULL) { + /* now we need a root obj */ + *jroot = json_object_new_object(); + } + leaf = jsonPathGetLeaf(name, ustrlen(name)); + iRet = jsonPathFindParent(*jroot, name, leaf, &parent, 1); + if (unlikely(iRet != RS_RET_OK)) { + json_object_put(json); + FINALIZE; + } + if (json_object_get_type(parent) != json_type_object) { + DBGPRINTF("msgAddJSON: not a container in json path," + "name is '%s'\n", name); + json_object_put(json); + ABORT_FINALIZE(RS_RET_INVLD_SETOP); + } + if(jsonVarExtract(parent, (char*)leaf, &leafnode) == FALSE) + leafnode = NULL; + /* json-c code indicates we can simply replace a + * json type. Unfortunaltely, this is not documented + * as part of the interface spec. We still use it, + * because it speeds up processing. If it does not work + * at some point, use + * json_object_object_del(parent, (char*)leaf); + * before adding. rgerhards, 2012-09-17 + */ + if (force_reset || (leafnode == NULL)) { + json_object_object_add(parent, (char*)leaf, json); + } else { + if(json_object_get_type(json) == json_type_object) { + CHKiRet(jsonMerge(*jroot, json)); + } else { + /* TODO: improve the code below, however, the current + * state is not really bad */ + if(json_object_get_type(leafnode) == json_type_object) { + DBGPRINTF("msgAddJSON: trying to update a container " + "node with a leaf, name is %s - " + "forbidden", name); + json_object_put(json); + ABORT_FINALIZE(RS_RET_INVLD_SETOP); + } + json_object_object_add(parent, (char*)leaf, json); + } + } + } + +finalize_it: + if(mut != NULL) + pthread_mutex_unlock(mut); + RETiRet; +} + + +rsRetVal +msgDelJSON(smsg_t * const pM, uchar *name) +{ + struct json_object **jroot; + struct json_object *parent, *leafnode; + uchar *leaf; + pthread_mutex_t *mut = NULL; + DEFiRet; + + CHKiRet(getJSONRootAndMutexByVarChar(pM, name[0], &jroot, &mut)); + pthread_mutex_lock(mut); + + if(*jroot == NULL) { + DBGPRINTF("msgDelJSONVar; jroot empty in unset for property %s\n", + name); + FINALIZE; + } + + if(name[1] == '\0') { + /* full tree! Strange, but I think we should permit this. After all, + * we trust rsyslog.conf to be written by the admin. + */ + DBGPRINTF("unsetting JSON root object\n"); + json_object_put(*jroot); + *jroot = NULL; + } else { + leaf = jsonPathGetLeaf(name, ustrlen(name)); + CHKiRet(jsonPathFindParent(*jroot, name, leaf, &parent, 0)); + if(jsonVarExtract(parent, (char*)leaf, &leafnode) == FALSE) + leafnode = NULL; + if(leafnode == NULL) { + DBGPRINTF("unset JSON: could not find '%s'\n", name); + ABORT_FINALIZE(RS_RET_JNAME_NOTFOUND); + } else { + DBGPRINTF("deleting JSON value path '%s', " + "leaf '%s', type %d\n", + name, leaf, json_object_get_type(leafnode)); + json_object_object_del(parent, (char*)leaf); + } + } + +finalize_it: + if(mut != NULL) + pthread_mutex_unlock(mut); + RETiRet; +} + +/* add Metadata to the message. This is stored in a special JSON + * container. Note that only string types are currently supported, + * what should pose absolutely no problem with the string-ish nature + * of rsyslog metadata. + * added 2015-01-09 rgerhards + */ +rsRetVal +msgAddMetadata(smsg_t *const __restrict__ pMsg, + uchar *const __restrict__ metaname, + uchar *const __restrict__ metaval) +{ + DEFiRet; + struct json_object *const json = json_object_new_object(); + CHKmalloc(json); + struct json_object *const jval = json_object_new_string((char*)metaval); + if(jval == NULL) { + json_object_put(json); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + json_object_object_add(json, (const char *const)metaname, jval); + iRet = msgAddJSON(pMsg, (uchar*)"!metadata", json, 0, 0); +finalize_it: + RETiRet; +} + +rsRetVal +msgAddMultiMetadata(smsg_t *const __restrict__ pMsg, + const uchar ** __restrict__ metaname, + const uchar ** __restrict__ metaval, + const int count) +{ + DEFiRet; + int i = 0 ; + struct json_object *const json = json_object_new_object(); + CHKmalloc(json); + for ( i = 0 ; i < count ; i++ ) { + struct json_object *const jval = json_object_new_string((char*)metaval[i]); + if(jval == NULL) { + json_object_put(json); + ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); + } + json_object_object_add(json, (const char *const)metaname[i], jval); + } + iRet = msgAddJSON(pMsg, (uchar*)"!metadata", json, 0, 0); +finalize_it: + RETiRet; +} + +struct json_object * +jsonDeepCopy(struct json_object *src) +{ + struct json_object *dst = NULL, *json; + int arrayLen, i; + + if(src == NULL) goto done; + + switch(json_object_get_type(src)) { + case json_type_boolean: + dst = json_object_new_boolean(json_object_get_boolean(src)); + break; + case json_type_double: + dst = json_object_new_double(json_object_get_double(src)); + break; + case json_type_int: + dst = json_object_new_int64(json_object_get_int64(src)); + break; + case json_type_string: + dst = json_object_new_string(json_object_get_string(src)); + break; + case json_type_object: + dst = json_object_new_object(); + struct json_object_iterator it = json_object_iter_begin(src); + struct json_object_iterator itEnd = json_object_iter_end(src); + while (!json_object_iter_equal(&it, &itEnd)) { + json = jsonDeepCopy(json_object_iter_peek_value(&it)); + json_object_object_add(dst, json_object_iter_peek_name(&it), json); + json_object_iter_next(&it); + } + break; + case json_type_array: + arrayLen = json_object_array_length(src); + dst = json_object_new_array(); + for(i = 0 ; i < arrayLen ; ++i) { + json = json_object_array_get_idx(src, i); + json = jsonDeepCopy(json); + json_object_array_add(dst, json); + } + break; + case json_type_null: + default:DBGPRINTF("jsonDeepCopy(): error unknown type %d\n", + json_object_get_type(src)); + dst = NULL; + break; + } +done: return dst; +} + + +rsRetVal +msgSetJSONFromVar(smsg_t * const pMsg, uchar *varname, struct svar *v, int force_reset) +{ + struct json_object *json = NULL; + char *cstr; + DEFiRet; + switch(v->datatype) { + case 'S':/* string */ + cstr = es_str2cstr(v->d.estr, NULL); + json = json_object_new_string(cstr); + free(cstr); + break; + case 'N':/* number (integer) */ + json = json_object_new_int64(v->d.n); + break; + case 'J':/* native JSON */ + json = jsonDeepCopy(v->d.json); + break; + default:DBGPRINTF("msgSetJSONFromVar: unsupported datatype %c\n", + v->datatype); + ABORT_FINALIZE(RS_RET_ERR); + } + + msgAddJSON(pMsg, varname, json, force_reset, 0); +finalize_it: + RETiRet; +} + +rsRetVal +MsgAddToStructuredData(smsg_t * const pMsg, uchar *toadd, rs_size_t len) +{ + uchar *newptr; + rs_size_t newlen; + int empty; + DEFiRet; + empty = pMsg->pszStrucData == NULL || pMsg->pszStrucData[0] == '-'; + newlen = (empty) ? len : pMsg->lenStrucData + len; + CHKmalloc(newptr = (uchar*) realloc(pMsg->pszStrucData, newlen+1)); + if(empty) { + memcpy(newptr, toadd, len); + } else { + memcpy(newptr+pMsg->lenStrucData, toadd, len); + } + pMsg->pszStrucData = newptr; + pMsg->pszStrucData[newlen] = '\0'; + pMsg->lenStrucData = newlen; +finalize_it: + RETiRet; +} + + +/* Fill a message propert description. Space must already be alloced + * by the caller. This is for efficiency, as we expect this to happen + * as part of a larger structure alloc. + * Note that CEE/LOCAL_VAR properties can come in either as + * "$!xx"/"$.xx" or "!xx"/".xx" - we will unify them here. + */ +rsRetVal +msgPropDescrFill(msgPropDescr_t *pProp, uchar *name, int nameLen) +{ + propid_t id; + int offs; + DEFiRet; + if(propNameToID(name, &id) != RS_RET_OK) { + parser_errmsg("invalid property '%s'", name); + /* now try to find some common error causes */ + if(!strcasecmp((char*)name, "myhostname")) + parser_errmsg("did you mean '$myhostname' instead of '%s'? " + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "bom")) + parser_errmsg("did you mean '$bom' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "now")) + parser_errmsg("did you mean '$now' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "year")) + parser_errmsg("did you mean '$year' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "month")) + parser_errmsg("did you mean '$month' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "day")) + parser_errmsg("did you mean '$day' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "hour")) + parser_errmsg("did you mean '$hour' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "hhour")) + parser_errmsg("did you mean '$hhour' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "qhour")) + parser_errmsg("did you mean '$qhour' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "minute")) + parser_errmsg("did you mean '$minute' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "now-utc")) + parser_errmsg("did you mean '$now-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "year-utc")) + parser_errmsg("did you mean '$year-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "month-utc")) + parser_errmsg("did you mean '$month-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "day-utc")) + parser_errmsg("did you mean '$day-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "hour-utc")) + parser_errmsg("did you mean '$hour-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "hhour-utc")) + parser_errmsg("did you mean '$hhour-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "qhour-utc")) + parser_errmsg("did you mean '$qhour-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + else if(!strcasecmp((char*)name, "minute-utc")) + parser_errmsg("did you mean '$minute-utc' instead of '%s'?" + "See also: https://www.rsyslog.com/rsyslog-info-1/", name); + ABORT_FINALIZE(RS_RET_INVLD_PROP); + } + if(id == PROP_CEE || id == PROP_LOCAL_VAR || id == PROP_GLOBAL_VAR) { + /* in these cases, we need the field name for later processing */ + /* normalize name: remove $ if present */ + offs = (name[0] == '$') ? 1 : 0; + pProp->name = ustrdup(name + offs); + pProp->nameLen = nameLen - offs; + /* we patch the root name, so that support functions do not need to + * check for different root chars. */ + pProp->name[0] = '!'; + } + pProp->id = id; +finalize_it: + RETiRet; +} + +void +msgPropDescrDestruct(msgPropDescr_t *pProp) +{ + if(pProp != NULL) { + if(pProp->id == PROP_CEE || + pProp->id == PROP_LOCAL_VAR || + pProp->id == PROP_GLOBAL_VAR) + free(pProp->name); + } +} + + +/* dummy */ +static rsRetVal msgQueryInterface(interface_t __attribute__((unused)) *i) { return RS_RET_NOT_IMPLEMENTED; } + +/* Initialize the message class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-01-04 + */ +BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE) + pthread_mutex_init(&glblVars_lock, NULL); + + /* request objects we use */ + CHKiRet(objUse(datetime, CORE_COMPONENT)); + CHKiRet(objUse(glbl, CORE_COMPONENT)); + CHKiRet(objUse(prop, CORE_COMPONENT)); + CHKiRet(objUse(var, CORE_COMPONENT)); + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize); + /* some more inits */ +# ifdef HAVE_MALLOC_TRIM + INIT_ATOMIC_HELPER_MUT(mutTrimCtr); +# endif +ENDObjClassInit(msg) -- cgit v1.2.3