/* * Copyright (c) 2017 Balabit * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * As an additional exemption you are allowed to compile & link against the * OpenSSL libraries as published by the OpenSSL project. See the file * COPYING for details. */ #include "snmptrapd-header-parser.h" #include "str-format.h" #include "timeutils/cache.h" #include "timeutils/conv.h" #include #include typedef struct { SnmpTrapdNVContext *nv_context; const gchar **input; gsize *input_len; } SnmpTrapdHeaderParser; typedef gboolean (*SnmpTrapdHeaderParserStep)(SnmpTrapdHeaderParser *self); static inline void _skip_spaces(SnmpTrapdHeaderParser *self) { const gchar *current_char = *self->input; while (*self->input_len > 0 && *current_char == ' ') { ++current_char; --(*self->input_len); } *self->input = current_char; } static gboolean _run_header_parser(SnmpTrapdHeaderParser *self, SnmpTrapdHeaderParserStep *parser_steps, gsize parser_steps_size) { SnmpTrapdHeaderParserStep parser_step; for (gsize step_index = 0; step_index < parser_steps_size; ++step_index) { _skip_spaces(self); parser_step = parser_steps[step_index]; if (!parser_step(self)) return FALSE; } return TRUE; } static inline gboolean _expect_char(SnmpTrapdHeaderParser *self, gchar c) { return scan_expect_char(self->input, (gint *) self->input_len, c); } static inline gboolean _expect_newline(SnmpTrapdHeaderParser *self) { return _expect_char(self, '\n'); } static inline gboolean _expect_newline_or_eom(SnmpTrapdHeaderParser *self) { return _expect_newline(self) || *self->input_len == 0; } static inline gboolean _expect_colon(SnmpTrapdHeaderParser *self) { return _expect_char(self, ':'); } static inline gboolean _expect_tab(SnmpTrapdHeaderParser *self) { return _expect_char(self, '\t'); } static inline void _scroll_to_eom(SnmpTrapdHeaderParser *self) { while (*self->input_len > 0 || **self->input) { ++(*self->input); --(*self->input_len); } } static gboolean _parse_v1_uptime(SnmpTrapdHeaderParser *self) { if (!scan_expect_str(self->input, (gint *) self->input_len, "Uptime:")) return FALSE; _skip_spaces(self); const gchar *uptime_start = *self->input; const gchar *uptime_end = strchr(uptime_start, '\n'); if (!uptime_end) { _scroll_to_eom(self); snmptrapd_nv_context_add_name_value(self->nv_context, "uptime", uptime_start, *self->input - uptime_start); return TRUE; } snmptrapd_nv_context_add_name_value(self->nv_context, "uptime", uptime_start, uptime_end - uptime_start); *self->input_len -= uptime_end - *self->input; *self->input = uptime_end; return TRUE; } static gboolean _parse_v1_trap_type_and_subtype(SnmpTrapdHeaderParser *self) { const gchar *type_start = *self->input; const gchar *type_end = strpbrk(type_start, "(\n"); gboolean type_exists = type_end && *type_end == '('; if (!type_exists) return FALSE; const gchar *subtype_start = type_end + 1; if (*(type_end - 1) == ' ') --type_end; snmptrapd_nv_context_add_name_value(self->nv_context, "type", type_start, type_end - type_start); const gchar *subtype_end = strpbrk(subtype_start, ")\n"); gboolean subtype_exists = subtype_end && *subtype_end == ')'; if (!subtype_exists) return FALSE; snmptrapd_nv_context_add_name_value(self->nv_context, "subtype", subtype_start, subtype_end - subtype_start); *self->input_len -= (subtype_end + 1) - *self->input; *self->input = subtype_end + 1; return TRUE; } static gboolean _parse_v1_enterprise_oid(SnmpTrapdHeaderParser *self) { const gchar *enterprise_string_start = *self->input; gsize input_left = *self->input_len; while (*self->input_len > 0 && !g_ascii_isspace(**self->input)) { ++(*self->input); --(*self->input_len); } gsize enterprise_string_length = input_left - *self->input_len; /* enterprise_string is optional */ if (enterprise_string_length == 0) return TRUE; snmptrapd_nv_context_add_name_value(self->nv_context, "enterprise_oid", enterprise_string_start, enterprise_string_length); return TRUE; } static gboolean _parse_transport_info(SnmpTrapdHeaderParser *self) { if(!scan_expect_char(self->input, (gint *) self->input_len, '[')) return FALSE; _skip_spaces(self); const gchar *transport_info_start = *self->input; const gchar *transport_info_end = strchr(transport_info_start, '\n'); if (!transport_info_end) return FALSE; while(*transport_info_end != ']') { --transport_info_end; if(transport_info_end == transport_info_start) return FALSE; } gsize transport_info_len = transport_info_end - transport_info_start; snmptrapd_nv_context_add_name_value(self->nv_context, "transport_info", transport_info_start, transport_info_len); *self->input_len -= (transport_info_end + 1) - *self->input; *self->input = transport_info_end + 1; return TRUE; } static gboolean _parse_hostname(SnmpTrapdHeaderParser *self) { const gchar *hostname_start = *self->input; gsize input_left = *self->input_len; while (*self->input_len > 0 && !g_ascii_isspace(**self->input)) { ++(*self->input); --(*self->input_len); } gsize hostname_length = input_left - *self->input_len; if (hostname_length == 0) return FALSE; snmptrapd_nv_context_add_name_value(self->nv_context, "hostname", hostname_start, hostname_length); return TRUE; } static gboolean scan_snmptrapd_timestamp(const gchar **buf, gint *left, WallClockTime *wct) { /* YYYY-MM-DD HH:MM:SS */ if (!scan_positive_int(buf, left, 4, &wct->wct_year) || !scan_expect_char(buf, left, '-') || !scan_positive_int(buf, left, 2, &wct->wct_mon) || !scan_expect_char(buf, left, '-') || !scan_positive_int(buf, left, 2, &wct->wct_mday) || !scan_expect_char(buf, left, ' ') || !scan_positive_int(buf, left, 2, &wct->wct_hour) || !scan_expect_char(buf, left, ':') || !scan_positive_int(buf, left, 2, &wct->wct_min) || !scan_expect_char(buf, left, ':') || !scan_positive_int(buf, left, 2, &wct->wct_sec)) return FALSE; wct->wct_year -= 1900; wct->wct_mon -= 1; return TRUE; } static gboolean _parse_timestamp(SnmpTrapdHeaderParser *self) { WallClockTime wct = WALL_CLOCK_TIME_INIT; if (!scan_snmptrapd_timestamp(self->input, (gint *)self->input_len, &wct)) return FALSE; convert_and_normalize_wall_clock_time_to_unix_time(&wct, &self->nv_context->msg->timestamps[LM_TS_STAMP]); return TRUE; } static gboolean _try_parse_v1_info(SnmpTrapdHeaderParser *self) { /* detect v1 format */ const gchar *new_line = strchr(*self->input, '\n'); if (new_line && new_line[1] != '\t') return TRUE; SnmpTrapdHeaderParserStep v1_info_parser_steps[] = { _parse_v1_enterprise_oid, _expect_newline, _expect_tab, _parse_v1_trap_type_and_subtype, _parse_v1_uptime }; return _run_header_parser(self, v1_info_parser_steps, sizeof(v1_info_parser_steps) / sizeof(SnmpTrapdHeaderParserStep)); } gboolean snmptrapd_header_parser_parse(SnmpTrapdNVContext *nv_context, const gchar **input, gsize *input_len) { /* * DATE HOST [TRANSPORT_INFO]: V1_ENTERPRISE_OID * V1_TRAP_TYPE (V1_TRAP_SUBTYPE) "Uptime:" UPTIME */ SnmpTrapdHeaderParser self = { .nv_context = nv_context, .input = input, .input_len = input_len }; SnmpTrapdHeaderParserStep parser_steps[] = { _parse_timestamp, _parse_hostname, _parse_transport_info, _expect_colon, _try_parse_v1_info, _expect_newline_or_eom }; return _run_header_parser(&self, parser_steps, sizeof(parser_steps) / sizeof(SnmpTrapdHeaderParserStep)); }