logparse / parsers / smbd_journald.pyon commit add systemctl and ufw parsers, support for varying degrees of severity (890d820)
   1"""
   2Get login statistics for a samba server daemon (uses journald). Recommended
   3setup in /etc/smbd.conf is to set `logging = syslog@3 ...` (ensure smbd was
   4built with `configure --with-syslog`).
   5"""
   6
   7import re
   8from systemd import journal
   9
  10from logparse import config
  11from logparse.formatting import *
  12from logparse.load_parsers import Parser
  13from logparse.util import LogPeriod, resolve
  14
  15class SmbdJournald(Parser):
  16
  17    def __init__(self):
  18        super().__init__()
  19        self.name = "smbd_journald"
  20        self.info = "Get login statistics for a samba server."
  21
  22    def parse_log(self):
  23        logger.debug("Starting smbd section")
  24        section = Section("smbd")
  25
  26        j = journal.Reader()
  27        j.this_boot()
  28        j.log_level(journal.LOG_DEBUG)
  29        j.add_match(_COMM="smbd")
  30
  31        messages = [entry["MESSAGE"] for entry in j if "MESSAGE" in entry]
  32
  33        total_auths = 0     # total number of logins for all users and all shares
  34        shares = {}         # file shares (each share is mapped to a list of user-hostname pairs)
  35
  36        logger.debug("Found {0} samba logins".format(str(len(messages))))
  37
  38        for msg in messages:  # one log file for each client
  39
  40            if "connect to service" in msg:
  41                entry = re.search('(\w*)\s*\(ipv.:(.+):.+\) connect to service (\S+) initially as user (\S+)', msg)  # [('client', 'ip', 'share', 'user')]
  42                try:
  43                    client, ip, share, user = entry.group(1,2,3,4)
  44                except:
  45                    logger.warning("Malformed log message: " + msg)
  46                    continue
  47
  48                if not share in shares:
  49                    share_match = False
  50                    for pattern in config.prefs.get("smbd", "shares").split():
  51                        share_match = re.fullmatch(pattern, share) or share_match
  52                    if not share_match:
  53                        logger.debug("Ignoring share {0} due to config".format(share))
  54                        continue
  55
  56                if (not client.strip()):
  57                    client = ip
  58                userhost = user + '@' + resolve(client, fqdn=config.prefs.get("smbd", "smbd-resolve-domains"))
  59
  60                user_match = False
  61                for pattern in config.prefs.get("smbd", "users").split():
  62                    user_match = re.fullmatch(pattern, userhost) or user_match
  63                if not user_match:
  64                    logger.debug("Ignoring login to {0} by user {1} due to config".format(share, userhost))
  65                    continue
  66
  67                total_auths += 1
  68                if share in shares:
  69                    shares[share].append(userhost)
  70                else:
  71                    shares[share] = [userhost]
  72
  73        section.append_data(Data(subtitle="Total of {0} authentications".format(str(total_auths))))
  74
  75        for share, logins in shares.items():
  76            share_data = Data()
  77            share_data.items = logins
  78            share_data.orderbyfreq()
  79            share_data.truncl(config.prefs.getint("logparse", "maxlist"))
  80            share_data.subtitle = share +  " ({0}, {1})".format(plural("user", len(share_data.items)), plural("login", len(logins)))
  81            section.append_data(share_data)
  82            logger.debug("Found {0} logins for share {1}".format(str(len(logins)), share))
  83
  84        logger.info("Finished smbd section")
  85        return section