From 11079f41c124d25a3024cdbdd5632162e9d108e1 Mon Sep 17 00:00:00 2001 From: Davidson Francis Date: Sun, 28 Jul 2024 02:44:20 -0300 Subject: [PATCH] Rework string manipulation & minor improvements on webhook events --- Makefile | 2 +- env_events.c | 99 +++++++++------------ events.c | 15 ++-- notifiers.c | 84 +++++++++++++----- str.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++ str.h | 39 ++++++++ 6 files changed, 395 insertions(+), 88 deletions(-) create mode 100644 str.c create mode 100644 str.h diff --git a/Makefile b/Makefile index 145ee68..35f2276 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ CFLAGS += -Wall -Wextra LDLIBS += -pthread -lcurl STRIP = strip VERSION = v0.1 -OBJS = alertik.o events.o env_events.o notifiers.o log.o syslog.o +OBJS = alertik.o events.o env_events.o notifiers.o log.o syslog.o str.o ifeq ($(LOG_FILE),yes) CFLAGS += -DUSE_FILE_AS_LOG diff --git a/env_events.c b/env_events.c index a7b427d..3a7e038 100644 --- a/env_events.c +++ b/env_events.c @@ -15,6 +15,7 @@ #include "env_events.h" #include "alertik.h" #include "notifiers.h" +#include "str.h" /* * Environment events @@ -105,30 +106,10 @@ get_event_idx(int ev_num, char *str, const char *const *str_list, int size) panic("String parameter (%s) invalid for %s\n", env, str); } -/** - * @brief Appends a character to the destination buffer if there is space. - * - * @param dst Pointer to the destination buffer. - * @param dst_end End of the destination buffer. - * @param c Character to append. - * - * @return Returns 1 if the character was appended, 0 otherwise. - */ -static int append_dst(char **dst, const char *dst_end, char c) { - char *d = *dst; - if (d < dst_end) { - *d = c; - *dst = ++d; - return 1; - } - return 0; -} - /** * @brief Handles match replacement in the event mask message. * - * @param dst Pointer to the destination buffer. - * @param dst_e End of the destination buffer. + * @param notif_message Pointer to the append buffer. * @param c_msk Pointer to the current position in the mask message. * @param e_msk End of the mask message. * @param pmatch Array of regex matches. @@ -138,7 +119,7 @@ static int append_dst(char **dst, const char *dst_end, char c) { * @return Returns 1 if the replacement was handled, 0 otherwise. */ static int handle_match_replacement( - char **dst, char *dst_e, + struct str_ab *notif_message, const char **c_msk, const char *e_msk, regmatch_t *pmatch, struct env_event *env, @@ -172,10 +153,8 @@ static int handle_match_replacement( off = pmatch[match].rm_so; len = pmatch[match].rm_eo - off; - for (regoff_t i = 0; i < len; i++) { - if ( !append_dst(dst, dst_e, log_ev->msg[off + i]) ) - return 0; - } + if (ab_append_str(notif_message, log_ev->msg + off, len) < 0) + return 0; return 1; } @@ -192,22 +171,19 @@ static int handle_match_replacement( * * @return Returns the pointer to the end of the masked message. */ -static char* +static int create_masked_message(struct env_event *env, regmatch_t *pmatch, - struct log_event *log_ev, char *buf, size_t buf_size) + struct log_event *log_ev, struct str_ab *notif_message) { - char *dst, *dst_e; - const char *c_msk, *e_msk; + const char *c_msk, *e_msk; c_msk = env->ev_mask_msg; e_msk = c_msk + strlen(c_msk); - dst = buf; - dst_e = dst + buf_size; for (; *c_msk != '\0'; c_msk++) { if (*c_msk != '@') { - if (!append_dst(&dst, dst_e, *c_msk)) + if (ab_append_chr(notif_message, *c_msk) < 0) break; continue; } @@ -220,7 +196,7 @@ create_masked_message(struct env_event *env, regmatch_t *pmatch, * If next is also '@',escape it. */ if (c_msk[1] == '@') { - if (!append_dst(&dst, dst_e, *c_msk)) + if (ab_append_chr(notif_message, *c_msk) < 0) break; else { c_msk++; @@ -240,14 +216,16 @@ create_masked_message(struct env_event *env, regmatch_t *pmatch, else { c_msk++; - if (!handle_match_replacement(&dst, dst_e, &c_msk, e_msk, + if (!handle_match_replacement(notif_message, &c_msk, e_msk, pmatch, env, log_ev)) { break; } } } - return dst; + + /* If we could parse the entire mask. */ + return (*c_msk == '\0'); } /** @@ -262,10 +240,10 @@ static int handle_regex(struct log_event *ev, int idx_env) { char time_str[32] = {0}; regmatch_t pmatch[MAX_MATCHES] = {0}; - char notification_message[2048] = {0}; + struct str_ab notif_message; + int ret; int notif_idx; - char *notif_p; struct env_event *env_ev; env_ev = &env_events[idx_env]; @@ -280,34 +258,35 @@ static int handle_regex(struct log_event *ev, int idx_env) log_msg("> amnt sub expr: %zu\n", env_ev->regex.re_nsub); log_msg("> notifier : %s\n", notifiers_str[notif_idx]); - notif_p = notification_message; + ab_init(¬if_message); /* Check if there are any subexpressions, if not, just format * the message. */ if (env_ev->regex.re_nsub) { - notif_p = create_masked_message(env_ev, pmatch, ev, - notification_message, sizeof(notification_message) - 1); + if (!create_masked_message(env_ev, pmatch, ev, ¬if_message)) { + log_msg("Unable to create masked message!\n"); + return 0; + } - snprintf( - notif_p, - (notification_message + sizeof(notification_message)) - notif_p, - ", at: %s", - get_formatted_time(ev->timestamp, time_str) - ); + if (ab_append_fmt(¬if_message, ", at: %s", + get_formatted_time(ev->timestamp, time_str))) + { + return 0; + } } else { - snprintf( - notification_message, - sizeof(notification_message) - 1, + ret = ab_append_fmt(¬if_message, "%s, at: %s", env_ev->ev_mask_msg, - get_formatted_time(ev->timestamp, time_str) - ); + get_formatted_time(ev->timestamp, time_str)); + + if (ret) + return 0; } - if (notifiers[notif_idx].send_notification(notification_message) < 0) { + if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) { log_msg("unable to send the notification through %s\n", notifiers_str[notif_idx]); } @@ -325,10 +304,11 @@ static int handle_regex(struct log_event *ev, int idx_env) */ static int handle_substr(struct log_event *ev, int idx_env) { + int ret; int notif_idx; char time_str[32] = {0}; struct env_event *env_ev; - char notification_message[2048] = {0}; + struct str_ab notif_message; env_ev = &env_events[idx_env]; notif_idx = env_ev->ev_notifier_idx; @@ -340,16 +320,19 @@ static int handle_substr(struct log_event *ev, int idx_env) log_msg("> type: substr, match: (%s), notifier: %s\n", env_ev->ev_match_str, notifiers_str[notif_idx]); + ab_init(¬if_message); + /* Format the message. */ - snprintf( - notification_message, - sizeof notification_message - 1, + ret = ab_append_fmt(¬if_message, "%s, at: %s", env_ev->ev_mask_msg, get_formatted_time(ev->timestamp, time_str) ); - if (notifiers[notif_idx].send_notification(notification_message) < 0) { + if (ret) + return 0; + + if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) { log_msg("unable to send the notification through %s\n", notifiers_str[notif_idx]); } diff --git a/events.c b/events.c index 580a765..179b82c 100644 --- a/events.c +++ b/events.c @@ -12,6 +12,7 @@ #include "alertik.h" #include "notifiers.h" #include "log.h" +#include "str.h" /* * Static events @@ -252,18 +253,19 @@ static void handle_wifi_login_attempts(struct log_event *ev, int idx_env) char time_str[32] = {0}; char mac_addr[32] = {0}; char wifi_iface[32] = {0}; - char notification_message[2048] = {0}; + struct str_ab notif_message; int notif_idx; + int ret; log_msg("> Login attempt detected!\n"); if (parse_login_attempt_msg(ev->msg, wifi_iface, mac_addr) < 0) return; + ab_init(¬if_message); + /* Send our notification. */ - snprintf( - notification_message, - sizeof notification_message - 1, + ret = ab_append_fmt(¬if_message, "There is someone trying to connect " "to your WiFi: %s, with the mac-address: %s, at:%s", wifi_iface, @@ -271,10 +273,13 @@ static void handle_wifi_login_attempts(struct log_event *ev, int idx_env) get_formatted_time(ev->timestamp, time_str) ); + if (ret) + return; + log_msg("> Retrieved info, MAC: (%s), Interface: (%s)\n", mac_addr, wifi_iface); notif_idx = static_events[idx_env].ev_notifier_idx; - if (notifiers[notif_idx].send_notification(notification_message) < 0) { + if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) { log_msg("unable to send the notification!\n"); return; } diff --git a/notifiers.c b/notifiers.c index cf78f77..7c946f4 100644 --- a/notifiers.c +++ b/notifiers.c @@ -11,6 +11,7 @@ #include "log.h" #include "notifiers.h" #include "alertik.h" +#include "str.h" /* * Notification handling/notifiers @@ -69,7 +70,12 @@ static int setopts_get_curl(CURL *hnd, const char *url) } /** + * @brief Cleanup all resources used by libcurl, including the handler, + * curl_slist and escape'd chars. * + * @param hnd curl handler + * @param escape Escape string if any (leave NULL if there's none). + * @param slist String list if any (leave NULL if there's none). */ static void do_curl_cleanup(CURL *hnd, char *escape, struct curl_slist *slist) { @@ -80,19 +86,33 @@ static void do_curl_cleanup(CURL *hnd, char *escape, struct curl_slist *slist) } /** + * @brief Finally sends a curl request, check its return code + * and then cleanup the resources allocated. * + * @param hnd curl handler + * @param escape Escape string if any (leave NULL if there's none). + * @param slist String list if any (leave NULL if there's none). + * + * @return Returns CURLE_OK if success, !CURLE_OK if error. */ static CURLcode do_curl(CURL *hnd, char *escape, struct curl_slist *slist) { - CURLcode ret_curl = !CURLE_OK; + long response_code = 0; + CURLcode ret_curl = !CURLE_OK; #ifndef DISABLE_NOTIFICATIONS ret_curl = curl_easy_perform(hnd); if (ret_curl != CURLE_OK) { log_msg("> Unable to send request!\n"); goto error; - } else { - log_msg("> Done!\n"); + } + else { + curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &response_code); + log_msg("> Done!\n", response_code); + if (response_code != 200) { + log_msg("(Info: Response code != 200 (%ld), your message might " + "not be correctly sent!)\n", response_code); + } } #endif @@ -143,36 +163,48 @@ static int setopts_post_json_curl(CURL *hnd, const char *url, } /** + * @brief Sends a generic webhook POST request with JSON payload in the + * format {"text": "text here"}. * + * @param url Target webhook URL. + * @param text Text to be sent in the json payload. + * + * @return Returns CURLE_OK if success, 1 if error. */ static int send_generic_webhook(const char *url, const char *text) { CURL *hnd = NULL; struct curl_slist *s = NULL; - char payload_data[4096] = {0}; + struct str_ab payload_data; + const char *t; if (!(hnd = curl_easy_init())) { log_msg("Failed to initialize libcurl!\n"); return 1; } - snprintf( - payload_data, - sizeof payload_data - 1, - "{\"text\":\"%s\"}", - text - ); + ab_init(&payload_data); + ab_append_str(&payload_data, "{\"text\":\"", 9); + /* Append the payload data text while escaping double + * quotes. + */ + for (t = text; *t != '\0'; t++) { + if (*t != '"') { + if (ab_append_chr(&payload_data, *t) < 0) + return 1; + } + else { + if (ab_append_str(&payload_data, "\\\"", 2) < 0) + return 1; + } + } - #if 0 - TODO: - - escape "" in the payload, otherwise it will silently - escape from the {"text": } - - think about the return code from the request and - emmit some message if not 200 - #endif + /* End the string. */ + if (ab_append_str(&payload_data, "\"}", 2) < 0) + return 1; - if (setopts_post_json_curl(hnd, url, payload_data, &s)) + if (setopts_post_json_curl(hnd, url, payload_data.buff, &s)) return 1; log_msg("> Sending notification!\n"); @@ -209,9 +241,10 @@ void setup_telegram(void) static int send_telegram_notification(const char *msg) { - char full_request_url[4096] = {0}; + struct str_ab full_request_url; char *escaped_msg = NULL; CURL *hnd = NULL; + int ret; if (!(hnd = curl_easy_init())) { log_msg("Failed to initialize libcurl!\n"); @@ -224,13 +257,16 @@ static int send_telegram_notification(const char *msg) do_curl_cleanup(hnd, escaped_msg, NULL); } - snprintf( - full_request_url, - sizeof full_request_url - 1, + ab_init(&full_request_url); + + ret = ab_append_fmt(&full_request_url, "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s", telegram_bot_token, telegram_chat_id, escaped_msg); - setopts_get_curl(hnd, full_request_url); + if (ret) + return -1; + + setopts_get_curl(hnd, full_request_url.buff); log_msg("> Sending notification!\n"); return do_curl(hnd, escaped_msg, NULL); } @@ -250,7 +286,7 @@ void setup_slack(void) slack_webhook_url = getenv("SLACK_WEBHOOK_URL"); if (!slack_webhook_url) { - panic("Unable to find env vars for, please check if you have set\n" + panic("Unable to find env vars for Slack, please check if you have set " "the SLACK_WEBHOOK_URL!!\n"); } setup = 1; diff --git a/str.c b/str.c new file mode 100644 index 0000000..d57313d --- /dev/null +++ b/str.c @@ -0,0 +1,244 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +/* + * String/append buffer implementation based on Aqua: + * https://gist.github.com/Theldus/09ed2205aa5ba15cdf4571b71cd1c8fc + */ + +#include "str.h" +#include "log.h" + +/* Malloc is only used if AB_USE_MALLOC is defined. */ +#ifdef AB_USE_MALLOC +#if defined(AB_CALLOC) && defined(AB_REALLOC) && defined(AB_FREE) +# define AB_USE_STDLIB +#elif !defined(AB_CALLOC) && !defined(AB_REALLOC) && !defined(AB_FREE) +# define AB_USE_STDLIB +#else +#error "For custom memory allocators, you should define all three routines!" +#error "Please define: AB_CALLOC, AB_REALLOC and AB_FREE!" +#endif +#endif + +#ifndef AB_CALLOC +#define AB_CALLOC(nmemb,sz) calloc((nmemb),(sz)) +#define AB_REALLOC(p,newsz) realloc((p),(newsz)) +#define AB_FREE(p) free((p)) +#endif + +#ifdef AB_USE_STDLIB +#include +#endif + +#include +#include +#include +#include + +/* ========================================================================= */ +/* BUFFER ROUTINES */ +/* ========================================================================= */ + +#ifdef AB_USE_MALLOC +/** + * @brief Rounds up to the next power of two. + * + * @param target Target number to be rounded. + * + * @return Returns the next power of two. + */ +static size_t next_power(size_t target) +{ + target--; + target |= target >> 1; + target |= target >> 2; + target |= target >> 4; + target |= target >> 8; + target |= target >> 16; + target++; + return (target); +} +#endif + +/** + * @brief Checks if the new size fits in the append buffer, if not, + * reallocates the buffer size by @p incr bytes. + * + * If the macro AB_USE_MALLOC is not defined (default), this only + * checks if the new size fits the buffer. + * + * @param sh Aqua highlight context. + * @param incr Size (in bytes) to be incremented. + * + * @return Returns 0 if success, -1 otherwise. + * + * @note The new size is the next power of two, that is capable + * to hold the required buffer size. + */ +static int increase_buff(struct str_ab *sh, size_t incr) +{ +#ifndef AB_USE_MALLOC + if (sh->pos + incr >= MAX_LINE) { + log_msg("(increase buffer) Unable to fit appended message!\n"); + log_msg("(static storage) incr: %zu, buff_len: %zu, pos: %zu\n", + incr, sh->pos, sh->buff_len); + return (-1); + } +#else + char *new; + size_t new_size; + if (sh->pos + incr >= sh->buff_len) + { + new_size = next_power(sh->buff_len + incr); + new = AB_REALLOC(sh->buff, new_size); + if (new == NULL) + { + AB_FREE(sh->buff); + sh->buff = NULL; + + log_msg("(increase buffer) Unable to fit appended message!\n"); + log_msg("(realloc storage) incr: %zu, buff_len: %zu, pos: %zu\n", + incr, sh->pos, sh->buff_len); + return (-1); + } + sh->buff_len = new_size; + sh->buff = new; + } +#endif + return (0); +} + +/** + * @brief Initializes the append buffer context. + * + * @param ab Append buffer structure. + * + * @return Returns 0 if success, -1 otherwise. + */ +int ab_init(struct str_ab *ab) +{ + if (!ab) + return (-1); + + memset(ab, 0, sizeof(*ab)); + +#ifndef AB_USE_MALLOC + ab->buff_len = MAX_LINE; +#else + ab->buff = AB_CALLOC(MAX_LINE, 1); + if (!ab->buff) + return (-1); + ab->buff_len = MAX_LINE; +#endif + + return (0); +} + +/** + * @brief Append a given char @p c into the buffer. + * + * @param sh Aqua highlight context. + * @param c Char to be appended. + * + * @return Returns 0 if success, -1 otherwise. + */ +int ab_append_chr(struct str_ab *sh, char c) +{ + if (increase_buff(sh, 2) < 0) + return (-1); + + sh->buff[sh->pos + 0] = c; + sh->buff[sh->pos + 1] = '\0'; + sh->pos++; + return (0); +} + +/** + * @brief Appends a given string pointed by @p s of size @p len + * into the current buffer. + * + * If @p len is 0, the string is assumed to be null-terminated + * and its length is obtained. + * + * @param ab Append buffer context. + * @param s String to be append into the buffer. + * @param len String size, if 0, it's length is obtained. + * + * @return Returns 0 if success, -1 otherwise. + */ +int ab_append_str(struct str_ab *ab, const char *s, size_t len) +{ + if (!len) + len = strlen(s); + + if (increase_buff(ab, len + 1) < 0) + return (-1); + + memcpy(ab->buff + ab->pos, s, len); + ab->pos += len; + ab->buff[ab->pos] = '\0'; + return (0); +} + +/** + * @brief Appends a given formatted string pointed by @p fmt. + * + * @param ab Append buffer context. + * @param fmt Formatted string to be appended. + * + * @return Returns 0 if success, -1 otherwise. + */ +int ab_append_fmt(struct str_ab *ab, const char *fmt, ...) +{ + int str_len, ab_len, orig_ab_pos; + char *buff_st; + va_list ap; + + buff_st = ab->buff + ab->pos; + ab_len = ab->buff_len - ab->pos; + + va_start(ap, fmt); + str_len = vsnprintf(buff_st, ab_len, fmt, ap); + if (str_len < 0) { + log_msg("Unable to fit appended message!\n"); + return (-1); + } + va_end(ap); + + /* If it fits, just happily returns. */ + if (str_len + 1 <= ab_len) { + ab->pos += str_len; + return (0); + } + + /* Otherwise, adjust current pos and try to increase buffer. */ + else { + orig_ab_pos = ab->pos; + + /* temporarily advance our buffer + * to trick our increase buffer. */ + ab->pos = ab->buff_len; + + if (increase_buff(ab, (str_len + 1) - ab_len)) + return (-1); + + ab->pos = orig_ab_pos; + } + + buff_st = ab->buff + ab->pos; + ab_len = ab->buff_len - ab->pos; + + va_start(ap, fmt); + str_len = vsnprintf(buff_st, ab_len, fmt, ap); + if (str_len < 0 || (str_len + 1) > ab_len) { + log_msg("Unable to fit appended message!\n"); + return (-1); + } + va_end(ap); + + ab->pos += str_len; + return (0); +} diff --git a/str.h b/str.h new file mode 100644 index 0000000..eddb953 --- /dev/null +++ b/str.h @@ -0,0 +1,39 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +#ifndef STR_H +#define STR_H + + #include + + /* + * Enable to disable malloc support and enable dinamically + * allocated buffer. + */ + #if 0 + #define AB_USE_MALLOC + #endif + + /* Maximum highlighted line len, when built without malloc. */ + #define MAX_LINE 4096 + + /* Append buffer. */ + struct str_ab + { + #ifndef AB_USE_MALLOC + char buff[MAX_LINE + 1]; + #else + char *buff; + #endif + size_t buff_len; + size_t pos; + }; + + extern int ab_init(struct str_ab *ab); + extern int ab_append_chr(struct str_ab *sh, char c); + extern int ab_append_str(struct str_ab *ab, const char *s, size_t len); + extern int ab_append_fmt(struct str_ab *ab, const char *fmt, ...); + +#endif /* STR_H. */