Skip to content

Commit

Permalink
Track last IMAP / POP3 logins for users, and display in UI
Browse files Browse the repository at this point in the history
  • Loading branch information
jcameron committed Jun 13, 2011
1 parent 89f53d7 commit 0cb68df
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -1297,3 +1297,4 @@ The default DNS TTL for one or more domains can now be changed via the --ttl fla
---- Changes since 3.86 ----
Updated the Roundcube script installer to version 0.5.3, PHPprojekt to 6.0.6, phpMyAdmin to 3.4.2, phpMyFAQ to 2.6.17, Movable Type to 5.11, Instiki to 0.19.2, and SugarCRM to 6.2.0.
Added the new API command set-global-feature to turn features and plugins on and off from the command line.
The last IMAP, POP3 and SMTP logins for mailbox users are now tracked by Virtualmin, and can be viewed on the Edit Mailbox page and in the output from the list-users API command.
3 changes: 0 additions & 3 deletions IDEAS
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,3 @@
- https://www.virtualmin.com/node/18405
- CHANGELOG
- backup/restore

- Track last-login time for mailbox users (IMAP or FTP)
- http://www.virtualmin.com/node/18417
2 changes: 2 additions & 0 deletions collectinfo.pl
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ package virtual_server;
# Update IP list cache
&build_local_ip_list();

# Update DB of per-user last login times
&update_last_login_times();
14 changes: 14 additions & 0 deletions edit_user.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,20 @@ if ($hasspam) {
2, \@tds);
}

# Show most recent logins
if ($hasemail && !$in{'new'}) {
# XXX help page
$ll = &get_last_login_time($user->{'user'});
@grid = ( );
foreach $k (keys %$ll) {
push(@grid, $text{'user_lastlogin_'.$k},
&make_date($ll->{$k}));
}
print &ui_table_row(&hlink($text{'user_lastlogin'}, "lastlogin"),
@grid ? &ui_grid_table(\@grid, 2, 50)
: $text{'user_lastlogin_never'});
}

if ($hasemail) {
print &ui_hidden_table_end("table2a");
}
Expand Down
89 changes: 89 additions & 0 deletions feature-mail.pl
Original file line number Diff line number Diff line change
Expand Up @@ -4907,6 +4907,95 @@ sub same_dn
return lc($dn0) eq lc($dn1);
}

# update_last_login_times()
# Scans the mail log and updates the last time a user logs in via IMAP, POP3
# or SMTP.
sub update_last_login_times
{
# Find the mail log
my $maillog = $config{'bw_maillog'};
$maillog = &get_mail_log() if ($maillog eq "auto");
return 0 if (!$maillog);

# Seek to the last position
&lock_file($mail_login_file);
my @st = stat($maillog);
my $lastpost;
my %logins;
&read_file($mail_login_file, \%logins);
$lastpos = $logins{'lastpos'} || $st[7];
if ($lastpos > $st[7]) {
# Off end .. file has probably been rotated
$lastpos = 0;
}
open(MAILLOG, $maillog);
seek(MAILLOG, $lastpos, 0);
my $now = time();
my @tm = localtime($now);
while(<MAILLOG>) {
s/\r|\n//g;

# Remove Solaris extra part like [ID 197553 mail.info]
s/\[ID\s+\d+\s+\S+\]\s+//;

if (/^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+dovecot:\s+(pop3|imap)-login:\s+Login:\s+user=<([^>]+)>/) {
# POP3 or IMAP login with dovecot
my $ltime;
eval { $ltime = timelocal($5, $4, $3, $2,
$apache_mmap{lc($1)}, $tm[5]); };
if (!$ltime || $ltime > $now+(24*60*60)) {
# Must have been last year!
eval { $ltime = timelocal($5, $4, $3, $2,
$apache_mmap{lc($1)}, $tm[5]-1); };
}
&add_last_login_time(\%logins, $ltime, $7, $8);
}
elsif (/^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+.*sasl_username=([^ ,]+)/) {
# Postfix SMTP
my $ltime;
eval { $ltime = timelocal($5, $4, $3, $2,
$apache_mmap{lc($1)}, $tm[5]); };
if (!$ltime || $ltime > $now+(24*60*60)) {
# Must have been last year!
eval { $ltime = timelocal($5, $4, $3, $2,
$apache_mmap{lc($1)}, $tm[5]-1); };
}
&add_last_login_time(\%logins, $ltime, 'smtp', $7);
}
}
close(MAILLOG);
@st = stat($maillog);
$logins{'lastpos'} = $st[7];
&write_file($mail_login_file, \%logins);
&unlock_file($mail_login_file);
return 1;
}

