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