mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2025-04-03 00:07:05 +00:00
273 lines
8.1 KiB
Python
273 lines
8.1 KiB
Python
#####
|
|
##### This file is part of Mail-in-a-Box-LDAP which is released under the
|
|
##### terms of the GNU Affero General Public License as published by the
|
|
##### Free Software Foundation, either version 3 of the License, or (at
|
|
##### your option) any later version. See file LICENSE or go to
|
|
##### https://github.com/downtownallday/mailinabox-ldap for full license
|
|
##### details.
|
|
#####
|
|
|
|
from .Timeseries import Timeseries
|
|
from .exceptions import InvalidArgsError
|
|
|
|
with open(__file__.replace('.py','.1.sql')) as fp:
|
|
select_1 = fp.read()
|
|
|
|
with open(__file__.replace('.py','.2.sql')) as fp:
|
|
select_2 = fp.read()
|
|
|
|
with open(__file__.replace('.py','.3.sql')) as fp:
|
|
select_3 = fp.read()
|
|
|
|
|
|
def user_activity(conn, args):
|
|
'''
|
|
details on user activity
|
|
'''
|
|
try:
|
|
user_id = args['user_id']
|
|
|
|
# use Timeseries to get a normalized start/end range
|
|
ts = Timeseries(
|
|
'User activity',
|
|
args['start_date'],
|
|
args['end_date'],
|
|
0
|
|
)
|
|
except KeyError:
|
|
raise InvalidArgsError()
|
|
|
|
# limit results
|
|
try:
|
|
limit = 'LIMIT ' + str(int(args.get('row_limit', 1000)));
|
|
except ValueError:
|
|
limit = 'LIMIT 1000'
|
|
|
|
#
|
|
# sent mail by user
|
|
#
|
|
c = conn.cursor()
|
|
|
|
sent_mail = {
|
|
'start': ts.start,
|
|
'end': ts.end,
|
|
'y': 'Sent mail',
|
|
'fields': [
|
|
# mta_connection
|
|
'connect_time',
|
|
'sasl_method',
|
|
# mta_accept
|
|
'envelope_from',
|
|
# mta_delivery
|
|
'rcpt_to',
|
|
'service',
|
|
'spam_score',
|
|
'spam_result',
|
|
'message_size',
|
|
'status',
|
|
'relay',
|
|
'delivery_info',
|
|
'delivery_connection',
|
|
'delivery_connection_info',
|
|
'sent_id', # must be last
|
|
],
|
|
'field_types': [
|
|
{ 'type':'datetime', 'format': '%Y-%m-%d %H:%M:%S' },# connect_time
|
|
'text/plain', # sasl_method
|
|
'text/email', # envelope_from
|
|
{ 'type':'text/email', 'label':'Recipient' }, # rcpt_to
|
|
'text/plain', # mta_delivery.service
|
|
{ 'type':'decimal', 'places':2 }, # spam_score
|
|
'text/plain', # spam_result
|
|
'number/size', # message_size
|
|
'text/plain', # status
|
|
'text/hostname', # relay
|
|
'text/plain', # delivery_info
|
|
'text/plain', # delivery_connection
|
|
'text/plain', # delivery_connection_info
|
|
'number/plain', # sent_id - must be last
|
|
],
|
|
'items': [],
|
|
'unique_sends': 0
|
|
}
|
|
last_mta_accept_id = -1
|
|
sent_id = 0
|
|
for row in c.execute(select_1 + limit, {
|
|
'user_id': user_id,
|
|
'start_date': ts.start,
|
|
'end_date': ts.end
|
|
}):
|
|
v = []
|
|
for key in sent_mail['fields']:
|
|
if key != 'sent_id':
|
|
v.append(row[key])
|
|
if last_mta_accept_id != row['mta_accept_id']:
|
|
sent_mail['unique_sends'] += 1
|
|
last_mta_accept_id = row['mta_accept_id']
|
|
sent_id += 1
|
|
v.append(sent_id)
|
|
sent_mail['items'].append(v)
|
|
|
|
|
|
#
|
|
# received mail by user
|
|
#
|
|
|
|
received_mail = {
|
|
'start': ts.start,
|
|
'end': ts.end,
|
|
'y': 'Sent mail',
|
|
'fields': [
|
|
# mta_connection
|
|
'connect_time',
|
|
'service',
|
|
'sasl_username',
|
|
'remote_host',
|
|
'remote_ip',
|
|
|
|
# mta_accept
|
|
'envelope_from',
|
|
'disposition',
|
|
'spf_result',
|
|
'dkim_result',
|
|
'dkim_reason',
|
|
'dmarc_result',
|
|
'dmarc_reason',
|
|
'message_id',
|
|
'failure_info',
|
|
|
|
# mta_delivery
|
|
'orig_to',
|
|
'postgrey_result',
|
|
'postgrey_reason',
|
|
'postgrey_delay',
|
|
'spam_score',
|
|
'spam_result',
|
|
'message_size',
|
|
'lmtp_id',
|
|
],
|
|
'field_types': [
|
|
{ 'type':'datetime', 'format': '%Y-%m-%d %H:%M:%S' },# connect_time
|
|
'text/plain', # mta_connection.service
|
|
'text/email', # sasl_username
|
|
'text/plain', # remote_host
|
|
'text/plain', # remote_ip
|
|
'text/email', # envelope_from
|
|
'text/plain', # disposition
|
|
'text/plain', # spf_result
|
|
'text/plain', # dkim_result
|
|
'text/plain', # dkim_result
|
|
'text/plain', # dmarc_result
|
|
'text/plain', # dmarc_reason
|
|
'text/plain', # message_id
|
|
'text/plain', # failure_info
|
|
'text/email', # orig_to
|
|
'text/plain', # postgrey_result
|
|
'text/plain', # postgrey_reason
|
|
{ 'type':'time/span', 'unit':'s' }, # postgrey_delay
|
|
{ 'type':'decimal', 'places':2 }, # spam_score
|
|
'text/plain', # spam_result
|
|
'number/size', # message_size
|
|
'text/plain', # lmtp_id
|
|
],
|
|
'items': []
|
|
}
|
|
|
|
for row in c.execute(select_2 + limit, {
|
|
'user_id': user_id,
|
|
'start_date': ts.start,
|
|
'end_date': ts.end
|
|
}):
|
|
v = []
|
|
for key in received_mail['fields']:
|
|
if key == 'lmtp_id':
|
|
# Extract the LMTP ID from delivery info, which looks
|
|
# like:
|
|
#
|
|
# "250 2.0.0 <user@domain.tld> oPHmBDvTaWA7UwAAlWWVsw
|
|
# Saved"
|
|
#
|
|
# When we know the LMTP ID, we can get the message
|
|
# headers using doveadm, like this:
|
|
#
|
|
# "/usr/bin/doveadm fetch -u "user@domain.tld" hdr
|
|
# HEADER received "LMTP id oPHmBDvTaWA7UwAAlWWVsw"
|
|
#
|
|
delivery_info = row['delivery_info']
|
|
valid = False
|
|
if delivery_info:
|
|
parts = delivery_info.split(' ')
|
|
if parts[0]=='250' and parts[1]=='2.0.0':
|
|
v.append(parts[-2])
|
|
valid = True
|
|
if not valid:
|
|
v.append(None)
|
|
|
|
else:
|
|
v.append(row[key])
|
|
received_mail['items'].append(v)
|
|
|
|
|
|
#
|
|
# IMAP connections by disposition, by remote host
|
|
# Disposition
|
|
# Remote host
|
|
# Count
|
|
# In bytes (sum)
|
|
# Out bytes (sum)
|
|
# % of total
|
|
#
|
|
|
|
imap_conn_summary = {
|
|
'start': ts.start,
|
|
'end': ts.end,
|
|
'y': 'IMAP connection summary by host and disposition',
|
|
'fields': [
|
|
'count',
|
|
'total',
|
|
'remote_host',
|
|
'disposition',
|
|
'first_connection_time',
|
|
'last_connection_time',
|
|
'in_bytes',
|
|
'out_bytes',
|
|
],
|
|
'field_types': [
|
|
'number', # count
|
|
{ 'type': 'number/percent', 'places': 1 }, # total
|
|
'text/plain', # remote_host
|
|
'text/plain', # disposition
|
|
{ 'type':'datetime', 'format': ts.parsefmt }, # first_conn_time
|
|
{ 'type':'datetime', 'format': ts.parsefmt }, # last_conn_time
|
|
'number/size', # in_bytes,
|
|
'number/size', # out_bytes,
|
|
],
|
|
'items': []
|
|
}
|
|
|
|
count_field_idx = 0
|
|
total_field_idx = 1
|
|
total = 0
|
|
for row in c.execute(select_3 + limit, {
|
|
'user_id': user_id,
|
|
'start_date': ts.start,
|
|
'end_date': ts.end
|
|
}):
|
|
v = []
|
|
for key in imap_conn_summary['fields']:
|
|
if key=='count':
|
|
total += row[key]
|
|
if key!='total':
|
|
v.append(row[key])
|
|
|
|
imap_conn_summary['items'].append(v)
|
|
|
|
for v in imap_conn_summary['items']:
|
|
v.insert(total_field_idx, v[count_field_idx] / total)
|
|
|
|
return {
|
|
'sent_mail': sent_mail,
|
|
'received_mail': received_mail,
|
|
'imap_conn_summary': imap_conn_summary
|
|
}
|