2024-05-31 03:28:28 +02:00
|
|
|
/*
|
|
|
|
* Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers.
|
|
|
|
* This is free and unencumbered software released into the public domain.
|
|
|
|
*/
|
|
|
|
|
2024-07-19 03:41:19 +02:00
|
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
2024-05-31 03:28:28 +02:00
|
|
|
#include <stdio.h>
|
2024-07-19 03:41:19 +02:00
|
|
|
#include <stdlib.h>
|
2024-05-31 03:28:28 +02:00
|
|
|
#include <string.h>
|
|
|
|
#include "events.h"
|
|
|
|
#include "alertik.h"
|
2024-07-19 03:41:19 +02:00
|
|
|
#include "notifiers.h"
|
|
|
|
#include "log.h"
|
2024-05-31 03:28:28 +02:00
|
|
|
|
2024-07-26 02:28:18 +02:00
|
|
|
/*
|
|
|
|
* Static events
|
|
|
|
*/
|
|
|
|
|
2024-07-25 02:15:38 +02:00
|
|
|
/* Misc. */
|
|
|
|
#define MAX_MATCHES 32
|
|
|
|
static regmatch_t pmatch[MAX_MATCHES];
|
2024-05-31 03:28:28 +02:00
|
|
|
|
|
|
|
/* Handlers. */
|
2024-07-25 02:15:38 +02:00
|
|
|
static void handle_wifi_login_attempts(struct log_event *, int);
|
|
|
|
struct static_event static_events[NUM_EVENTS] = {
|
2024-05-31 03:28:28 +02:00
|
|
|
/* Failed login attempts. */
|
|
|
|
{
|
2024-07-25 02:15:38 +02:00
|
|
|
.ev_match_str = "unicast key exchange timeout",
|
|
|
|
.hnd = handle_wifi_login_attempts,
|
|
|
|
.ev_match_type = EVNT_SUBSTR,
|
|
|
|
.enabled = 0,
|
|
|
|
.ev_notifier_idx = NOTIFY_IDX_TELE
|
2024-05-31 03:28:28 +02:00
|
|
|
},
|
|
|
|
/* Add new handlers here. */
|
|
|
|
};
|
|
|
|
|
2024-07-26 02:28:18 +02:00
|
|
|
/**
|
|
|
|
* @brief Retrieves the event string from the environment variables.
|
|
|
|
*
|
|
|
|
* @param ev_num Event number.
|
|
|
|
* @param str String identifier.
|
|
|
|
*
|
|
|
|
* @return Returns the event string.
|
|
|
|
*/
|
2024-07-19 03:41:19 +02:00
|
|
|
static char *get_event_str(long ev_num, char *str)
|
|
|
|
{
|
|
|
|
char *env;
|
|
|
|
char ev[64] = {0};
|
|
|
|
snprintf(ev, sizeof ev - 1, "STATIC_EVENT%ld_%s", ev_num, str);
|
|
|
|
if (!(env = getenv(ev)))
|
|
|
|
panic("Unable to find event for %s\n", ev);
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
|
2024-07-26 02:28:18 +02:00
|
|
|
/**
|
|
|
|
* @brief Retrieves the index of the event from the environment variables.
|
|
|
|
*
|
|
|
|
* @param ev_num Event number.
|
|
|
|
* @param str String identifier.
|
|
|
|
* @param str_list List of strings to match against.
|
|
|
|
* @param size Size of the string list.
|
|
|
|
*
|
|
|
|
* @return Returns the index of the matching event.
|
|
|
|
*/
|
2024-07-19 03:41:19 +02:00
|
|
|
static int
|
|
|
|
get_event_idx(long 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);
|
|
|
|
}
|
|
|
|
|
2024-07-18 03:41:58 +02:00
|
|
|
/**
|
|
|
|
* @brief Given an event, checks if it belongs to one of the
|
|
|
|
* registered events and then, handle it.
|
|
|
|
*
|
|
|
|
* @param ev Event to be processed.
|
|
|
|
*
|
|
|
|
* @return Returns the amount of matches, 0 if none (not handled).
|
|
|
|
*/
|
|
|
|
int process_static_event(struct log_event *ev)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int handled;
|
2024-07-25 02:15:38 +02:00
|
|
|
struct static_event *sta_ev;
|
2024-07-18 03:41:58 +02:00
|
|
|
|
|
|
|
for (i = 0, handled = 0; i < NUM_EVENTS; i++) {
|
2024-07-24 03:24:00 +02:00
|
|
|
/* Skip not enabled events. */
|
2024-07-25 02:15:38 +02:00
|
|
|
if (!static_events[i].enabled)
|
2024-07-24 03:24:00 +02:00
|
|
|
continue;
|
|
|
|
|
2024-07-25 02:15:38 +02:00
|
|
|
sta_ev = &static_events[i];
|
|
|
|
|
|
|
|
if (static_events[i].ev_match_type == EVNT_SUBSTR) {
|
|
|
|
if (strstr(ev->msg, static_events[i].ev_match_str)) {
|
|
|
|
static_events[i].hnd(ev, i);
|
|
|
|
handled += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
if (regexec(&sta_ev->regex, ev->msg, MAX_MATCHES, pmatch, 0)) {
|
|
|
|
static_events[i].hnd(ev, i);
|
|
|
|
handled += 1;
|
|
|
|
}
|
2024-07-18 03:41:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return handled;
|
|
|
|
}
|
|
|
|
|
2024-07-19 03:41:19 +02:00
|
|
|
/**
|
|
|
|
* @brief Initialize static events.
|
|
|
|
*
|
|
|
|
* @return Returns 0 if there is no static event, and 1 if
|
|
|
|
* there is at least one _and_ is successfully configured.
|
|
|
|
*/
|
|
|
|
int init_static_events(void)
|
|
|
|
{
|
|
|
|
char *ptr, *end;
|
|
|
|
long ev;
|
|
|
|
|
|
|
|
/* Check for: STATIC_EVENTS_ENABLED=0,3,5,2... */
|
|
|
|
ptr = getenv("STATIC_EVENTS_ENABLED");
|
|
|
|
if (!ptr || ptr[0] == '\0') {
|
|
|
|
log_msg("Static events not detected, disabling...\n");
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
end = ptr;
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
ev = strtol(end, &end, 10);
|
|
|
|
if (errno != 0 || ((ptr == end) && ev == 0))
|
|
|
|
panic("Unable to parse STATIC_EVENTS_ENABLED, aborting...\n");
|
|
|
|
|
|
|
|
/* Skip whitespaces. */
|
|
|
|
while (*end != '\0' && isspace(*end))
|
|
|
|
end++;
|
|
|
|
|
|
|
|
/* Check if ev number is sane. */
|
|
|
|
if (ev < 0 || ev >= NUM_EVENTS)
|
|
|
|
panic("Event (%ld) is not valid!, should be between 0-%d\n",
|
|
|
|
ev, NUM_EVENTS - 1);
|
|
|
|
|
|
|
|
/* Try to retrieve & initialize notifier for the event. */
|
2024-07-25 02:15:38 +02:00
|
|
|
static_events[ev].ev_notifier_idx =
|
2024-07-19 03:41:19 +02:00
|
|
|
get_event_idx(ev, "NOTIFIER", notifiers_str, NUM_NOTIFIERS);
|
2024-07-25 02:15:38 +02:00
|
|
|
static_events[ev].enabled = 1;
|
2024-07-19 03:41:19 +02:00
|
|
|
|
|
|
|
if (*end != ',' && *end != '\0')
|
|
|
|
panic("Wrong event number in STATIC_EVENTS_ENABLED, aborting...\n");
|
|
|
|
|
|
|
|
} while (*end++ != '\0');
|
|
|
|
|
|
|
|
|
|
|
|
log_msg("Static events summary:\n");
|
|
|
|
for (int i = 0; i < NUM_EVENTS; i++) {
|
2024-07-25 02:15:38 +02:00
|
|
|
if (!static_events[i].enabled)
|
2024-07-19 03:41:19 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
printf(
|
|
|
|
"STATIC_EVENT%d : enabled\n"
|
|
|
|
"STATIC_EVENT%d_NOTIFIER: %s\n\n",
|
2024-07-25 02:15:38 +02:00
|
|
|
i, i, notifiers_str[static_events[i].ev_notifier_idx]
|
2024-07-19 03:41:19 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
/* Try to setup notifier if not yet. */
|
2024-07-25 02:15:38 +02:00
|
|
|
notifiers[static_events[i].ev_notifier_idx].setup();
|
|
|
|
|
|
|
|
/* If regex, compile it first. */
|
|
|
|
if (static_events[i].ev_match_type == EVNT_REGEX) {
|
|
|
|
if (regcomp(
|
|
|
|
&static_events[i].regex,
|
|
|
|
static_events[i].ev_match_str,
|
|
|
|
REG_EXTENDED))
|
|
|
|
{
|
|
|
|
panic("Unable to compile regex (%s) for EVENT%d!!!",
|
|
|
|
static_events[i].ev_match_str, i);
|
|
|
|
}
|
|
|
|
}
|
2024-07-19 03:41:19 +02:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2024-07-18 03:41:58 +02:00
|
|
|
|
2024-07-25 02:15:38 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
2024-05-31 03:28:28 +02:00
|
|
|
///////////////////////////// FAILED LOGIN ATTEMPTS ///////////////////////////
|
2024-07-25 02:15:38 +02:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2024-07-26 02:28:18 +02:00
|
|
|
/**
|
|
|
|
* @brief Parses the message pointed by @p msg and saves the
|
|
|
|
* read mac-address and interface in @p mac_addr and @wifi_iface.
|
|
|
|
*
|
|
|
|
* @param msg Buffer to be read and parsed.
|
|
|
|
* @param wifi_iface Output buffer that will contain the parsed
|
|
|
|
* device interface.
|
|
|
|
* @param mac_addr Output buffer that will contain the parsed
|
|
|
|
* mac address.
|
|
|
|
*
|
|
|
|
* @return Returns 0 if success, -1 otherwise.
|
|
|
|
*/
|
2024-05-31 03:28:28 +02:00
|
|
|
static int
|
|
|
|
parse_login_attempt_msg(const char *msg, char *wifi_iface, char *mac_addr)
|
|
|
|
{
|
|
|
|
size_t len = strlen(msg);
|
|
|
|
size_t tmp = 0;
|
|
|
|
size_t at = 0;
|
|
|
|
|
|
|
|
/* Find '@' and the last ' '. */
|
|
|
|
for (at = 0; at < len && msg[at] != '@'; at++) {
|
|
|
|
if (msg[at] == ' ')
|
|
|
|
tmp = at;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (at == len || !tmp) {
|
|
|
|
log_msg("unable to parse additional data, ignoring...\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(mac_addr, msg + tmp + 1, MIN(at - tmp - 1, 32));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find network name.
|
|
|
|
* Assuming that the interface name does not have ':'...
|
|
|
|
*/
|
|
|
|
for (tmp = at + 1; tmp < len && msg[tmp] != ':'; tmp++);
|
|
|
|
if (tmp == len) {
|
|
|
|
log_msg("unable to find interface name!, ignoring..\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(wifi_iface, msg + at + 1, MIN(tmp - at - 1, 32));
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2024-07-26 02:28:18 +02:00
|
|
|
/**
|
|
|
|
* @brief For a given log event @p ev and offset index @p idx_env,
|
|
|
|
* handle the event and send a notification message to the
|
|
|
|
* configured notifier.
|
|
|
|
*
|
|
|
|
* @param ev Log event structure.
|
|
|
|
* @param idx_env Event index.
|
|
|
|
*/
|
2024-07-19 03:41:19 +02:00
|
|
|
static void handle_wifi_login_attempts(struct log_event *ev, int idx_env)
|
2024-05-31 03:28:28 +02:00
|
|
|
{
|
|
|
|
char time_str[32] = {0};
|
|
|
|
char mac_addr[32] = {0};
|
|
|
|
char wifi_iface[32] = {0};
|
|
|
|
char notification_message[2048] = {0};
|
2024-07-19 03:41:19 +02:00
|
|
|
int notif_idx;
|
2024-05-31 03:28:28 +02:00
|
|
|
|
|
|
|
log_msg("> Login attempt detected!\n");
|
|
|
|
|
|
|
|
if (parse_login_attempt_msg(ev->msg, wifi_iface, mac_addr) < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Send our notification. */
|
|
|
|
snprintf(
|
|
|
|
notification_message,
|
|
|
|
sizeof notification_message - 1,
|
2024-07-16 03:16:27 +02:00
|
|
|
"There is someone trying to connect "
|
2024-05-31 03:28:28 +02:00
|
|
|
"to your WiFi: %s, with the mac-address: %s, at:%s",
|
|
|
|
wifi_iface,
|
|
|
|
mac_addr,
|
|
|
|
get_formatted_time(ev->timestamp, time_str)
|
|
|
|
);
|
|
|
|
|
|
|
|
log_msg("> Retrieved info, MAC: (%s), Interface: (%s)\n", mac_addr, wifi_iface);
|
|
|
|
|
2024-07-25 02:15:38 +02:00
|
|
|
notif_idx = static_events[idx_env].ev_notifier_idx;
|
2024-07-19 03:41:19 +02:00
|
|
|
if (notifiers[notif_idx].send_notification(notification_message) < 0) {
|
2024-05-31 03:28:28 +02:00
|
|
|
log_msg("unable to send the notification!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////// YOUR HANDLER HERE //////////////////////////////
|