diff --git a/config b/config index 9bcdb41..236e8e5 100644 --- a/config +++ b/config @@ -32,5 +32,8 @@ source "scripts/paths" # Project-specific variables below. +[ -z "${LIBISO8601_CFLAGS}" ] && LIBISO8601_CFLAGS="$(libiso8601-config --cflags)" +[ -z "${LIBISO8601_LIBS}" ] && LIBISO8601_LIBS="$(libiso8601-config --libs)" + [ -z "${CC}" ] && CC="gcc" [ -z "${CFLAGS}" ] && CFLAGS="-g -O2 -W -Wall" diff --git a/src/docs/MainPage.dox b/src/docs/MainPage.dox index 9f68fcd..a3cf0c7 100644 --- a/src/docs/MainPage.dox +++ b/src/docs/MainPage.dox @@ -5,7 +5,25 @@ * http://www.gnu.org/copyleft/gpl.html for details. */ -/*! \mainpage +/*! \mainpage Syslog message parsing library + +This library contains a set of routines helpful in parsing syslog messages and +is intended for any consumer of syslog messages (e.g. a syslog relay or a log +analyser, etc.). + +\section standards Standards + +The parser is designed around RFC +3164 messages, which it will parse perfectly. However, very few actual +syslog implementations seem to match this exactly, so the parsing routine is +more flexible; it can cope with various different date/time formats etc. In +addition, it will also recover a process ID in square brackets after the +application tag if one is present. + +The parser is not (yet) RFC +5424 compliant, although it will be able to read the priority, timestamp +and tag and PROCID fields correctly. RFC 5424 parsing is a future goal of the +library; it has not been implemented due to time constraints. */ diff --git a/src/libsyslogp/000_TopHeader.h b/src/libsyslogp/000_TopHeader.h index a8ec3f2..7de79c5 100644 --- a/src/libsyslogp/000_TopHeader.h +++ b/src/libsyslogp/000_TopHeader.h @@ -9,6 +9,7 @@ #define HEADER_libsyslogp /* standard includes, or includes needed for type declarations */ +#include /* options for text editors kate: replace-trailing-space-save true; space-indent true; tab-width 4; diff --git a/src/libsyslogp/000_TopSource.c b/src/libsyslogp/000_TopSource.c index 149cf3b..5242e3a 100644 --- a/src/libsyslogp/000_TopSource.c +++ b/src/libsyslogp/000_TopSource.c @@ -8,6 +8,10 @@ #include "syslogp.h" /* Below are all the includes used throughout the library. */ +#include +#include +#include +#include /* options for text editors kate: replace-trailing-space-save true; space-indent true; tab-width 4; diff --git a/src/libsyslogp/500_syslogp.c b/src/libsyslogp/500_syslogp.c new file mode 100644 index 0000000..aed6174 --- /dev/null +++ b/src/libsyslogp/500_syslogp.c @@ -0,0 +1,465 @@ +/* libsyslogp/src/libsyslogp/500_syslogp.c + * + * (c)2009, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + + + +static char* +syslog_parse_pri(struct syslog_msg* msg, char* str) +{ + char* rd; + unsigned int pri = 0; + + rd = str; + if(*rd++ != '<') goto error_out; + + if(*rd == '0') { + if(rd[1] != '>') goto error_out; + } else { + do { + if(*rd < '0' || *rd > '9') goto error_out; + pri *= 10; + pri += *rd - '0'; + ++rd; + }while(*rd != '>'); + if(rd - str > 4) goto error_out; + } + + switch(pri & 7) { + case 0: msg->level = LOG_EMERG; break; + case 1: msg->level = LOG_ALERT; break; + case 2: msg->level = LOG_CRIT; break; + case 3: msg->level = LOG_ERR; break; + case 4: msg->level = LOG_WARNING; break; + case 5: msg->level = LOG_NOTICE; break; + case 6: msg->level = LOG_INFO; break; + case 7: msg->level = LOG_DEBUG; break; + } + + switch(pri >> 3) { + case 0: msg->facility = LOG_KERN; break; + case 1: msg->facility = LOG_USER; break; + case 2: msg->facility = LOG_MAIL; break; + case 3: msg->facility = LOG_DAEMON; break; + case 4: msg->facility = LOG_AUTHPRIV; break; + case 5: msg->facility = LOG_SYSLOG; break; + case 6: msg->facility = LOG_LPR; break; + case 7: msg->facility = LOG_NEWS; break; + case 8: msg->facility = LOG_UUCP; break; + case 9: msg->facility = LOG_CRON; break; + case 10: msg->facility = LOG_AUTHPRIV; break; + case 11: msg->facility = LOG_FTP; break; + case 12: msg->facility = LOG_CRON; break; + case 13: msg->facility = LOG_SYSLOG; break; + case 14: msg->facility = LOG_SYSLOG; break; + case 15: msg->facility = LOG_CRON; break; + case 16: msg->facility = LOG_LOCAL0; break; + case 17: msg->facility = LOG_LOCAL1; break; + case 18: msg->facility = LOG_LOCAL2; break; + case 19: msg->facility = LOG_LOCAL3; break; + case 20: msg->facility = LOG_LOCAL4; break; + case 21: msg->facility = LOG_LOCAL5; break; + case 22: msg->facility = LOG_LOCAL6; break; + case 23: msg->facility = LOG_LOCAL7; break; + default: goto error_out; + } + + return ++rd; + + error_out: + msg->level = LOG_NOTICE; + msg->facility = LOG_USER; + return str; +} + + + +static int +syslog_parse_month(const char* str, int* month) +{ + if(!strncmp(str, "Jan", 3)) *month = 1; + else if(!strncmp(str, "Feb", 3)) *month = 2; + else if(!strncmp(str, "Mar", 3)) *month = 3; + else if(!strncmp(str, "Apr", 3)) *month = 4; + else if(!strncmp(str, "May", 3)) *month = 5; + else if(!strncmp(str, "Jun", 3)) *month = 6; + else if(!strncmp(str, "Jul", 3)) *month = 7; + else if(!strncmp(str, "Aug", 3)) *month = 8; + else if(!strncmp(str, "Sep", 3)) *month = 9; + else if(!strncmp(str, "Oct", 3)) *month = 10; + else if(!strncmp(str, "Nov", 3)) *month = 11; + else if(!strncmp(str, "Dec", 3)) *month = 12; + else return -1; + + return 0; +} + + + +static int +syslog_parse_implied_year = 0; + + + +void +syslog_parse_set_implied_year(int year) +{ + syslog_parse_implied_year = year; +} + + + +static char* +syslog_parse_date(struct syslog_msg* msg, char* str) +{ + int seen_t, len, year, month, day, hour, min, sec; + char* p; + time_t t; + struct tm* tm; + + len = strlen(str); + + /* skip leading whitespace */ + for(; *str == ' '; ++str) { } + + /* check for an ISO8601 date and time (don't just try parsing the string as + * something starting e.g. "2009 " would parse OK, but be wrong) */ + seen_t = 0; + for(p = str; *p != ' '; ++p) { + switch(*p) { + case 'T': + if(seen_t) goto not_iso; + seen_t = 1; + break; + + case '0' ... '9': + case '-': + case ':': + case '.': + case 'Z': + case '+': + break; + + default: + goto not_iso; + } + } + if(!seen_t) goto not_iso; + + *p = 0; + if(iso8601_parse(str, &msg->timestamp, 0, 0)) { + *p = ' '; + goto not_iso; + } + return p + 1; + + /* fall back to older BSD-style or similar timestamps */ + not_iso: + if(len >= 21 && str[3] == ' ' && str[6] == ' ' && str[11] == ' ' && + str[14] == ':' && str[17] == ':') + { + /* PIX timestamp, expected format: MMM DD YYYY HH:MM:SS: */ + + if(syslog_parse_month(str, &month)) goto not_pix; + + day = strtol(str + 4, &p, 10); + if(*p != ' ') goto not_pix; + + year = strtol(str + 7, &p, 10); + if(*p != ' ') goto not_pix; + + hour = strtol(str + 12, &p, 10); + if(*p != ':') goto not_pix; + + min = strtol(str + 15, &p, 10); + if(*p != ':') goto not_pix; + + sec = strtol(str + 18, &p, 10); + if(*p != ' ' && *p != ':') goto not_pix; + + if(iso8601_from_cal(&msg->timestamp, year, month, day) || + iso8601_from_clocktime(&msg->timestamp, hour, min, sec)) + { + goto not_pix; + } + + return str + ((str[20] == ' ') ? 21 : 20); + } + + not_pix: + if(len >= 21 && str[3] == ' ' && str[6] == ' ' && str[9] == ':' && + str[12] == ':' && str[15] == ' ' && str[20] == ' ') + { + /* Linksys timestamp, expected format: MMM DD HH:MM:SS YYYY */ + + if(syslog_parse_month(str, &month)) goto not_linksys; + + day = strtol(str + 4, &p, 10); + if(*p != ' ') goto not_linksys; + + hour = strtol(str + 7, &p, 10); + if(*p != ':') goto not_linksys; + + min = strtol(str + 10, &p, 10); + if(*p != ':') goto not_linksys; + + sec = strtol(str + 13, &p, 10); + if(*p != ' ') goto not_linksys; + + year = strtol(str + 16, &p, 10); + if(*p != ' ' || year < 1970 || year > 2100) goto not_linksys; + + if(iso8601_from_cal(&msg->timestamp, year, month, day) || + iso8601_from_clocktime(&msg->timestamp, hour, min, sec)) + { + goto not_linksys; + } + + return str + 21; + } + + not_linksys: + if(len >= 15 && str[3] == ' ' && str[6] == ' ' && str[9] == ':' && + str[12] == ':') + { + /* RFC 3164 timestamp, expected format: MMM DD HH:MM:SS ... */ + + if(syslog_parse_month(str, &month)) goto not_bsd; + + day = strtol(str + 4, &p, 10); + if(*p != ' ') goto not_bsd; + + hour = strtol(str + 7, &p, 10); + if(*p != ':') goto not_bsd; + + min = strtol(str + 10, &p, 10); + if(*p != ':') goto not_bsd; + + sec = strtol(str + 13, &p, 10); + if(*p != ' ' && *p != ':' && *p != '.') goto not_bsd; + + /* get year */ + if(syslog_parse_implied_year) { + year = syslog_parse_implied_year; + } else { + time(&t); + tm = gmtime(&t); + year = tm->tm_year + 1900; + } + + if(iso8601_from_cal(&msg->timestamp, year, month, day) || + iso8601_from_clocktime(&msg->timestamp, hour, min, sec)) + { + goto not_bsd; + } + + /* check for fractional seconds */ + p = str + 15; + if(*p != '.') return str + 16; /* skip trailing ':' or space */ + + sec = 0; + min = 0; + ++p; + do { + if(*p < '0' || *p > '9') return str + 15; + ++min; + sec *= 10; + sec += *p - '0'; + ++p; + }while(*p && *p != ' '); + + if(min > 9) return str + 15; + for(msg->timestamp.nsec = sec; min < 9; ++min) { + msg->timestamp.nsec *= 10; + } + + return p; + } + + not_bsd: + return str; +} + + + +static char* +syslog_parse_spacefield(char** ptr, char* str) +{ + /* skip whitespace */ + for(; *str == ' '; ++str) { } + if(!*str) return str; + + /* store pointer to string */ + *ptr = str; + + /* move to end of string */ + for(; *str != ' '; ++str) { + if(!*str) return str; + } + + /* null terminate and move to next field */ + *str++ = 0; + return str; +} + + + +static char* +syslog_parse_tag_id(struct syslog_msg* msg, char* str) +{ + char* p; + + /* skip whitespace */ + for(; *str == ' '; ++str) { } + if(!*str) return str; + + /* grab tag */ + msg->tag = str; + + /* search for tag delimiter */ + for(;; ++str) { + switch(*str) { + case ':': + case '.': + case '[': + case '{': + case '<': + goto found_id; + + case ' ': + case 0: + goto after_id; + } + } + + /* try to extract an ID */ + found_id: + p = 0; + switch(*str) { + case ':': + if(str[1] == ' ') goto after_id; + /* fall through */ + case '.': + *str++ = 0; + msg->id = str; + for(; *str && *str != ':' && *str != ' '; ++str) { } + goto after_id; + + case '[': + p = strchr(str, ']'); + break; + + case '{': + p = strchr(str, '}'); + break; + + case '<': + p = strchr(str, '>'); + break; + } + if(!p) return str; + + *str++ = 0; + msg->id = str; + *p = 0; + str = p + 1; + if(*str == ':') ++str; + if(*str == ' ') ++str; + return str; + + after_id: + if(!*str) return str; + *str++ = 0; + if(*str == ' ') ++str; + return str; +} + + + +void +syslog_parse(struct syslog_msg* msg, char* str) +{ + int version = 0, len; + + memset(msg, 0, sizeof(struct syslog_msg)); + str = syslog_parse_pri(msg, str); + + /* check for an RFC5424 version 1 message */ + if(!strncmp(str, "1 ", 2)) { + str += 2; + version = 1; + } + + str = syslog_parse_date(msg, str); + str = syslog_parse_spacefield(&msg->hostname, str); + + if(version == 1) { + str = syslog_parse_spacefield(&msg->tag, str); + str = syslog_parse_spacefield(&msg->id, str); + } else { + str = syslog_parse_tag_id(msg, str); + } + + /* get message part, removing any trailing \n */ + len = strlen(str); + if(len && str[len - 1] == '\n') str[len - 1] = 0; + msg->msg = str; +} + + + +const char* +syslog_level_name(int level) +{ + switch(level) { + case LOG_EMERG: return "emergency"; + case LOG_ALERT: return "alert"; + case LOG_CRIT: return "critical"; + case LOG_ERR: return "error"; + case LOG_WARNING: return "warning"; + case LOG_NOTICE: return "notice"; + case LOG_INFO: return "info"; + case LOG_DEBUG: return "debug"; + } + return 0; +} + + + +const char* +syslog_facility_name(int facility) +{ + switch(facility) { + case LOG_AUTH: + case LOG_AUTHPRIV: return "auth"; + case LOG_CRON: return "cron"; + case LOG_DAEMON: return "daemon"; + case LOG_FTP: return "ftp"; + case LOG_KERN: return "kernel"; + case LOG_LOCAL0: return "local0"; + case LOG_LOCAL1: return "local1"; + case LOG_LOCAL2: return "local2"; + case LOG_LOCAL3: return "local3"; + case LOG_LOCAL4: return "local4"; + case LOG_LOCAL5: return "local5"; + case LOG_LOCAL6: return "local6"; + case LOG_LOCAL7: return "local7"; + case LOG_LPR: return "printer"; + case LOG_MAIL: return "mail"; + case LOG_NEWS: return "news"; + case LOG_SYSLOG: return "syslog"; + case LOG_USER: return "user"; + case LOG_UUCP: return "uucp"; + } + return 0; +} + + + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/ diff --git a/src/libsyslogp/500_syslogp.h b/src/libsyslogp/500_syslogp.h new file mode 100644 index 0000000..f1dbc04 --- /dev/null +++ b/src/libsyslogp/500_syslogp.h @@ -0,0 +1,171 @@ +/* libsyslogp/src/libsyslogp/500_syslogp.h + * + * (c)2009, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + + + +/*! \defgroup syslogp syslog message parser + +This set of routines allows parsing of a syslog message and processing of the +result. \ref syslog_parse() is called on the raw message data and the component +parts of the message are then stored in a struct \ref syslog_msg. + +Note: BSD-syslog style messages don't generally contain a year in the timestamp +field. This means that \ref syslog_parse() calls \c time(3) to find the current +year whenever it parses such a message. This is expensive and, potentially, +incaccurate. You can set the year implied in such messages with +\ref syslog_parse_set_implied_year(). + +*/ +/*!@{*/ + + + +/*! \brief Parsed syslog message. + +The component parts of an RFC3164 syslog message. The MSGID and SD (structured +data) parts of an RFC5424 syslog message will not be parsed and will be +considered part of the \a msg text. + +*/ +struct syslog_msg { + /*! \brief Message severity level. + + This is one of the \c LOG_INFO/LOG_WARNING etc. types from \c syslog(3). It + can be turned into a friendly name via \ref syslog_level_name(). It will be + set to \c LOG_NOTICE if the priority field could not be recovered from the + original message. + + */ + int level; + + /*! \brief Message facility. + + This is one of the \c LOG_DAEMON/LOG_LOCAL0 etc. types from \c syslog(3). It + can be turned into a friendly name via \ref syslog_facility_name(). It will + be set to \c LOG_USER if the priority field could not be recovered from the + original message. + + */ + int facility; + + /*! \brief Timestamp. + + The timestamp of the message. It will be all 0 if the timestamp could not + be recovered from the original message. + + */ + struct iso8601_date timestamp; + + /*! \brief Hostname. + + The hostname as specified by the producing application. If this could not be + recovered, it will be set to 0. + + */ + char* hostname; + + /*! \brief Producing application name (tag). + + The name (or tag) of the application which produced the original message. If + this could not be recovered, it will be set to 0. + + */ + char* tag; + + /*! \brief Producing application instance ID. + + Many (but not all) RFC3164 messages place an application instance ID in + square brackets immediately after the tag. RFC5424 has a field specifically + for this purpose. If discovered, this will point to the data in that field. + If not, it will be 0. + + */ + char* id; + + /*! \brief Message text. + + This points to the remaining message text once the header and tag have been + stripped off. It may be 0 or a pointer to an empty string if the message was + truly empty. + + */ + char* msg; +}; + + + +/*! \brief Parse a syslog message. + +\param[out] msg Structure into which parsed message data is written. +\param str Pointer to message string. Null terminated. Will be altered! + +This function parses the syslog message written into \a str, which is a null +terminated C string containing the syslog message. \a str will be altered by +this process (e.g. some characters will be overwritten with ASCII NULs). The +parsed message data is written into \a msg. + +\a msg contains several string pointers (such as \a tag, \a id and \a msg). +After parsing, these will be set to be pointers into \a str. Each component +will be null-terminated (which is why \a str will be altered). + +*/ +void syslog_parse(struct syslog_msg* msg, char* str) +#ifndef DOXYGEN + __attribute__((nonnull)) +#endif +; + + + +/*! \brief Set year implied in BSD-style timestamps. + +\param year Year to assume (or 0 to query the system clock). + +BSD-style timestamps do not contain the current year. libsyslogp will thus query +the system clock when it is trying to parse such a timestamp. This is expensive +and, potentially, inaccurate. This function can be used to set the implied year. + +*/ +void syslog_parse_set_implied_year(int year); + + + +/*! \brief Get friendly name for syslog level. + +\param level \c syslog(3) log level constant. +\returns Pointer to string constant. +\retval 0 if \a level is invalid. + +This function interprets a \c syslog(3) log level constant (such as \c LOG_INFO +or \c LOG_WARNING) and returns a friendly string ("info", "warning"). If the +value of \a level is invalid, it returns a null pointer. + +*/ +const char* syslog_level_name(int level); + + + +/*! \brief Get friendly name for syslog facility. + +\param facility \c syslog(3) log facility constant. +\returns Pointer to string constant. +\retval 0 if \a facility is invalid. + +This function interprets a \c syslog(3) log facility constant (such as +\c LOG_DAEMON or \c LOG_USER) and returns a friendly string ("daemon", "user"). +If the value of \a facility is invalid, it returns a null pointer. + +*/ +const char* syslog_facility_name(int facility); + + + +/*!@}*/ +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4:syntax=c.doxygen +*/ diff --git a/src/libsyslogp/build.lib b/src/libsyslogp/build.lib index c9a7a79..21fbd7f 100644 --- a/src/libsyslogp/build.lib +++ b/src/libsyslogp/build.lib @@ -12,9 +12,9 @@ then source src/libsyslogp/soversion libsyslogp="obj/${libsyslogp_BASE}.so.${SOMAJOR}.${SOMICRO}" - libsyslogp_DEP_CFLAGS="" # @TODO@ cflags - libsyslogp_DEP_LIBS="" # @TODO@ libs - SO_EXTRA="${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} -lc" + libsyslogp_DEP_CFLAGS="${LIBISO8601_CFLAGS}" + libsyslogp_DEP_LIBS="${LIBISO8601_LIBS}" + SO_EXTRA="-D_GNU_SOURCE -std=gnu99 ${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} -lc" echo "Building library ${libsyslogp}..." diff --git a/src/tests/build.tests b/src/tests/build.tests index 246f586..0f39c20 100644 --- a/src/tests/build.tests +++ b/src/tests/build.tests @@ -7,7 +7,7 @@ build_target libsyslogp || return 1 if [ -z ${tests_BUILT} ] then LIBS="${libsyslogp} ${libsyslogp_DEP_CFLAGS} ${libsyslogp_DEP_LIBS} " - EXTRAS="" # @TODO@ libs, cflags + EXTRAS="-D_GNU_SOURCE -std=gnu99" echo "Building test programs..." do_cmd mkdir -p obj/tests || return 1 diff --git a/src/tests/regr.c b/src/tests/regr.c new file mode 100644 index 0000000..0233b5a --- /dev/null +++ b/src/tests/regr.c @@ -0,0 +1,230 @@ +/* libsyslogp/src/tests/regr.c + * + * (c)2009, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + +#include "syslogp.h" + +#include +#include +#include +#include + + + +#define ISO_DAY_20081106 (733717) +#define ISO_SEC_1700 (17 * 3600) + + + +const struct syslog_msg exp_simplecron = { + .level = LOG_NOTICE, + .facility = LOG_USER, + .timestamp = { + .day = ISO_DAY_20081106, + .sec = ISO_SEC_1700, + }, + .hostname = "amethyst", + .tag = "cron", + .id = "31311", + .msg = "(root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)", +}; + + + +const struct syslog_msg exp_rfc5424cron = { + .level = LOG_NOTICE, + .facility = LOG_USER, + .timestamp = { + .day = ISO_DAY_20081106, + .sec = ISO_SEC_1700, + }, + .hostname = "amethyst", + .tag = "cron", + .id = "31311", + .msg = "- (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)", +}; + + + +struct test { + const char* name; + const char* orig; + char* string; + const struct syslog_msg* exp; +}; + + + +struct test +tests[] = { + { + .name = "bsd", + .orig = "<13>Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)", + .exp = &exp_simplecron, + }, + + { + .name = "space_bsd", + .orig = "<13> Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)", + .exp = &exp_simplecron, + }, + + { + .name = "bsd_newline", + .orig = "<13>Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "nopri_bsd_newline", + .orig = "Nov 6 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "bsd_secfrac", + .orig = "<13>Nov 6 17:00:00.000 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "linksys", + .orig = "<13>Nov 6 17:00:00 2008 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "space_linksys", + .orig = "<13> Nov 6 17:00:00 2008 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "pix", + .orig = "<13>Nov 6 2008 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "space_pix", + .orig = "<13> Nov 6 2008 17:00:00 amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "iso", + .orig = "<13>2008-11-06T17:00:00Z amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "_iso", + .orig = "<13> 2008-11-06T17:00:00Z amethyst cron[31311]: (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_simplecron, + }, + + { + .name = "rfc5424", + .orig = "<13>1 2008-11-06T17:00:00Z amethyst cron 31311 - (root) CMD (rm -f /var/spool/cron/lastrun/cron.hourly)\n", + .exp = &exp_rfc5424cron, + }, + + { + .name = 0, + } +}; + + + +int +compare_msg(const struct syslog_msg* a, const struct syslog_msg* b) +{ + if(a->level != b->level) return -1; + if(a->facility != b->facility) return -1; + if(iso8601_cmp(&a->timestamp, &b->timestamp)) return -1; +#define CMP_STR(str) do { \ + if((a->str && !b->str) || (b->str && !a->str)) return -1; \ + if(a->str && strcmp(a->str, b->str)) return -1; \ +}while(0) + CMP_STR(hostname); + CMP_STR(tag); + CMP_STR(id); + CMP_STR(msg); +#undef CMP_STR + return 0; +} + + + +struct iso8601_details +iso_details = { + .date_prec = iso8601_prec_day, + .time_prec = iso8601_prec_secfrac, + .extended = 1, +}; + + + +void +dump_msg(const char* label, const struct syslog_msg* msg) +{ + char isodate[40]; + + printf("======== %s ========\n" + "Level : %d (%s)\n" + "Facility : %d (%s)\n" + "Timestamp: %s\n" + "Hostname : '%s'\n" + "Tag : '%s'\n" + "ID : '%s'\n" + "Message : “%s”\n\n", + label, + msg->level, syslog_level_name(msg->level), + msg->facility, syslog_facility_name(msg->facility), + iso8601_print(isodate, sizeof(isodate), &msg->timestamp, &iso_details), + msg->hostname, msg->tag, msg->id, msg->msg + ); +} + + + +int main(int argc, char* argv[]) +{ + int ret = 0; + struct test* test; + struct syslog_msg msg_result; + + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + fputs("Automated regression test.\n", stdout); + return 0; + } + + syslog_parse_set_implied_year(2008); + + for(test = tests; test->name; ++test) { + /* operate on a copy of the string (constant is read only) */ + test->string = strdup(test->orig); + syslog_parse(&msg_result, test->string); + + /* compare the result, testing for and dumping errors */ + ret = compare_msg(&msg_result, test->exp); + printf("%s: %s\n", test->name, ret ? "FAILED" : "passed"); + + if(ret) { + printf("*** Test failed while parsing:\n“%s”\n\n", test->orig); + dump_msg("Expected result", test->exp); + dump_msg("Actual result", &msg_result); + break; + } + } + + return ret; +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/ diff --git a/src/tests/stdin_parser.c b/src/tests/stdin_parser.c new file mode 100644 index 0000000..08558c8 --- /dev/null +++ b/src/tests/stdin_parser.c @@ -0,0 +1,74 @@ +/* libsyslogp/src/tests/stdin_parser.c + * + * (c)2009, Laurence Withers, . + * Released under the GNU GPLv3. See file COPYING or + * http://www.gnu.org/copyleft/gpl.html for details. +*/ + +#include "syslogp.h" + +#include +#include +#include + + + +struct iso8601_details +iso_details = { + .date_prec = iso8601_prec_day, + .time_prec = iso8601_prec_secfrac, + .extended = 1, +}; + + + +void +test_parse(char* ln) +{ + struct syslog_msg msg; + char isodate[40]; + + syslog_parse(&msg, ln); + + printf("\n" + "Level : %d (%s)\n" + "Facility : %d (%s)\n" + "Timestamp: %s\n" + "Hostname : '%s'\n" + "Tag : '%s'\n" + "ID : '%s'\n" + "Message : “%s”\n", + msg.level, syslog_level_name(msg.level), + msg.facility, syslog_facility_name(msg.facility), + iso8601_print(isodate, sizeof(isodate), &msg.timestamp, &iso_details), + msg.hostname, msg.tag, msg.id, msg.msg + ); +} + + + +int +main(int argc, char* argv[]) +{ + char* ln = 0; + size_t lnsize = 0; + ssize_t rd; + + if(argc == 2 && !strcmp(argv[1], "--print-summary")) { + fputs("Parses syslog lines presented on stdin.\n", stdout); + return 0; + } + + while(!feof(stdin)) { + rd = getline(&ln, &lnsize, stdin); + if(rd == -1) return 0; + test_parse(ln); + } + + return 0; +} + +/* options for text editors +kate: replace-trailing-space-save true; space-indent true; tab-width 4; +vim: expandtab:ts=4:sw=4 +*/