Initial env events impl + some rework

This commit is contained in:
Davidson Francis 2024-07-15 22:16:27 -03:00
parent 60e22c37d6
commit 0966091f94
8 changed files with 323 additions and 99 deletions

View File

@ -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

View File

@ -19,29 +19,18 @@
#include <netinet/in.h>
#include <netdb.h>
#include <curl/curl.h>
#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");

View File

@ -8,6 +8,7 @@
#include <stdarg.h>
#include <stdlib.h>
#include <time.h>
#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);

131
env_events.c Normal file
View File

@ -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 <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#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);
}

23
env_events.h Normal file
View File

@ -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 */

View File

@ -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)

122
notifiers.c Normal file
View File

@ -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 <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#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();
}

36
notifiers.h Normal file
View File

@ -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 */