mail_log.py: refactor the dovecot login collector
This commit is contained in:
parent
08becf7fa3
commit
87ec4e9f82
|
@ -53,10 +53,10 @@ VERBOSE = False
|
||||||
# List of strings to filter users with
|
# List of strings to filter users with
|
||||||
FILTERS = None
|
FILTERS = None
|
||||||
|
|
||||||
# What to show by default
|
# What to show (with defaults)
|
||||||
SCAN_OUT = True # Outgoing email
|
SCAN_OUT = True # Outgoing email
|
||||||
SCAN_IN = True # Incoming email
|
SCAN_IN = True # Incoming email
|
||||||
SCAN_CONN = False # IMAP and POP3 logins
|
SCAN_DOVECOT_LOGIN = True # Dovecot Logins
|
||||||
SCAN_GREY = False # Greylisted email
|
SCAN_GREY = False # Greylisted email
|
||||||
SCAN_BLOCKED = False # Rejected email
|
SCAN_BLOCKED = False # Rejected email
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ def scan_mail_log(env):
|
||||||
"scan_time": time.time(), # The time in seconds the scan took
|
"scan_time": time.time(), # The time in seconds the scan took
|
||||||
"sent_mail": OrderedDict(), # Data about email sent by users
|
"sent_mail": OrderedDict(), # Data about email sent by users
|
||||||
"received_mail": OrderedDict(), # Data about email received by users
|
"received_mail": OrderedDict(), # Data about email received by users
|
||||||
"dovecot": OrderedDict(), # Data about Dovecot activity
|
"logins": OrderedDict(), # Data about Dovecot login activity
|
||||||
"postgrey": {}, # Data about greylisting of email addresses
|
"postgrey": {}, # Data about greylisting of email addresses
|
||||||
"rejected": OrderedDict(), # Emails that were blocked
|
"rejected": OrderedDict(), # Emails that were blocked
|
||||||
"known_addresses": None, # Addresses handled by the Miab installation
|
"known_addresses": None, # Addresses handled by the Miab installation
|
||||||
|
@ -199,43 +199,55 @@ def scan_mail_log(env):
|
||||||
[accum]
|
[accum]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Print Dovecot report
|
# Print login report
|
||||||
|
|
||||||
if collector["dovecot"]:
|
if collector["logins"]:
|
||||||
msg = "Email client logins between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
msg = "User logins per hour between {:%Y-%m-%d %H:%M:%S} and {:%Y-%m-%d %H:%M:%S}"
|
||||||
print_header(msg.format(END_DATE, START_DATE))
|
print_header(msg.format(END_DATE, START_DATE))
|
||||||
|
|
||||||
data = OrderedDict(sorted(collector["dovecot"].items(), key=email_sort))
|
data = OrderedDict(sorted(collector["logins"].items(), key=email_sort))
|
||||||
|
|
||||||
|
# Get a list of all of the protocols seen in the logs in reverse count order.
|
||||||
|
all_protocols = defaultdict(int)
|
||||||
|
for u in data.values():
|
||||||
|
for protocol_name, count in u["totals_by_protocol"].items():
|
||||||
|
all_protocols[protocol_name] += count
|
||||||
|
all_protocols = [k for k, v in sorted(all_protocols.items(), key=lambda kv : -kv[1])]
|
||||||
|
|
||||||
print_user_table(
|
print_user_table(
|
||||||
data.keys(),
|
data.keys(),
|
||||||
data=[
|
data=[
|
||||||
("imap", [u["imap"] for u in data.values()]),
|
(protocol_name, [
|
||||||
("pop3", [u["pop3"] for u in data.values()]),
|
round(u["totals_by_protocol"][protocol_name] / (u["latest"]-u["earliest"]).total_seconds() * 60*60, 1)
|
||||||
|
if (u["latest"]-u["earliest"]).total_seconds() > 0
|
||||||
|
else 0 # prevent division by zero
|
||||||
|
for u in data.values()])
|
||||||
|
for protocol_name in all_protocols
|
||||||
],
|
],
|
||||||
sub_data=[
|
sub_data=[
|
||||||
("IMAP IP addresses", [[k + " (%d)" % v for k, v in u["imap-logins"].items()]
|
("Protocol and Source", [[
|
||||||
for u in data.values()]),
|
"{} {}: {} times".format(protocol_name, host, count)
|
||||||
("POP3 IP addresses", [[k + " (%d)" % v for k, v in u["pop3-logins"].items()]
|
for (protocol_name, host), count
|
||||||
for u in data.values()]),
|
in sorted(u["totals_by_protocol_and_host"].items(), key=lambda kv:-kv[1])
|
||||||
|
] for u in data.values()])
|
||||||
],
|
],
|
||||||
activity=[
|
activity=[
|
||||||
("imap", [u["activity-by-hour"]["imap"] for u in data.values()]),
|
(protocol_name, [u["activity-by-hour"][protocol_name] for u in data.values()])
|
||||||
("pop3", [u["activity-by-hour"]["pop3"] for u in data.values()]),
|
for protocol_name in all_protocols
|
||||||
],
|
],
|
||||||
earliest=[u["earliest"] for u in data.values()],
|
earliest=[u["earliest"] for u in data.values()],
|
||||||
latest=[u["latest"] for u in data.values()],
|
latest=[u["latest"] for u in data.values()],
|
||||||
|
numstr=lambda n : str(round(n, 1)),
|
||||||
)
|
)
|
||||||
|
|
||||||
accum = {"imap": defaultdict(int), "pop3": defaultdict(int), "both": defaultdict(int)}
|
accum = { protocol_name: defaultdict(int) for protocol_name in all_protocols }
|
||||||
for h in range(24):
|
for h in range(24):
|
||||||
accum["imap"][h] = sum(d["activity-by-hour"]["imap"][h] for d in data.values())
|
for protocol_name in all_protocols:
|
||||||
accum["pop3"][h] = sum(d["activity-by-hour"]["pop3"][h] for d in data.values())
|
accum[protocol_name][h] = sum(d["activity-by-hour"][protocol_name][h] for d in data.values())
|
||||||
accum["both"][h] = accum["imap"][h] + accum["pop3"][h]
|
|
||||||
|
|
||||||
print_time_table(
|
print_time_table(
|
||||||
["imap", "pop3", " +"],
|
all_protocols,
|
||||||
[accum["imap"], accum["pop3"], accum["both"]]
|
[accum[protocol_name] for protocol_name in all_protocols]
|
||||||
)
|
)
|
||||||
|
|
||||||
if collector["postgrey"]:
|
if collector["postgrey"]:
|
||||||
|
@ -348,9 +360,9 @@ def scan_mail_log_line(line, collector):
|
||||||
elif service == "postfix/lmtp":
|
elif service == "postfix/lmtp":
|
||||||
if SCAN_IN:
|
if SCAN_IN:
|
||||||
scan_postfix_lmtp_line(date, log, collector)
|
scan_postfix_lmtp_line(date, log, collector)
|
||||||
elif service in ("imap-login", "pop3-login"):
|
elif service.endswith("-login"):
|
||||||
if SCAN_CONN:
|
if SCAN_DOVECOT_LOGIN:
|
||||||
scan_dovecot_line(date, log, collector, service[:4])
|
scan_dovecot_login_line(date, log, collector, service[:4])
|
||||||
elif service == "postgrey":
|
elif service == "postgrey":
|
||||||
if SCAN_GREY:
|
if SCAN_GREY:
|
||||||
scan_postgrey_line(date, log, collector)
|
scan_postgrey_line(date, log, collector)
|
||||||
|
@ -448,8 +460,8 @@ def scan_postfix_smtpd_line(date, log, collector):
|
||||||
collector["rejected"][user] = data
|
collector["rejected"][user] = data
|
||||||
|
|
||||||
|
|
||||||
def scan_dovecot_line(date, log, collector, prot):
|
def scan_dovecot_login_line(date, log, collector, protocol_name):
|
||||||
""" Scan a dovecot log line and extract interesting data """
|
""" Scan a dovecot login log line and extract interesting data """
|
||||||
|
|
||||||
m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
|
m = re.match("Info: Login: user=<(.*?)>, method=PLAIN, rip=(.*?),", log)
|
||||||
|
|
||||||
|
@ -459,33 +471,28 @@ def scan_dovecot_line(date, log, collector, prot):
|
||||||
|
|
||||||
if user_match(user):
|
if user_match(user):
|
||||||
# Get the user data, or create it if the user is new
|
# Get the user data, or create it if the user is new
|
||||||
data = collector["dovecot"].get(
|
data = collector["logins"].get(
|
||||||
user,
|
user,
|
||||||
{
|
{
|
||||||
"imap": 0,
|
|
||||||
"pop3": 0,
|
|
||||||
"earliest": None,
|
"earliest": None,
|
||||||
"latest": None,
|
"latest": None,
|
||||||
"imap-logins": defaultdict(int),
|
"totals_by_protocol": defaultdict(int),
|
||||||
"pop3-logins": defaultdict(int),
|
"totals_by_protocol_and_host": defaultdict(int),
|
||||||
"activity-by-hour": {
|
"activity-by-hour": defaultdict(lambda : defaultdict(int)),
|
||||||
"imap": defaultdict(int),
|
|
||||||
"pop3": defaultdict(int),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
data[prot] += 1
|
|
||||||
data["activity-by-hour"][prot][date.hour] += 1
|
|
||||||
|
|
||||||
if data["latest"] is None:
|
if data["latest"] is None:
|
||||||
data["latest"] = date
|
data["latest"] = date
|
||||||
data["earliest"] = date
|
data["earliest"] = date
|
||||||
|
|
||||||
if rip not in ("127.0.0.1", "::1") or True:
|
data["totals_by_protocol"][protocol_name] += 1
|
||||||
data["%s-logins" % prot][rip] += 1
|
data["totals_by_protocol_and_host"][(protocol_name, rip)] += 1
|
||||||
|
|
||||||
collector["dovecot"][user] = data
|
if rip not in ("127.0.0.1", "::1") or True:
|
||||||
|
data["activity-by-hour"][protocol_name][date.hour] += 1
|
||||||
|
|
||||||
|
collector["logins"][user] = data
|
||||||
|
|
||||||
|
|
||||||
def scan_postfix_lmtp_line(date, log, collector):
|
def scan_postfix_lmtp_line(date, log, collector):
|
||||||
|
@ -640,7 +647,7 @@ def print_time_table(labels, data, do_print=True):
|
||||||
for i, d in enumerate(data):
|
for i, d in enumerate(data):
|
||||||
lines[i] += base.format(d[h])
|
lines[i] += base.format(d[h])
|
||||||
|
|
||||||
lines.insert(0, "┬")
|
lines.insert(0, "┬ totals by time of day:")
|
||||||
lines.append("└" + (len(lines[-1]) - 2) * "─")
|
lines.append("└" + (len(lines[-1]) - 2) * "─")
|
||||||
|
|
||||||
if do_print:
|
if do_print:
|
||||||
|
@ -650,7 +657,7 @@ def print_time_table(labels, data, do_print=True):
|
||||||
|
|
||||||
|
|
||||||
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
|
def print_user_table(users, data=None, sub_data=None, activity=None, latest=None, earliest=None,
|
||||||
delimit=False):
|
delimit=False, numstr=str):
|
||||||
str_temp = "{:<32} "
|
str_temp = "{:<32} "
|
||||||
lines = []
|
lines = []
|
||||||
data = data or []
|
data = data or []
|
||||||
|
@ -764,7 +771,7 @@ def print_user_table(users, data=None, sub_data=None, activity=None, latest=None
|
||||||
|
|
||||||
# Print totals
|
# Print totals
|
||||||
|
|
||||||
data_accum = [str(a) for a in data_accum]
|
data_accum = [numstr(a) for a in data_accum]
|
||||||
footer = str_temp.format("Totals:" if do_accum else " ")
|
footer = str_temp.format("Totals:" if do_accum else " ")
|
||||||
for row, (l, _) in enumerate(data):
|
for row, (l, _) in enumerate(data):
|
||||||
temp = "{:>%d}" % max(5, len(l) + 1)
|
temp = "{:>%d}" % max(5, len(l) + 1)
|
||||||
|
@ -818,7 +825,7 @@ if __name__ == "__main__":
|
||||||
action="store_true")
|
action="store_true")
|
||||||
parser.add_argument("-s", "--sent", help="Scan for sent emails.",
|
parser.add_argument("-s", "--sent", help="Scan for sent emails.",
|
||||||
action="store_true")
|
action="store_true")
|
||||||
parser.add_argument("-l", "--logins", help="Scan for IMAP/POP logins.",
|
parser.add_argument("-l", "--logins", help="Scan for user logins to IMAP/POP3.",
|
||||||
action="store_true")
|
action="store_true")
|
||||||
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
|
parser.add_argument("-g", "--grey", help="Scan for greylisted emails.",
|
||||||
action="store_true")
|
action="store_true")
|
||||||
|
@ -863,8 +870,8 @@ if __name__ == "__main__":
|
||||||
if not SCAN_OUT:
|
if not SCAN_OUT:
|
||||||
print("Ignoring sent emails")
|
print("Ignoring sent emails")
|
||||||
|
|
||||||
SCAN_CONN = args.logins
|
SCAN_DOVECOT_LOGIN = args.logins
|
||||||
if not SCAN_CONN:
|
if not SCAN_DOVECOT_LOGIN:
|
||||||
print("Ignoring logins")
|
print("Ignoring logins")
|
||||||
|
|
||||||
SCAN_GREY = args.grey
|
SCAN_GREY = args.grey
|
||||||
|
|
Loading…
Reference in New Issue