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