diff --git a/Makefile b/Makefile index eda6e49..4611d24 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ CFLAGS += -Wall -Wextra LDLIBS += -pthread -lcurl STRIP = strip VERSION = v0.1 +OBJS = alertik.o events.o env_events.o notifiers.o ifeq ($(LOG_FILE),yes) CFLAGS += -DUSE_FILE_AS_LOG @@ -32,7 +33,7 @@ CFLAGS += -DGIT_HASH=\"$(GIT_HASH)\" all: alertik Makefile $(STRIP) --strip-all alertik -alertik: alertik.o events.o +alertik: $(OBJS) clean: - rm -f alertik.o events.o alertik + rm -f $(OBJS) alertik diff --git a/alertik.c b/alertik.c index 37c905a..b252da1 100644 --- a/alertik.c +++ b/alertik.c @@ -19,29 +19,18 @@ #include #include -#include - #include "alertik.h" #include "events.h" +#include "env_events.h" +#include "notifiers.h" /* Uncomment/comment to enable/disable the following settings. */ // #define USE_FILE_AS_LOG /* stdout if commented. */ -// #define CURL_VERBOSE -// #define VALIDATE_CERTS -// #define DISABLE_NOTIFICATIONS #define FIFO_MAX 64 #define SYSLOG_PORT 5140 #define LOG_FILE "log/log.txt" -/* Telegram & request settings. */ -static char *TELEGRAM_BOT_TOKEN; -static char *TELEGRAM_CHAT_ID; -char *TELEGRAM_NICKNAME; - -#define CURL_USER_AGENT "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " \ - "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" - /* Circular message buffer. */ static struct circ_buffer { int head; @@ -56,7 +45,7 @@ static pthread_cond_t fifo_new_log_entry = PTHREAD_COND_INITIALIZER; /* Misc. */ #define LAST_SENT_THRESHOLD_SECS 10 /* Minimum time (in secs) between two */ -static time_t time_last_sent_notify; /* notifications. */ +time_t time_last_sent_notify; /* notifications. */ static int curr_file; //////////////////////////////// LOGGING ////////////////////////////////////// @@ -228,75 +217,6 @@ static int pop_msg_from_fifo(struct log_event *ev) } ///////////////////////////// MESSAGE HANDLING //////////////////////////////// - -/* Just to omit the print to stdout. */ -size_t libcurl_noop_cb(void *ptr, size_t size, size_t nmemb, void *data) { - ((void)ptr); - ((void)data); - return size * nmemb; -} - -int send_telegram_notification(const char *msg) -{ - char full_request_url[4096] = {0}; - char *escaped_msg = NULL; - CURLcode ret_curl; - CURL *hnd; - int ret; - - ret = -1; - - hnd = curl_easy_init(); - if (!hnd) { - log_msg("> Unable to initialize libcurl!\n"); - return ret; - } - - log_msg("> Sending notification!\n"); - - escaped_msg = curl_easy_escape(hnd, msg, 0); - if (!escaped_msg) { - log_msg("> Unable to escape notification message...\n"); - goto error; - } - - snprintf( - full_request_url, - sizeof full_request_url - 1, - "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s", - TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, escaped_msg); - - curl_easy_setopt(hnd, CURLOPT_URL, full_request_url); - curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); - curl_easy_setopt(hnd, CURLOPT_USERAGENT, CURL_USER_AGENT); - curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 3L); - curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); - curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, libcurl_noop_cb); -#ifdef CURL_VERBOSE - curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); -#endif -#ifndef VALIDATE_CERTS - curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); -#endif - -#ifndef DISABLE_NOTIFICATIONS - ret_curl = curl_easy_perform(hnd); - if (ret_curl != CURLE_OK) { - log_msg("> Unable to send request!\n"); - goto error; - } else { - time_last_sent_notify = time(NULL); /* Update the time of our last sent */ - log_msg("> Done!\n"); /* notification. */ - } -#endif - - ret = 0; -error: - curl_free(escaped_msg); - curl_easy_cleanup(hnd); - return ret; -} - static void *handle_messages(void *p) { ((void)p); @@ -332,21 +252,12 @@ int main(void) atexit(close_log_file); - TELEGRAM_BOT_TOKEN = getenv("TELEGRAM_BOT_TOKEN"); - TELEGRAM_CHAT_ID = getenv("TELEGRAM_CHAT_ID"); - TELEGRAM_NICKNAME = getenv("TELEGRAM_NICKNAME"); - #ifndef USE_FILE_AS_LOG curr_file = STDOUT_FILENO; #endif - if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID || !TELEGRAM_NICKNAME) { - panic("Unable to find env vars, please check if you have all of the " - "following set:\n" - "- TELEGRAM_BOT_TOKEN\n" - "- TELEGRAM_CHAT_ID\n" - "- TELEGRAM_NICKNAME\n"); - } + setup_notifiers(); + init_environment_events(); log_msg( "Alertik (" GIT_HASH ") (built at " __DATE__ " " __TIME__ ")\n"); diff --git a/alertik.h b/alertik.h index f05aad1..d7412c6 100644 --- a/alertik.h +++ b/alertik.h @@ -8,6 +8,7 @@ #include #include + #include #define panic_errno(s) \ do {\ @@ -23,7 +24,7 @@ #define MIN(a,b) (((a)<(b))?(a):(b)) - extern char *TELEGRAM_NICKNAME; + extern time_t time_last_sent_notify; extern char *get_formatted_time(time_t time, char *time_str); extern void log_msg(const char *fmt, ...); extern int send_telegram_notification(const char *msg); diff --git a/env_events.c b/env_events.c new file mode 100644 index 0000000..ad650c7 --- /dev/null +++ b/env_events.c @@ -0,0 +1,131 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +#include +#include +#include +#include +#include +#include "env_events.h" +#include "alertik.h" +#include "notifiers.h" + +/* Event match types. */ +#define MATCH_TYPES_LEN 2 +static const char *const match_types[] = {"substr", "regex"}; + +/* Environment events list. */ +struct env_event env_events[MAX_ENV_EVENTS] = {0}; + +/** + * Safe string-to-int routine that takes into account: + * - Overflow and Underflow + * - No undefined behavior + * + * Taken from https://stackoverflow.com/a/12923949/3594716 + * and slightly adapted: no error classification, because + * I don't need to know, error is error. + * + * @param out Pointer to integer. + * @param s String to be converted. + * + * @return Returns 0 if success and a negative number otherwise. + */ +static int str2int(int *out, const char *s) +{ + char *end; + if (s[0] == '\0' || isspace(s[0])) + return -1; + errno = 0; + + long l = strtol(s, &end, 10); + + /* Both checks are needed because INT_MAX == LONG_MAX is possible. */ + if (l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) + return -1; + if (l < INT_MIN || (errno == ERANGE && l == LONG_MIN)) + return -1; + if (*end != '\0') + return -1; + + *out = l; + return 0; +} + +/**/ +static char *get_event_str(int ev_num, char *str) +{ + char *env; + char ev[64] = {0}; + snprintf(ev, sizeof ev - 1, "EVENT%d_%s", ev_num, str); + if (!(env = getenv(ev))) + panic("Unable to find event for %s\n", ev); + return env; +} + +/**/ +static int +get_event_idx(int ev_num, char *str, const char *const *str_list, int size) +{ + char *env = get_event_str(ev_num, str); + for (int i = 0; i < size; i++) { + if (!strcmp(env, str_list[i])) + return i; + } + panic("String parameter (%s) invalid for %s\n", env, str); +} + + + + + + + +/**/ +int init_environment_events(void) +{ + char *tmp; + int events; + + tmp = getenv("ENV_EVENTS"); + if (!tmp || (str2int(&events, tmp) < 0) || events <= 0) { + log_msg("Environment events not detected, disabling...\n"); + return (0); + } + + if (events >= MAX_ENV_EVENTS) + panic("Environment events exceeds the maximum supported (%d/%d)\n", + events, MAX_ENV_EVENTS); + + log_msg("%d environment events found, registering...\n"); + for (int i = 0; i < events; i++) { + /* EVENTn_MATCH_TYPE. */ + env_events[i].ev_match_type = get_event_idx(i, "MATCH_TYPE", + match_types, MATCH_TYPES_LEN); + /* EVENTn_NOTIFIER. */ + env_events[i].ev_notifier_idx = get_event_idx(i, "NOTIFIER", + notifiers_str, NUM_NOTIFIERS); + /* EVENTn_MATCH_STR. */ + env_events[i].ev_match_str = get_event_str(i, "MATCH_STR"); + /* EVENTn_MASK_MSG. */ + env_events[i].ev_mask_msg = get_event_str(i, "MASK_MSG"); + } + + log_msg("Environment events summary:\n"); + for (int i = 0; i < events; i++) { + printf( + "EVENT%d_MATCH_TYPE: %s\n" + "EVENT%d_MATCH_STR: %s\n" + "EVENT%d_NOTIFIER: %s\n" + "EVENT%d_MASK_MSG: %s\n\n", + i, match_types[env_events[i].ev_match_type], + i, env_events[i].ev_match_str, + i, notifiers_str[env_events[i].ev_notifier_idx], + i, env_events[i].ev_mask_msg + ); + } + + return (0); +} diff --git a/env_events.h b/env_events.h new file mode 100644 index 0000000..6f8e0bd --- /dev/null +++ b/env_events.h @@ -0,0 +1,23 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +#ifndef ENV_EVENTS_H +#define ENV_EVENTS_H + + #include "events.h" + + #define MAX_ENV_EVENTS 16 + + struct env_event { + int ev_match_type; /* whether regex or str. */ + int ev_notifier_idx; /* Telegram, Discord... */ + const char *ev_match_str; /* regex str or substr here. */ + const char *ev_mask_msg; /* Mask message to be sent. */ + }; + + extern struct env_event env_events[MAX_ENV_EVENTS]; + extern int init_environment_events(void); + +#endif /* ENV_EVENTS_H */ diff --git a/events.c b/events.c index 0eb3880..921b67a 100644 --- a/events.c +++ b/events.c @@ -72,9 +72,8 @@ void handle_wifi_login_attempts(struct log_event *ev) snprintf( notification_message, sizeof notification_message - 1, - "%s! %s!, there is someone trying to connect " + "There is someone trying to connect " "to your WiFi: %s, with the mac-address: %s, at:%s", - TELEGRAM_NICKNAME, TELEGRAM_NICKNAME, wifi_iface, mac_addr, get_formatted_time(ev->timestamp, time_str) diff --git a/notifiers.c b/notifiers.c new file mode 100644 index 0000000..c0d8956 --- /dev/null +++ b/notifiers.c @@ -0,0 +1,122 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +#include +#include +#include +#include +#include + +#include + +#include "notifiers.h" +#include "alertik.h" + +/* Just to omit the print to stdout. */ +size_t libcurl_noop_cb(void *ptr, size_t size, size_t nmemb, void *data) { + ((void)ptr); + ((void)data); + return size * nmemb; +} + +//////////////////////////////// TELEGRAM ////////////////////////////////////// +/* Telegram & request settings. */ +static char *telegram_bot_token; +static char *telegram_chat_id; + +void setup_telegram(void) +{ + telegram_bot_token = getenv("TELEGRAM_BOT_TOKEN"); + telegram_chat_id = getenv("TELEGRAM_CHAT_ID"); + if (!telegram_bot_token || !telegram_chat_id) { + panic( + "Unable to find env vars, please check if you have all of the " + "following set:\n" + "- TELEGRAM_BOT_TOKEN\n" + "- TELEGRAM_CHAT_ID\n" + ); + } +} + +int send_telegram_notification(const char *msg) +{ + char full_request_url[4096] = {0}; + char *escaped_msg = NULL; + CURLcode ret_curl; + CURL *hnd; + int ret; + + ret = -1; + + hnd = curl_easy_init(); + if (!hnd) { + log_msg("> Unable to initialize libcurl!\n"); + return ret; + } + + log_msg("> Sending notification!\n"); + + escaped_msg = curl_easy_escape(hnd, msg, 0); + if (!escaped_msg) { + log_msg("> Unable to escape notification message...\n"); + goto error; + } + + snprintf( + full_request_url, + sizeof full_request_url - 1, + "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s", + telegram_bot_token, telegram_chat_id, escaped_msg); + + curl_easy_setopt(hnd, CURLOPT_URL, full_request_url); + curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(hnd, CURLOPT_USERAGENT, CURL_USER_AGENT); + curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 3L); + curl_easy_setopt(hnd, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, libcurl_noop_cb); +#ifdef CURL_VERBOSE + curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); +#endif +#ifndef VALIDATE_CERTS + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); +#endif + +#ifndef DISABLE_NOTIFICATIONS + ret_curl = curl_easy_perform(hnd); + if (ret_curl != CURLE_OK) { + log_msg("> Unable to send request!\n"); + goto error; + } else { + time_last_sent_notify = time(NULL); /* Update the time of our last sent */ + log_msg("> Done!\n"); /* notification. */ + } +#endif + + ret = 0; +error: + curl_free(escaped_msg); + curl_easy_cleanup(hnd); + return ret; +} +////////////////////////////////// END //////////////////////////////////////// + +const char *const notifiers_str[] = { + "Telegram" +}; + +struct notifier notifiers[] = { + /* Telegram. */ + { + .setup = setup_telegram, + .send_notification = send_telegram_notification + } +}; + +/* Global setup. */ +void setup_notifiers(void) +{ + for (int i = 0; i < NUM_NOTIFIERS; i++) + notifiers[i].setup(); +} diff --git a/notifiers.h b/notifiers.h new file mode 100644 index 0000000..d5bf4d3 --- /dev/null +++ b/notifiers.h @@ -0,0 +1,36 @@ +/* + * Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers. + * This is free and unencumbered software released into the public domain. + */ + +#ifndef NOTIFIERS_H +#define NOTIFIERS_H + + /* Uncomment/comment to enable/disable the following settings. */ + // #define CURL_VERBOSE + // #define VALIDATE_CERTS + // #define DISABLE_NOTIFICATIONS + + #define CURL_USER_AGENT "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " \ + "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" + + #define NUM_NOTIFIERS 1 + + /* Notifiers list, like: + * - Telegram + * - Slack + * - Discord + * - Teams + */ + extern const char *const notifiers_str[NUM_NOTIFIERS]; + + /* Notifier struct. */ + struct notifier { + void(*setup)(void); + int(*send_notification)(const char *msg); + }; + + extern struct notifier notifiers[NUM_NOTIFIERS]; + extern void setup_notifiers(void); + +#endif /* NOTIFIERS_H */