# add_last_login_time(&logins, time, type, username)
# Add to the hash of login types for some user
sub add_last_login_time
{
my ($logins, $ltime, $ltype, $user) = @_;
my %curr = map { split(/=/, $_) } split(/\s+/, $logins->{$user});
$curr{$ltype} = $ltime;
$logins->{$user} = join(" ", map { $_."=".$curr{$_} } keys %curr);
}

# get_last_login_time(username)
# Returns a hash ref of last login types to times for a user
sub get_last_login_time
{
my ($user) = @_;
my %logins;
&read_file_cached($mail_login_file, \%logins);
if ($logins{$user}) {
return { map { split(/=/, $_) } split(/\s+/, $logins{$user}) };
}
else {
return undef;
}
}

$done_feature_script{'mail'} = 1;

1;
Expand Down
6 changes: 6 additions & 0 deletions help/lastlogin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<header>Last email login</header>

This field shows the last login times via IMAP, POP3 or SMTP detected by
Virtualmin for this user. <p>

<footer>
5 changes: 5 additions & 0 deletions lang/en
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,11 @@ user_emysqlprov=Failed to update user on services host : $1
user_emysqllist=Failed to fetch users from services host : $1
user_emysqldelete=Failed to delete user on services host : $1
user_emysqlprovips=Failed to update IP addresses on services host : $1
user_lastlogin=Last email login
user_lastlogin_pop3=POP3 (fetching email)
user_lastlogin_imap=IMAP (fetching email)
user_lastlogin_smtp=SMTP (sending email)
user_lastlogin_never=No logins recorded yet

aliases_ecannot=You are not allowed to edit aliases in this domain
aliases_title=Mail Aliases
Expand Down
7 changes: 7 additions & 0 deletions list-users.pl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ package virtual_server;
!$d->{'spam'} ? "Disabled for domain" :
$u->{'nospam'} ? "No" : "Yes","\n";
}
$ll = &get_last_login_time($u->{'user'});
if ($ll) {
print " Last logins: ",
join(", ",
map { $_." ".&make_date($ll->{$_}) }
keys %$ll),"\n";
}
@dblist = ( );
foreach $db (@{$u->{'dbs'}}) {
push(@dblist, $db->{'name'}." ($db->{'type'})");
Expand Down
23 changes: 23 additions & 0 deletions virtual-server-lib-funcs.pl
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,19 @@ sub modify_user
}
}

# Update the last logins file
if ($_[0]->{'user'} ne $_[1]->{'user'}) {
&lock_file($mail_login_file);
my %logins;
&read_file_cached($mail_login_file, \%logins);
if ($logins{$_[1]->{'user'}}) {
$logins{$_[1]->{'user'}} = $logins{$_[1]->{'user'}};
delete($logins{$_[1]->{'user'}});
&write_file($mail_login_file, \%logins);
}
&unlock_file($mail_login_file);
}

# Clear quota cache for this user
if (defined(&clear_lookup_domain_cache) && $_[2]) {
&clear_lookup_domain_cache($_[2], $_[0]);
Expand Down Expand Up @@ -1949,6 +1962,16 @@ sub delete_user
# Update cache of existing usernames
$unix_user{&escape_alias($_[0]->{'user'})} = 0;

# Delete from last logins file
&lock_file($mail_login_file);
my %logins;
&read_file_cached($mail_login_file, \%logins);
if ($logins{$_[0]->{'user'}}) {
delete($logins{$_[0]->{'user'}});
&write_file($mail_login_file, \%logins);
}
&unlock_file($mail_login_file);

# Create everyone file for domain, minus the user
if ($_[1] && $_[1]->{'mail'}) {
&create_everyone_file($_[1]);
Expand Down
2 changes: 2 additions & 0 deletions virtual-server-lib.pl
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@
$procmail_log_cache = "$ENV{'WEBMIN_VAR'}/procmail.cache";
$procmail_log_times = "$ENV{'WEBMIN_VAR'}/procmail.times";

$mail_login_file = "$module_config_directory/mailbox-logins";

@newfeatures_dirs = ( "$module_root_directory/newfeatures-all",
$virtualmin_pro ? "$module_root_directory/newfeatures-pro"
: "$module_root_directory/newfeatures-gpl" );
Expand Down

0 comments on commit 0cb68df

Please sign in to comment.