#! /usr/bin/python3

import ipaddress
import re
import sys
import syslog
import select
from systemd import journal

# The unicode() function in python 3 is renamed as str()
if sys.version_info.major == 3:
    unicode = str

IP_WHITELIST_CONF="/etc/sysmonitor/ip_whitelist.conf"

'''
 sshd logs:
 ...
 Accepted password for root from x.x.x.x port y ssh2
 Accepted publickey for root from x.x.x.x port y ssh2:...
 ...
 Failed password for root from x.x.x.x port y ssh2
 Failed password for invalid user xxx from x.x.x.x port y ssh2
 ...
 Invalid user kkk from x.x.x.x port y
 ...

'''

# match first word
ALL_SSHD = re.compile(r'^(?P<action>0?[A-Za-z]*).*')

# eg. Failed password for invalid user xxx from x.x.x.x port y ssh2
SSHD_FAILED_PASS = re.compile(r'Failed password for (invalid user )?(?P<user>[^\s]*).*from\s(?P<host>0?[^\s]*)\sport\s(?P<port>0?\d+).*')

# eg. Invalid user xxx from x.x.x.x port y
SSHD_INVALID_USER = re.compile(r'Invalid user (?P<user>[^\s]*).*from\s(?P<host>0?[^\s]*)\sport\s(?P<port>0?\d+).*')

# eg. Accepted publickey for ...
# eg. Accepted password for xxx from x.x.x.x port y
SSHD_ACCEPTED_CONN = re.compile(r'Accepted\s*(publickey|password)\s*for\s*(?P<user>[^\s]*)\sfrom\s(?P<host>[^\s]*)\sport\s(?P<port>\d+).*')


class Ip_whitelist:
    def __init__(self):
        self.whitelist_ips = []
        self.whitelist_nets = []

        with open(IP_WHITELIST_CONF, 'r') as ifp:
            for line in ifp:
                line = unicode(line.strip())
                if line.startswith('#'):
                    continue

                if line.find('/'):
                    try:
                        self.whitelist_nets.append(ipaddress.ip_network(line))
                    except Exception as e:
                        syslog.syslog('non-whitelist: %s, skipping...'%e)
                        print(e)
                else:
                    try:
                        self.whitelist_ips.append(ipaddress.ip_address(line))
                    except Exception as e:
                        syslog.syslog('non-whitelist: %s, skipping...'%e)
                        print(e)

    def is_whitelist(self, ip):
        ip = unicode(ip)
        try:
            ip = ipaddress.ip_address(ip)
        except Exception as e:
            syslog.syslog('non-whitelist: invalid input %s'%e)
            print('invalid input: %s'%e)
            return False

        if ip in self.whitelist_ips:
            return True

        for net in self.whitelist_nets:
            if ip in net:
                return True
        return False


def action_accepted_conn(msg, time, ip_whitelist):
    match = SSHD_ACCEPTED_CONN.match(msg)
    if not match:
        return
    if ip_whitelist.is_whitelist(match.group('host')):
        return
    syslog.syslog('non-whitelist: %s port %s user:%s success at %s'%(match.group('host'), match.group('port'), match.group('user'), time))


def action_failed_pass(msg, time, ip_whitelist):
    match = SSHD_FAILED_PASS.match(msg)
    if not match:
        return
    if ip_whitelist.is_whitelist(match.group('host')):
        return
    syslog.syslog('non-whitelist: %s port %s user:%s failed at %s'%(match.group('host'), match.group('port'), match.group('user'), time))


def action_invalid_user(msg, time, ip_whitelist):
    match = SSHD_INVALID_USER.match(msg)
    if not match:
        return
    if ip_whitelist.is_whitelist(match.group('host')):
        return
    syslog.syslog('non-whitelist: %s port %s invalid user:%s at %s'%(match.group('host'), match.group('port'), match.group('user'), time))

sshd_actions = {
    'Accepted' : action_accepted_conn,
    'Failed'   : action_failed_pass,
    'Invalid'  : action_invalid_user,
    'None'     : lambda *args: None
}


def get_action(msg):
    match = ALL_SSHD.match(msg)
    if not match:
        return 'None'

    if match.group('action') not in sshd_actions:
        return 'None'

    return match.group('action')


if __name__ == "__main__":
    syslog.openlog('sysmonitor')

    whitelist = Ip_whitelist()
    j = journal.Reader()
    j.log_level(journal.LOG_INFO)

    j.add_match(_SYSTEMD_UNIT='sshd.service')
    j.seek_tail()
    j.get_previous()

    p = select.poll()
    p.register(j, j.get_events())

    while p.poll():
        if j.process() != journal.APPEND:
            continue

        for entry in j:
            if entry['MESSAGE'] != "":
                msg = entry['MESSAGE']
                sshd_actions[get_action(msg)](msg, entry['__REALTIME_TIMESTAMP'], whitelist)

