""" Get login statistics for a samba server daemon (uses journald). Recommended setup in /etc/smbd.conf is to set `logging = syslog@3 ...` (ensure smbd was built with `configure --with-syslog`). """ import re from systemd import journal from logparse import config from logparse.formatting import * from logparse.load_parsers import Parser from logparse.util import LogPeriod, resolve class SmbdJournald(Parser): def __init__(self): super().__init__() self.name = "smbd_journald" self.info = "Get login statistics for a samba server." def parse_log(self): logger.debug("Starting smbd section") section = Section("smbd") j = journal.Reader() j.this_boot() j.log_level(journal.LOG_DEBUG) j.add_match(_COMM="smbd") messages = [entry["MESSAGE"] for entry in j if "MESSAGE" in entry] total_auths = 0 # total no. of logins for all users and all shares shares = {} # file shares (each share is mapped to a list of # user-hostname pairs) logger.debug("Found {0} samba logins".format(str(len(messages)))) logger.debug("Parsing data") for msg in messages: # one log file for each client if "connect to service" in msg: # Generate list of [('client', 'ip', 'share', 'user')] entry = re.search("(\w*)\s*\(ipv.:(.+):.+\) connect to service" "(\S+) initially as user (\S+)", msg) try: client, ip, share, user = entry.group(1,2,3,4) except: logger.warning("Malformed log message: " + msg) continue if not share in shares: share_match = False for pattern in config.prefs.get("smbd", "shares").split(): share_match = re.fullmatch(pattern, share) or share_match if not share_match: logger.debug("Ignoring share {0} due to config".format(share)) continue if (not client.strip()): client = ip userhost = user + '@' + resolve(client, fqdn=config.prefs.get("smbd", "smbd-resolve-domains")) user_match = False for pattern in config.prefs.get("smbd", "users").split(): user_match = re.fullmatch(pattern, userhost) or user_match if not user_match: logger.debug("Ignoring login to {0} by user {1} " "due to config".format(share, userhost)) continue total_auths += 1 if share in shares: shares[share].append(userhost) else: shares[share] = [userhost] # Format Data() objects section.append_data(Data(subtitle="Total of {0} authentications" .format(str(total_auths)))) for share, logins in shares.items(): share_data = Data() share_data.items = logins share_data.orderbyfreq() share_data.truncl(config.prefs.getint("logparse", "maxlist")) share_data.subtitle = share + " ({0}, {1})".format( plural("user", len(share_data.items)), plural("login", len(logins))) section.append_data(share_data) logger.debug("Found {0} logins for share {1}".format( str(len(logins)), share)) logger.info("Finished smbd section") return section