[Dovecot] dovecot.index mtime
Is there anyway I can get Dovecot to update mtime on dovecot.index everytime a user successfully checks there email? Or perhaps there is a better way to do what I want here.
I am working on a script that I want to check when the last time a user checked there email account. If a user has not checked it in say 6 months I want to automatically suspend the account through the MTA. If they start checking it again I also want to automatically reinstate it. What I thought was a good way was to look at the mtime on dovecot.index but a user can be checking there email daily but if they do not receive a new message in say 6 months the mtime will date back to 6 months still. Ran into this with an email address used by a fax machine.
So is there a better way with dovecot to determine this? I am using dovecot-1.0.rc29 right now.
Matt
On Sat, 10 Nov 2007, Matt wrote:
Is there anyway I can get Dovecot to update mtime on dovecot.index everytime a user successfully checks there email? Or perhaps there is a better way to do what I want here.
I am working on a script that I want to check when the last time a user checked there email account. If a user has not checked it in say 6 months I want to automatically suspend the account through the MTA. If they start checking it again I also want to automatically reinstate it. What I thought was a good way was to look at the mtime on dovecot.index but a user can be checking there email daily but if they do not receive a new message in say 6 months the mtime will date back to 6 months still. Ran into this with an email address used by a fax machine.
So is there a better way with dovecot to determine this? I am using dovecot-1.0.rc29 right now.
[From an email I wrote in October]
You can do this (and many other things) through Post-Login Scripting: http://wiki.dovecot.org/PostLoginScripting
See in particular “Last-login tracking”: http://wiki.dovecot.org/PostLoginScripting#line-20
[...and for your situation:]
I think I'd avoid actually disabling the account through the MTA, especially if it's for security considerations and your accounts have shell access. But it wouldn't be that hard:
In dovecot.conf, set your IMAP login script as follows:
protocol imap { # ... mail_executable = /full/path/to/loginscript.sh
Then, use the attached loginscript.sh. (and chmod +x it)
Tested with 1.0.5 under Gentoo. The 'stat' and 'date' commands might need alteration depending on your particular flavor of O/S.
(I also realized after writing it that you probably meant to disable mail delivery, not IMAP access. But, it was a fun exercise.)
In that case, all you need in loginscript.sh is:
#!/bin/sh touch $HOME/.dovecot-lastlogin exec /usr/libexec/dovecot/imap
Then run a cron job to disable delivery through your MTA of choice.
Best, Ben
I think I'd avoid actually disabling the account through the MTA, especially if it's for security considerations and your accounts have shell access. But it wouldn't be that hard:
My actual goal is to reduce load on the server. These are simply email users and have no shell or any other access. If there not using the account I do not want Exim wasting resources virus scanning, spam scanning(spamd) and storing messages for them.
I want to add all email users that have not checked email through pop3 or imap in ~6 months to /etc/virtual/suspended_email. Exim will reject messages to these accounts with error message: "Account suspended due to inactivity". I do not want to suspend pop3 or imap access in fact if they start using the account again I want to automatically re-enable. Likely will run a script in cron.daily to check all this.
I think that better explains what I am after.
Thanks.
Matt
[Oops. Sorry for the delay.]
On Sun, 11 Nov 2007, Matt wrote:
I think I'd avoid actually disabling the account through the MTA, especially if it's for security considerations and your accounts have shell access. But it wouldn't be that hard:
My actual goal is to reduce load on the server. These are simply email users and have no shell or any other access. If there not using the account I do not want Exim wasting resources virus scanning, spam scanning(spamd) and storing messages for them.
I want to add all email users that have not checked email through pop3 or imap in ~6 months to /etc/virtual/suspended_email. Exim will reject messages to these accounts with error message: "Account suspended due to inactivity". I do not want to suspend pop3 or imap access in fact if they start using the account again I want to automatically re-enable. Likely will run a script in cron.daily to check all this.
I think that better explains what I am after.
Yes. And it all sounds pretty reasonable.
I guess the issues I still think are important:
- You shouldn't use dovecot.index as your marker file.
As mentioned before, dovecot's deliver _might_ update it, and special-casing its existence might be annoying. Plus, its location is harder to determine than either the user's home or some chosen central directory.
e.g. Running env | sort > $HOME/.dovecot-env in the login script, the only place I see mention of an index file is: MAIL=maildir:/home/bhaskell/Maildir:INBOX=/var/mail/.bhaskell:INDEX=/var/mail/.bhaskell/dovecot
And even after IMAP'ing in and FETCHing some message headers, there's no dovecot.index in /var/mail/.bhaskell/dovecot.
[ed. I think it was a problem with permissions actually. I change my config quite a bit to test various dovecot settings. Even so, the problem remains. dovecot.index's location can even vary by namespace.]
- I would use a centralized directory.
I've written cron jobs in the past to do stuff with files in home directories. e.g.
getent passwd | cut -d: -f6 | sort | uniq |
xargs -iI find I -maxdepth 1 -name [...etc.]
The alternative is much simpler:
cd /var/lastlogins for l in * do [...etc.]
- I would use the login script to inform users that they'd been disabled.
If they're logging in through IMAP, you can [ALERT] them to the fact, so they don't wonder about a gap in mail delivery.
Best, Ben
On Sun, 11 Nov 2007, Matt wrote:
I think I'd avoid actually disabling the account through the MTA, especially if it's for security considerations and your accounts have shell access. But it wouldn't be that hard:
My actual goal is to reduce load on the server. These are simply email users and have no shell or any other access. If there not using the account I do not want Exim wasting resources virus scanning, spam scanning(spamd) and storing messages for them.
I want to add all email users that have not checked email through pop3 or imap in ~6 months to /etc/virtual/suspended_email. Exim will reject messages to these accounts with error message: "Account suspended due to inactivity". I do not want to suspend pop3 or imap access in fact if they start using the account again I want to automatically re-enable. Likely will run a script in cron.daily to check all this.
I think that better explains what I am after.
Yes. And it all sounds pretty reasonable.
I guess the issues I still think are important:
- You shouldn't use dovecot.index as your marker file.
I gave up trying to use dovecot.index. Instead I look at each users ./new directory. If there are any messages over 6 months old I put there email address in file that tells exim to reject any new messages. Run the script in cron.daily so if they start using it again it will reactivate in 24 hours or less. Having to look at all messages in the ./new folders is significantly more CPU and I/O load though. Having run the script last night it seems to work fine. Audited some of email addresses it added to be blocked and none look out of place.
Matt
On Wed, 14 Nov 2007, Matt wrote:
[...]
I guess the issues I still think are important:
- You shouldn't use dovecot.index as your marker file.
I gave up trying to use dovecot.index. Instead I look at each users ./new directory. If there are any messages over 6 months old I put there email address in file that tells exim to reject any new messages. Run the script in cron.daily so if they start using it again it will reactivate in 24 hours or less. Having to look at all messages in the ./new folders is significantly more CPU and I/O load though. Having run the script last night it seems to work fine. Audited some of email addresses it added to be blocked and none look out of place.
Glad to hear you've got something that's working for you.
For what it's worth, I'd look at the newest message in the ./cur folder, rather than the oldest in the ./new. (You want the last time the user looked at a message, not the first time the user got mail after being logged in.) The actual difference in times probably isn't an important detail when you're talking O(6 months).
But, the benefit is that you can get the timestamp for the last time the user read/deleted a message in ./cur simply by checking the change time of the ./cur directory. (rather than going through all its files)
e.g. $ stat -c '%z (=%Z)' ~/Maildir/cur 2007-11-14 15:26:09.000000000 -0500 (=1195071992) $ stat -c '%z (=%Z)' ~/Maildir/cur/* | sort | tail -n 1 2007-11-14 15:26:09.000000000 -0500 (=1195071992)
Should make it fast enough to run in cron.hourly, depending on your number of users/mailboxes.
Best, Ben
I guess the issues I still think are important:
- You shouldn't use dovecot.index as your marker file.
I gave up trying to use dovecot.index. Instead I look at each users ./new directory. If there are any messages over 6 months old I put there email address in file that tells exim to reject any new messages. Run the script in cron.daily so if they start using it again it will reactivate in 24 hours or less. Having to look at all messages in the ./new folders is significantly more CPU and I/O load though. Having run the script last night it seems to work fine. Audited some of email addresses it added to be blocked and none look out of place.
Glad to hear you've got something that's working for you.
For what it's worth, I'd look at the newest message in the ./cur folder, rather than the oldest in the ./new. (You want the last time the user
But most of my user POP3 and do not leave messages on server. I just check if there is a message over 6 months old in new.
looked at a message, not the first time the user got mail after being logged in.) The actual difference in times probably isn't an important detail when you're talking O(6 months).
But, the benefit is that you can get the timestamp for the last time the user read/deleted a message in ./cur simply by checking the change time of the ./cur directory. (rather than going through all its files)
Light bulb. Wish I had thought of that.
e.g. $ stat -c '%z (=%Z)' ~/Maildir/cur 2007-11-14 15:26:09.000000000 -0500 (=1195071992) $ stat -c '%z (=%Z)' ~/Maildir/cur/* | sort | tail -n 1 2007-11-14 15:26:09.000000000 -0500 (=1195071992)
Should make it fast enough to run in cron.hourly, depending on your number of users/mailboxes.
I am going to have to try this. I used find with -mtime to find all messages over 6 months old in all the ./new directorys. If this way works it would be far more efficient.
Thanks.
Matt
I guess the issues I still think are important:
- You shouldn't use dovecot.index as your marker file.
I gave up trying to use dovecot.index. Instead I look at each users ./new directory. If there are any messages over 6 months old I put there email address in file that tells exim to reject any new messages. Run the script in cron.daily so if they start using it again it will reactivate in 24 hours or less. Having to look at all messages in the ./new folders is significantly more CPU and I/O load though. Having run the script last night it seems to work fine. Audited some of email addresses it added to be blocked and none look out of place.
Glad to hear you've got something that's working for you.
For what it's worth, I'd look at the newest message in the ./cur folder, rather than the oldest in the ./new. (You want the last time the user
But most of my user POP3 and do not leave messages on server. I just check if there is a message over 6 months old in new.
looked at a message, not the first time the user got mail after being logged in.) The actual difference in times probably isn't an important detail when you're talking O(6 months).
But, the benefit is that you can get the timestamp for the last time the user read/deleted a message in ./cur simply by checking the change time of the ./cur directory. (rather than going through all its files)
Light bulb. Wish I had thought of that.
e.g. $ stat -c '%z (=%Z)' ~/Maildir/cur 2007-11-14 15:26:09.000000000 -0500 (=1195071992) $ stat -c '%z (=%Z)' ~/Maildir/cur/* | sort | tail -n 1 2007-11-14 15:26:09.000000000 -0500 (=1195071992)
Should make it fast enough to run in cron.hourly, depending on your number of users/mailboxes.
I am going to have to try this. I used find with -mtime to find all messages over 6 months old in all the ./new directorys. If this way works it would be far more efficient.
A flaw though. If the account is checked daily but never receives any new mail (such as the email account that goes to my fax machine and I am sure there are a few others) the ctime on the cur will not be updated. It would not automagically start working again either since it cannot receive new messages when suspended and thus cannot update ./cur. Someone just might email my fax machine some day rather then call.
This might be a good preliminary check then only then double check that there are messages in ./new. Of course it makes my simple perl script a little more involved. Is there a low overhead way to check that ./new is not empty?
Another annoying thing about Exim is it checks if a user is over quotta after it has accepted a message. It then trys to write the message to the users ./new directory and at that point generates a bounce. Meaning over quotta accounts generate alot of bounces to junk mail that have invalid source email addresses from the start. One of the reasons I want to auto suspend unused email accounts.
Thanks.
Matt
On Wed, 14 Nov 2007, Matt wrote:
Another annoying thing about Exim is it checks if a user is over quotta after it has accepted a message. It then trys to write the message to the users ./new directory and at that point generates a bounce. Meaning over quotta accounts generate alot of bounces to junk mail that have invalid source email addresses from the start. One of the reasons I want to auto suspend unused email accounts.
Maybe telling Exim to deliver using the dovecot local delivery agent (called "deliver") would fix that issue?
-- Asheesh.
-- "Besides, I think [Slackware] sounds better than 'Microsoft,' don't you?" (By Patrick Volkerding)
On Wed, 14 Nov 2007, Matt wrote:
[...]
For what it's worth, I'd look at the newest message in the ./cur folder, rather than the oldest in the ./new. (You want the last time the user
But most of my user POP3 and do not leave messages on server. I just check if there is a message over 6 months old in new.
Even in POP3, dovecot moves RETR'ieved messages into ./cur as soon as they're read, thus changing its timestamp.
[...]
But, the benefit is that you can get the timestamp for the last time the user read/deleted a message in ./cur simply by checking the change time of the ./cur directory. (rather than going through all its files)
Light bulb. Wish I had thought of that.
:-)
[...]
Should make it fast enough to run in cron.hourly, depending on your number of users/mailboxes.
I am going to have to try this. I used find with -mtime to find all messages over 6 months old in all the ./new directorys. If this way works it would be far more efficient.
A flaw though. If the account is checked daily but never receives any new mail (such as the email account that goes to my fax machine and I am sure there are a few others) the ctime on the cur will not be updated. It would not automagically start working again either since it cannot receive new messages when suspended and thus cannot update ./cur. Someone just might email my fax machine some day rather then call.
This might be a good preliminary check then only then double check that there are messages in ./new. Of course it makes my simple perl script a little more involved. Is there a low overhead way to check that ./new is not empty?
Yes, that's definitely a case I didn't consider. Here's how I'd implement an empty-directory check in Perl:
sub empty { # Use 'while' instead of 'for' to avoid reading all the filenames. # basic idea: # return 1 unless directory contains something other than '.' or '..' # In a cron script, I think I'd treat un-openable dirs as empty ***
my $dirname = shift;
opendir my $d, $dirname or return 1; # *** hence the '1'
local $_;
!/^\.\.?$/ and return 0 while $_ = readdir $d;
1;
}
sub oldest_msg { # finds oldest file in a dir and returns its ctime # un-openable dirs return 'now' my $dirname = shift; opendir my $d, $dirname or return time; my $min = time; for (readdir $d) { # here we need to read them all to find the oldest my $ctime = (stat)[10]; next if $ctime > $min; $min = $ctime; } $min; }
Then you have roughly the following logic:
my $longago = time - 86_400 * 180; # $longago = 180 days ago if ( (stat 'cur')[10] > $longago) # cur's ctime isn't long ago or empty('new') # or there are no new messages or oldest_msg('new') > $longago # or the oldest isn't long ago ) { # then don't disable it # or switch the 'if' to 'unless', and this is where you disable it }
# ...Perl's definitely my favorite language, if it's not obvious...
[...]
Thanks.
No prob.
Best, Ben
On Sat, 2007-11-10 at 18:12 -0600, Matt wrote:
Is there anyway I can get Dovecot to update mtime on dovecot.index everytime a user successfully checks there email?
Postlogin scripting would work like Benjamin said. Another possibility would be to check atime if you haven't disabled atime updates.
Is there anyway I can get Dovecot to update mtime on dovecot.index everytime a user successfully checks there email?
Postlogin scripting would work like Benjamin said. Another possibility would be to check atime if you haven't disabled atime updates.
atime is updated everytime I do a backup which is once a week.
protocol imap { # ... mail_executable = /full/path/to/loginscript.sh
I imagine I would need to put this in pop3 section as well?
#!/bin/sh touch $HOME/.dovecot-lastlogin exec /usr/libexec/dovecot/imap
Can I just touch the dovecot.index instead?
Also, I am bit confused. Why must the script exec imap? I imagine $HOME will contain the path to the home directory of the pop3/imap user that just successfully logged in?
THanks
Matt
On Sun, 11 Nov 2007, Matt wrote:
Is there anyway I can get Dovecot to update mtime on dovecot.index everytime a user successfully checks there email?
Postlogin scripting would work like Benjamin said. Another possibility would be to check atime if you haven't disabled atime updates.
atime is updated everytime I do a backup which is once a week.
protocol imap { # ... mail_executable = /full/path/to/loginscript.sh
I imagine I would need to put this in pop3 section as well?
Yes. I would do it via symlinks, then have loginscript.sh exec the appropriate dovecot process based on how it was called.
e.g.:
protocol imap { mail_executable = /full/path/to/imap ... protocol pop3 { mail_executable = /full/path/to/pop3
with imap and pop3 each a symlink to loginscript.sh. Then, replace the
exec in loginscript.sh with:
exec /usr/libexec/dovecot/basename $0
#!/bin/sh touch $HOME/.dovecot-lastlogin exec /usr/libexec/dovecot/imap
Can I just touch the dovecot.index instead?
Maybe. Not sure what environment variable holds its location, though. And I'm not 100% sure it always exists or whether you'd have to special-case the first-time login. (Will 'touch' creating a zero-length index cause trouble for dovecot? I suspect not.) It might also be nice to just have it as a separate file with an obvious name, rather than relying on nothing else changing dovecot.index. (I think, for example, you can have dovecot's deliver update dovecot.index... or maybe that was just on someone's wishlist.)
Also, I am bit confused. Why must the script exec imap?
mail_executable tells dovecot what to run after a successful login. Usually that's 'imap'. So, when you replace imap with loginscript.sh, you need to be sure that it eventually turns into 'imap', which handles the actual IMAP connection after your script makes its tweaks.
I imagine $HOME will contain the path to the home directory of the pop3/imap user that just successfully logged in?
Yes.
Best, Ben
"BH" == Benjamin R Haskell dovecot@benizi.com writes:
>> Can I just touch the dovecot.index instead?
BH> Maybe. Not sure what environment variable holds its location,
BH> though. And I'm not 100% sure it always exists or whether you'd
BH> have to special-case the first-time login. (Will 'touch' creating
BH> a zero-length index cause trouble for dovecot? I suspect not.)
Also be aware that the script is not being run with the uid of the user - it is /usr/libexec/dovecot/imap that drops privs - so touching the index file if it does not already exist may cause problems later when the user process wants to update the index file.
If you have sessreg from the X11 distribution you could also try:
#!/bin/sh if test -z "$DUMP_CAPABILITY"; then /usr/bin/sessreg -a -L /var/log/lastlog -u none -w none -l imap -h "$IP" "$USER" fi exec /usr/libexec/dovecot/imap
to put an entry into /var/log/lastlog (though note you need two scripts for both IMAP and POP logins).
>> Also, I am bit confused. Why must the script exec imap?
It doesn't _have_ to. It's a micro-optimisation. If the script did not exec the user imap process then a /bin/sh process would be sitting around waiting for the user imap process to exit and when it did then the /bin/sh process would itself just exit.
On Mon, 12 Nov 2007, pod wrote:
"BH" == Benjamin R Haskell dovecot@benizi.com writes:
Can I just touch the dovecot.index instead?
BH> Maybe. Not sure what environment variable holds its location, BH> though. And I'm not 100% sure it always exists or whether you'd BH> have to special-case the first-time login. (Will 'touch' creating BH> a zero-length index cause trouble for dovecot? I suspect not.)
Also be aware that the script is not being run with the uid of the user - it is /usr/libexec/dovecot/imap that drops privs - so touching the index file if it does not already exist may cause problems later when the user process wants to update the index file.
Thanks for pointing that out. I'd been testing by running: dovecot --exec-mail imap which runs as the current user. (Didn't realize that.)
You could also use mail_drop_priv_before_exec in dovecot.conf (with the caveats in the comments):
# Drop all privileges before exec()ing the mail process. This is mostly # meant for debugging, otherwise you don't get core dumps. It could be a small # security risk if you use single UID for multiple users, as the users could # ptrace() each others processes then. mail_drop_priv_before_exec = yes
Or, probably better, you could put the last login information in a common directory, rather than the user's home directory (which would also make the cron job easier, I suspect).
If you have sessreg from the X11 distribution you could also try:
#!/bin/sh if test -z "$DUMP_CAPABILITY"; then /usr/bin/sessreg -a -L /var/log/lastlog -u none -w none -l imap -h "$IP" "$USER" fi exec /usr/libexec/dovecot/imap
to put an entry into /var/log/lastlog (though note you need two scripts for both IMAP and POP logins).
Since you're not doing anything protocol-specific (like echo "* OK [ALERT] Blah"), you could use the symlink trick to only require one script:
ln -s /path/script /path/imap ln -s /path/script /path/pop3
Then replace the exec with:
exec /usr/libexec/dovecot/basename $0
Also, I am bit confused. Why must the script exec imap?
It doesn't _have_ to. It's a micro-optimisation. If the script did not exec the user imap process then a /bin/sh process would be sitting around waiting for the user imap process to exit and when it did then the /bin/sh process would itself just exit.
Ah. In my other email I took the question to mean "Why must the script run imap?", but I bet this is what Matt meant.
Best, Ben
"BH" == Benjamin R Haskell dovecot@benizi.com writes:
BH> Since you're not doing anything protocol-specific (like echo "* OK
BH> [ALERT] Blah"), you could use the symlink trick to only require
BH> one script:
BH> ln -s /path/script /path/imap
BH> ln -s /path/script /path/pop3
BH> Then replace the exec with:
BH> exec /usr/libexec/dovecot/`basename $0`
I was thinking about the "-l imap" in the sessreg invocation but clearly
one could use basename $0
to handle that too. My preference is to avoid
scripts whose behaviour is determined by the name by which they were
invoked.
Can I just touch the dovecot.index instead?
BH> Maybe. Not sure what environment variable holds its location, BH> though. And I'm not 100% sure it always exists or whether you'd BH> have to special-case the first-time login. (Will 'touch' creating BH> a zero-length index cause trouble for dovecot? I suspect not.)
Also be aware that the script is not being run with the uid of the user - it is /usr/libexec/dovecot/imap that drops privs - so touching the index file if it does not already exist may cause problems later when the user process wants to update the index file.
What if the script only touched the dovecot.index if it exists? That would likely not be too difficult to test for with shell script.
Matt
If you have sessreg from the X11 distribution you could also try:
#!/bin/sh if test -z "$DUMP_CAPABILITY"; then /usr/bin/sessreg -a -L /var/log/lastlog -u none -w none -l imap -h "$IP" "$USER" fi exec /usr/libexec/dovecot/imap
to put an entry into /var/log/lastlog (though note you need two scripts for both IMAP and POP logins).
Also, I am bit confused. Why must the script exec imap?
It doesn't _have_ to. It's a micro-optimisation. If the script did not exec the user imap process then a /bin/sh process would be sitting around waiting for the user imap process to exit and when it did then the /bin/sh process would itself just exit.
"M" == Matt lm7812@gmail.com writes:
M> What if the script only touched the dovecot.index if it exists?
M> That would likely not be too difficult to test for with shell
M> script.
Might be tricky to do in shell without a race. On the other hand maybe a race in this case is not significant. On the other hand it's possible that a running user imap process uses timestamps on the index file to determine whether it needs to update its idea of the content and gratuitously changing it on connect may cause already running processes to do more work that strictly necessary. Haven't looked at the actual code to asses the credibility of this though.
On the other hand I've run out of hands now :)
participants (5)
-
Asheesh Laroia
-
Benjamin R. Haskell
-
Matt
-
pod
-
Timo Sirainen