2024-07-20 15:48:46 +03:30
#!/usr/bin/python
# -*- coding: utf-8 -*-
# syslog.py: independent worker process as a syslog server
# MikroWizard.com , Mikrotik router management solution
# Author: sepehr.ha@gmail.com
import socketserver
2025-02-03 12:32:09 +03:00
import asyncio
2024-07-20 15:48:46 +03:30
import time
2025-02-03 12:32:09 +03:00
import logging
import re
2024-07-20 15:48:46 +03:30
from libs . db import db_device
import logging
from libs . db import db_AA , db_events
from libs import util
try :
from libs import utilpro
ISPRO = True
except ImportError :
ISPRO = False
pass
2025-02-03 12:32:09 +03:00
log = logging . getLogger ( " SYSLOG " )
# A global asyncio event loop
event_loop = asyncio . new_event_loop ( )
asyncio . set_event_loop ( event_loop )
2024-07-20 15:48:46 +03:30
class SyslogUDPHandler ( socketserver . BaseRequestHandler ) :
2025-02-03 12:32:09 +03:00
def extract_data_from_regex ( self , regex , line ) :
2024-07-20 15:48:46 +03:30
try :
matches = re . finditer ( regex , line , re . MULTILINE )
2025-02-03 12:32:09 +03:00
sgroups = [ ]
2024-07-20 15:48:46 +03:30
for matchNum , match in enumerate ( matches , start = 1 ) :
for groupNum in range ( 0 , len ( match . groups ( ) ) ) :
groupNum = groupNum + 1
sgroups . append ( match . group ( groupNum ) )
return sgroups
2025-02-03 12:32:09 +03:00
except Exception as e :
log . error ( f " Regex error: { e } " )
2024-07-20 15:48:46 +03:30
return None
2025-02-03 12:32:09 +03:00
2024-07-20 15:48:46 +03:30
def handle ( self ) :
2025-02-03 12:32:09 +03:00
# Run the coroutine in the global event loop
asyncio . run_coroutine_threadsafe ( self . handle_log ( ) , event_loop )
# Respond to the client (optional)
async def handle_log ( self ) :
2024-07-20 15:48:46 +03:30
data = bytes . decode ( self . request [ 0 ] . strip ( ) , encoding = " utf-8 " )
message = str ( data )
#get current timestamp
ts = int ( time . time ( ) )
socket = self . request [ 1 ]
dev = db_device . query_device_by_ip ( self . client_address [ 0 ] )
2025-02-03 12:32:09 +03:00
regex = r ' (.*),?(info.*|warning|critical|error) mikrowizard( \ d+):.* '
2024-07-20 15:48:46 +03:30
if dev :
info = self . extract_data_from_regex ( regex , message )
opts = util . build_api_options ( dev )
try :
int ( info [ 2 ] )
if dev and dev . id != int ( info [ 2 ] ) :
log . error ( " Device id mismatch ignoring syslog for ip : {} " . format ( self . client_address [ 0 ] ) )
except :
log . error ( " **device id mismatch " )
log . error ( message )
2025-02-03 12:32:09 +03:00
log . error ( info )
2024-07-20 15:48:46 +03:30
log . error ( self . client_address [ 0 ] )
log . error ( " device id mismatch** " )
dev = False
pass
if dev and dev . id == int ( info [ 2 ] ) and ' mikrowizard ' in message and ' via api ' not in message :
if ' system,info,account ' in message :
regex = r " user (.*) logged (in|out) from (..*)via.(.*) "
info = self . extract_data_from_regex ( regex , message )
users = util . get_local_users ( opts )
try :
if info [ 0 ] in users :
msg = ' local '
else :
msg = ' radius '
if ' logged in ' in message :
if ' via api ' not in message :
db_AA . Auth . add_log ( dev . id , ' loggedin ' , info [ 0 ] , info [ 2 ] , info [ 3 ] , timestamp = ts , message = msg )
elif ' logged out ' in message :
if info [ 0 ] in users :
db_AA . Auth . add_log ( dev . id , ' loggedout ' , info [ 0 ] , info [ 2 ] , info [ 3 ] , timestamp = ts , message = msg )
except Exception as e :
log . error ( e )
log . error ( message )
elif ' system,error,critical ' in message :
if " login failure " in message :
users = util . get_local_users ( opts )
regex = r " login failure for user (.*) from (..*)via.(.*) "
info = self . extract_data_from_regex ( regex , message )
ts = int ( time . time ( ) )
if info [ 0 ] in users :
msg = ' local '
else :
msg = ' radius '
db_AA . Auth . add_log ( dev . id , ' failed ' , info [ 0 ] , info [ 1 ] , info [ 2 ] , timestamp = ts , message = msg )
elif " rebooted " in message :
regex = r ' system,error,critical mikrowizard \ d+: (.*) '
info = self . extract_data_from_regex ( regex , message )
db_events . state_event ( dev . id , " syslog " , " Unexpected Reboot " , " Critical " , 1 , info [ 0 ] )
elif ' system,info mikrowizard ' in message :
regex = r " system,info mikrowizard \ d+: (.*) (changed|added|removed|unscheduled) by (winbox- \ d. { 1,3} \ d \ /.* \ (winbox \ )|mac-msg \ (winbox \ )|tcp-msg \ (winbox \ )|ssh|telnet|api|api-ssl|.* \ /web|ftp|www-ssl).*:(.*)@(.*) \ ((.*) \ ) "
2025-01-10 17:48:23 +03:00
#with new versions of mikrotik syslog is not sending the correct trace in message
buged_regex = r " system,info mikrowizard \ d+: (.*) (changed|added|removed|unscheduled) by \ ((.*) \ ) "
2025-02-03 12:32:09 +03:00
if ISPRO :
# threading.Thread(target=utilpro.do_pro,args=()).start()
utilpro . do_pro ( " syslog " , False , dev , message )
2024-07-20 15:48:46 +03:30
if re . match ( regex , message ) :
info = self . extract_data_from_regex ( regex , message )
address = info [ 4 ] . split ( ' / ' )
ctype = ' '
if ' winbox ' in info [ 2 ] :
ctype = ' winbox '
if ' tcp ' in info [ 2 ] :
ctype = ' winbox-tcp '
elif ' mac ' in info [ 2 ] :
ctype = ' winbox-mac '
if ' terminal ' in address :
ctype + = ' /terminal '
elif ' ssh ' in info [ 2 ] :
ctype = ' ssh '
elif ' telnet ' in info [ 2 ] :
ctype = ' telnet '
elif ' /web ' in info [ 2 ] :
ctype = info [ 2 ] . split ( ' / ' ) [ 1 ] + " " + " ( {} ) " . format ( info [ 2 ] . split ( ' / ' ) [ 0 ] )
elif ' api ' in info [ 2 ] :
ctype = ' api '
db_AA . Account . add_log ( dev . id , info [ 0 ] , info [ 1 ] , info [ 3 ] , message , ctype , address [ 0 ] , info [ 5 ] )
2025-01-10 17:48:23 +03:00
elif re . match ( buged_regex , message ) :
info = self . extract_data_from_regex ( buged_regex , message )
db_AA . Account . add_log ( dev . id , info [ 0 ] , info [ 1 ] , " Unknown (Mikrotik Bug) " , message , config = info [ 2 ] )
log . error ( info )
2024-07-20 15:48:46 +03:30
elif " rebooted " in message :
db_events . state_event ( dev . id , " syslog " , " Router Rebooted " , " info " , 1 , info [ 0 ] )
2024-08-10 11:24:28 +03:30
elif " resetting system configuration " in message :
2024-07-31 17:43:09 +03:30
db_events . state_event ( dev . id , " syslog " , " Router reset " , " info " , 1 , info [ 0 ] )
2024-07-20 15:48:46 +03:30
else :
regex = r " system,info mikrowizard \ d+: (.*) (changed|added|removed|unscheduled) by (.*) "
info = self . extract_data_from_regex ( regex , message )
db_AA . Account . add_log ( dev . id , info [ 0 ] , info [ 1 ] , info [ 2 ] , message )
elif ' interface,info mikrowizard ' in message :
link_regex = r " interface,info mikrowizard \ d+: (.*) link (down|up).* "
events = list ( db_events . get_events_by_src_and_status ( " syslog " , 0 , dev . id ) . dicts ( ) )
if " link down " in message :
info = self . extract_data_from_regex ( link_regex , message )
db_events . state_event ( dev . id , " syslog " , " Link Down: " + info [ 0 ] , " Warning " , 0 , " Link is down for {} " . format ( info [ 0 ] ) )
elif " link up " in message :
info = self . extract_data_from_regex ( link_regex , message )
util . check_or_fix_event ( events , ' state ' , " Link Down: " + info [ 0 ] )
2025-02-03 12:32:09 +03:00
elif any ( term in message for term in [ " dhcp,info " , " dhcp,critical " , " dhcp,warning " ] ) :
type = ' cleint '
# if (" dhcp-client on" in message):
# dhcp_regex=r'dhcp,info mikrowizard\d+: dhcp-client on (.*) (got IP address|lost IP address) (\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\b) ?-? ?(.*)?'
# else:
# dhcp_regex=r'dhcp,info mikrowizard\d+: (.*) (assigned|deassigned) (\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\b) (for|to|from) (\b([A-Fa-f0-9]{2}[:-]){5}[A-Fa-f0-9]{2}\b)? ?(.*)?'
# type='server'
if ( not " dhcp-client on " in message ) :
type = ' server '
# dhcp_regex=r'dhcp,info mikrowizard\d+: (dhcp-client|.*) (deassigned|assigned|.*) (\d+\.\d+\.\d+\.\d+|on.*address)\s*(from|to|for|- lease stopped locally|$)\s*(.*)'
dhcp_regex = r ' dhcp,(?:info|warning|critical|error)(?:,info|,warning|,critical|,error)? mikrowizard \ d+: (.*) '
2024-07-20 15:48:46 +03:30
info = self . extract_data_from_regex ( dhcp_regex , message )
2025-02-03 12:32:09 +03:00
if " dhcp,info " in message :
level = " info "
elif " dhcp,warning " in message :
level = " warning "
elif " dhcp,critical " in message :
level = " critical "
else :
level = " error "
if type == ' server ' :
if info and " deassigned " in message :
log . error ( " Logging deassigned " )
db_events . state_event ( dev . id , " syslog " , " dhcp deassigned " , level , 1 , " {} " . format ( info [ 0 ] ) )
# db_events.state_event(dev.id, "syslog", "dhcp assigned","info",1,"server {} assigned {} to {}".format(info[0],info[2],info[4]))
elif info and " assigned " in message :
log . error ( " Logging deassigned " )
db_events . state_event ( dev . id , " syslog " , " dhcp assigned " , level , 1 , " {} " . format ( info [ 0 ] ) )
# db_events.state_event(dev.id, "syslog", "dhcp deassigned","info",1,"server {} deassigned {} from {}".format(info[0],info[2],info[4]))
else :
db_events . state_event ( dev . id , " syslog " , " dhcp client " , level , 1 , " {} " . format ( info [ 0 ] ) )
# db_events.state_event(dev.id, "syslog", "dhcp client","info",1,"{} {}".format(info[1],info[2]))
2024-07-20 15:48:46 +03:30
elif " wireless,info mikrowizard " in message :
if ISPRO :
utilpro . wireless_syslog_event ( dev , message )
else :
regex = r ' wireless,info mikrowizard \ d+: ([0-9A-Fa-f] {2} :[0-9A-Fa-f] {2} :[0-9A-Fa-f] {2} :[0-9A-Fa-f] {2} :[0-9A-Fa-f] {2} :[0-9A-Fa-f] {2} )@(.*): (connected|disconnected), (signal strength|.*)? (-? \ d {2} )?.* '
info = self . extract_data_from_regex ( regex , message )
if info :
strength = " "
if len ( info ) > 4 :
strength = info [ 4 ]
db_events . state_event ( dev . id , " syslog " , " wireless client " , " info " , 1 , " {} {} {} {} {} " . format ( info [ 0 ] , info [ 1 ] , info [ 2 ] , info [ 3 ] , strength ) )
log . error ( len ( info ) )
log . error ( message )
else :
log . error ( message )
2025-02-03 12:32:09 +03:00
def start_event_loop ( loop ) :
""" Run the event loop in a separate thread. """
asyncio . set_event_loop ( loop )
loop . run_forever ( )
2024-07-20 15:48:46 +03:30
if __name__ == " __main__ " :
try :
2025-02-03 12:32:09 +03:00
# Start the asyncio event loop in a separate thread
import threading
thread = threading . Thread ( target = start_event_loop , args = ( event_loop , ) , daemon = True )
thread . start ( )
# Start the UDP server
server = socketserver . UDPServer ( ( " 0.0.0.0 " , 5014 ) , SyslogUDPHandler )
server . serve_forever ( )
2024-07-20 15:48:46 +03:30
except ( IOError , SystemExit ) :
raise
2025-02-03 12:32:09 +03:00
except KeyboardInterrupt :
log . info ( " Shutting down server " )
event_loop . stop ( )