257b4438f2b88321b95ce3d6917d8659d84c8297
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