LMTP mods for quota handling
"Johan Reinalda" <[email protected]> Wed, 9 Apr 2003 14:28:07 -0700
| Newsgroups | gmane.mail.procmail.devel |
|---|---|
| Message-ID | <[email protected]> |
This is a multi-part message in MIME format.
------=_NextPart_000_0023_01C2FEA4.3D352560
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
Hello,
here at Thunderbird, the American Graduate School of International
Management, we have been using Procmail in LMTP mode for about 10 months to
handle local delivery of all our student email. Mailbox access is handled by
the UW imap/pop3 daemons.
We have made some mods to change mailbox spool file location and add quota
management. The mods are explained below and attached with diffs relative to
the v3.22 source code.
Feel free to use this as you wish, your mileage may vary :-)
If you have questions, please send to me directly, as I do not subscribe to
this list.
Sincerely,
Johan Reinalda
Director, Network Engineering and Admin.
Thunderbird, the American Graduate School
Glendale, AZ
www.thunderbird.edu
----------------------------------
Background:
We store both the spool file (typically /var/spool/mail/username or such)
and the
mbox file in the user's home directory, for simplicity and to ease quota
handling. Users have no shell level access, so the only things that can
exist in their home dir is mail related files. We also do not allow direct
imap access,
which simplifies things greatly.
Procmail sources need to be modified to store new incoming in the
user's home directory, instead of the default spool directory. This allows
us to get around potential number of file limitations. (Note: This mod could
be done with a global /etc/procmailrc setting (DEFAULT=$HOME/$LOGNAME), but
this creates an empty file at the default mail spool dir /var/spool/mail for
each user :-( )
We also added some custom quota handling to the LMTP portion of procmail. We
use the LMTP interface for the sendmail LDA, since this allows for better
return notification then the system return value otherwize used.
Where the mods are:
* config.h - enable LMTP support; straight forward, see diff
* src/authenticate.c - change routine that gets user mail spool file to
point to user home directory plus username; also straight forward, see diff
* src/lmtp.c - add support for quotas in the RCPT handling. If a user is
over quota (ie. there is more space in use then allowed, the message is
denied with a hard error, ie a 500 level error code)
Several functions in src/lmtp.c handle the quota:
The function getuserquota(username) reads the file
/var/spool/mail/quotas.txt (this filename is set via a #define), and looks
for entries of the form <username>=<maxbytes>. There should be no spaces in
this format. Lines starting with # are ignored. The username _DEFAULT_ is
special, and defines the default quota for all users that are not explicitly
defined in the file. A quota value of 0 turns off quota checking. If
/var/spool/mail/quotas.txt does not exists, 0 is returned, in effect turning
off quotas for all users. Eg. a quotas.txt file could look like this:
#this is the quota file
_DEFAULT_=7500000
postmaster=0
root=0
johan=0
specialuser=12500000
The function bytesused(directory) recursively reads all normal files under
the given directory and returns the total byte count. Note that this can
cause problems if you have normal working files in a user's home directory
as well. We do not offer that service here.
Also, if you have things like the UW pop3/imap daemons, they write temp
files in this same directory.
This leaves the possibility that you get a false over-quota, since the byte
count can include the mbox file twice! This is handled better in
bytesused2() as seen in lmtp.c.diff-no-recurse
Note that this assume the only two files that can exist in the home
directory are the spool file 'username' and
the mbox file produced by the pop3/imap daemon
Also, note that the quota goes into effect *after* a message has been
delivered. Eg. We have a max msg size in sendmail of 5MB, and a default
mailbox quota of 7.5MB. If the user has 7.49MB, a new message of 5MB will be
accepted; any following message will be denied, since the mailbox size is
now 12.49MB.
------=_NextPart_000_0023_01C2FEA4.3D352560
Content-Type: application/octet-stream;
name="config.h.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="config.h.diff"
*** config.h.original Thu May 30 13:39:58 2002
--- config.h Wed May 29 16:29:17 2002
***************
*** 52,58 ****
is group writable or contained in a group writable home directory
if the group involved is the user's default group. */
! /*#define LMTP /* uncomment this if you
want to use procmail
as an LMTP (rfc2033) server, presumably for invocation by an MTA.
The file examples/local_procmail_lmtp.m4 contains info on how to
--- 52,58 ----
is group writable or contained in a group writable home directory
if the group involved is the user's default group. */
! #define LMTP /* uncomment this if you
want to use procmail
as an LMTP (rfc2033) server, presumably for invocation by an MTA.
The file examples/local_procmail_lmtp.m4 contains info on how to
------=_NextPart_000_0023_01C2FEA4.3D352560
Content-Type: application/octet-stream;
name="lmtp.c.diff"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="lmtp.c.diff"
*** lmtp.c.original Thu May 30 13:40:21 2002
--- lmtp.c Thu May 30 12:53:42 2002
***************
*** 22,27 ****
--- 22,49 ----
#include "from.h"
#include "lmtp.h"
=20
+ #include <dirent.h>
+ #include <sys/stat.h>
+ #define QUOTAFILE "/var/spool/mail/quotas.txt"
+ #define DEFAULTUSER "_DEFAULT_"
+ static unsigned long bytesused __P((char *));
+ static unsigned long getuserquota __P((char *));
+ /* define JKRDEBUG to turn on some custum debugging to stderr.
+ * you can test this with eg: mail.local -f root johan < testmsg
+ *
+ #define JKRDEBUG 1
+ */
+=20
+ /* This is a duplicate of the structure defined in authenticate.c
+ * We need it here to be able to reference the passwd struct - JKR
+ */
+ struct auth_identity
+ { const struct passwd*pw;
+ char*mbox;
+ int sock;
+ };
+=20
+=20
/*
=20
lhlo =3D> print stuff
***************
*** 241,246 ****
--- 263,269 ----
cNOOP[]=3D"NOOP";
const char*msg,*msgcmd;int flush=3D0,c,lmtp_state=3DS_START;long =
size=3D0;
auth_identity**rcpts,**lastrcpt,**currcpt;
+ unsigned long quota;
=20
pushfd(STDIN);overread=3D0;overlen=3D0;nliseol=3D1;
bufinit;ctopfd=3D-1; /* setup our output */
***************
*** 399,404 ****
--- 422,439 ----
free(path);
goto message;
}
+=20
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Mailbox =3D %s, dir =3D =
%s\n",mailbox,temp->pw->pw_dir);
+ #endif
+ if((quota=3Dgetuserquota(mailbox)) > 0) {
+ if(bytesused(temp->pw->pw_dir) > quota)
+ { msg=3D"550 5.2.2 Mailbox quota exceeded\r\n";
+ free(path);
+ goto message;
+ }
+ }
+=20
auth_copyid(*currcpt=3Dauth_newid(),temp);
free(path);
currcpt++;
***************
*** 775,780 ****
--- 810,931 ----
(cleanup_func_type*)0,&state);
return state!=3DIS_READERR;
}
+=20
+ /* get the amount of storage space in a directory */
+ static unsigned long bytesused ( directory )=20
+ char *directory;
+ {
+ DIR* dir;
+ struct dirent* entry;
+ char entry_path[PATH_MAX + 1];
+ size_t path_len,file_len;
+ struct stat st;
+ unsigned long bytecount;
+=20
+ bytecount =3D 0;
+=20
+ /* copy path for local use */
+ strncpy(entry_path, directory, sizeof(entry_path));
+ path_len =3D strlen(entry_path);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Dir: %s, %d chars\n", entry_path, path_len);
+ #endif
+ /* append / at end if not exists */
+ if (entry_path[path_len - 1] !=3D '/') {
+ entry_path[path_len] =3D '/';
+ entry_path[path_len + 1] =3D '\0';
+ ++path_len;
+ }
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Corrected dir: %s\n", entry_path);
+ #endif
+ /* get the directory listing */
+ dir =3D opendir(entry_path);
+ /* and loop through it */
+ while ((entry =3D readdir(dir)) !=3D NULL) {
+ /* stat the found entries to add size to total */=09
+ strncpy(entry_path + path_len, entry->d_name, sizeof(entry_path) - =
path_len);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Statting: %s\n", entry_path);
+ #endif
+ lstat(entry_path, &st);
+ if(S_ISDIR(st.st_mode)) {
+ /* get the size of files in this directory */
+ file_len =3D strlen(entry_path);
+ if(strcmp(&entry_path[file_len - 2],"/.") &&
+ strcmp(&entry_path[file_len - 3], "/..") ) { =09
+ /* if(entry_path[file_len - 1] !=3D '.') { */
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Recursing into %s\n", entry_path);
+ #endif
+ bytecount +=3D bytesused(entry_path);
+ }
+ }
+ if(S_ISREG(st.st_mode)) {
+ /* a regular file, add size */
+ bytecount +=3D (unsigned long)st.st_size;
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Size: %d, total %d\n", st.st_size, bytecount);
+ #endif
+=20
+ }
+ }
+ closedir(dir);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Returning: %u bytes\n", bytecount);
+ #endif
+ return bytecount;
+ }
+=20
+ /* get the quota for a user */
+ static unsigned long getuserquota( name )
+ char *name;
+ {
+ FILE* fp;
+ unsigned long quota;
+ char *equal;
+ char line[1024];
+=20
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Opening quotafile: %s\n",QUOTAFILE);
+ #endif
+ quota =3D 0;
+ fp =3D fopen (QUOTAFILE,"r");
+ if(fp =3D=3D NULL)
+ return 0;
+ while (fgets(line, sizeof(line), fp) !=3D (char *) NULL) {
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Read %d chars: %s", strlen(line),line);
+ #endif
+ /* ignore comments */
+ if(line[0] !=3D '#') {
+ /* look for valid format and parse */
+ if((equal =3D strchr(line,'=3D')) !=3D NULL) {
+ *equal =3D '\0';
+ #ifdef JKRDEBUG
+ fprintf(stderr,"User found: %s\n", line);
+ #endif
+=20
+ if(!strcasecmp(line,DEFAULTUSER)) {
+ quota =3D atol(equal+1);
+ }
+ if(!strcasecmp(line,name)) {
+ quota =3D atol(equal+1);
+ break;
+ }
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Quota now: %u\n", quota);
+ #endif
+ }
+ }
+ }
+ fclose(fp);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Returning quota: %u\n", quota);
+ #endif
+ return quota;
+ }
+=20
#else
int lmtp_dummy_var; /* to prevent insanity in some linkers */
#endif
------=_NextPart_000_0023_01C2FEA4.3D352560
Content-Type: application/octet-stream;
name="authenticate.c.diff"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="authenticate.c.diff"
*** authenticate.c.original Thu Apr 18 15:26:45 2002
--- authenticate.c Fri Apr 19 11:41:03 2002
***************
*** 181,186 ****
--- 181,196 ----
=20
const char*auth_mailboxname(pass)auth_identity*const pass;
{ if(!pass->mbox)
+=20
+ /* User home directory plus username becomes the base directory for =
mail */
+ { size_t i;
+ =
if(!(pass->mbox=3Dmalloc((i=3Dstrlen(pass->pw->pw_dir))+strlen(pass->pw->=
pw_name)+2)))
+ return "";
+ strcpy(pass->mbox,pass->pw->pw_dir);
+ strcpy(pass->mbox+i,"/");
+ strcpy(pass->mbox+i+1,pass->pw->pw_name);
+=20
+ #ifdef notused
#ifdef MAILSPOOLHOME
{ static const char mailfile[]=3DMAILSPOOLHOME;size_t i;
=
if(!(pass->mbox=3Dmalloc((i=3Dstrlen(pass->pw->pw_dir))+STRLEN(mailfile)+=
1)))
***************
*** 205,210 ****
--- 215,221 ----
strcat(p,MAILSPOOLSUFFIX);
}
#endif /* MAILSPOOLHOME */
+ #endif /* notused */
}
return pass->mbox;
}
------=_NextPart_000_0023_01C2FEA4.3D352560
Content-Type: application/octet-stream;
name="lmtp.c.diff-no-recurse"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="lmtp.c.diff-no-recurse"
*** lmtp.c.original Mon Jun 3 14:27:28 2002
--- lmtp.c Tue Jan 28 16:43:55 2003
***************
*** 22,27 ****
--- 22,51 ----
#include "from.h"
#include "lmtp.h"
=20
+ #include <dirent.h>
+ #include <sys/stat.h>
+ #define QUOTAFILE "/var/spool/mail/quotas.txt"
+ #define DEFAULTUSER "_DEFAULT_"
+ #define MBOXFILE "mbox"
+ static unsigned long bytesused __P((char *));
+ static unsigned long bytesused2 __P((const struct passwd *));
+ static unsigned long getuserquota __P((char *));
+ /* define JKRDEBUG to turn on some custum debugging to stderr.
+ * you can test this with eg: mail.local -f root johan < testmsg
+ *
+ #define JKRDEBUG 1
+ */
+=20
+ /* This is a duplicate of the structure defined in authenticate.c
+ * We need it here to be able to reference the passwd struct - JKR
+ */
+ struct auth_identity
+ { const struct passwd* pw;
+ char* mbox;
+ int sock;
+ };
+=20
+=20
/*
=20
lhlo =3D> print stuff
***************
*** 241,246 ****
--- 265,271 ----
cNOOP[]=3D"NOOP";
const char*msg,*msgcmd;int flush=3D0,c,lmtp_state=3DS_START;long =
size=3D0;
auth_identity**rcpts,**lastrcpt,**currcpt;
+ unsigned long quota;
=20
pushfd(STDIN);overread=3D0;overlen=3D0;nliseol=3D1;
bufinit;ctopfd=3D-1; /* setup our output */
***************
*** 399,404 ****
--- 424,442 ----
free(path);
goto message;
}
+=20
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Mailbox =3D %s, dir =3D %s, name =3D =
%s\n",mailbox,temp->pw->pw_dir,temp->pw->pw_name);
+ #endif
+ if((quota=3Dgetuserquota(mailbox)) > 0) {
+ /* if(bytesused(temp->pw->pw_dir) > quota) */
+ if(bytesused2(temp->pw) > quota)
+ { msg=3D"550 5.2.2 Mailbox quota exceeded\r\n";
+ free(path);
+ goto message;
+ }
+ }
+=20
auth_copyid(*currcpt=3Dauth_newid(),temp);
free(path);
currcpt++;
***************
*** 775,780 ****
--- 813,1013 ----
(cleanup_func_type*)0,&state);
return state!=3DIS_READERR;
}
+=20
+ #ifdef NOTDEF
+ /* This code has a flaw:
+ * since it simply recursively loops in the user's homedir, there is
+ * a possibility that a wrong (high) bytecount is calculated while
+ * pop3/imap daemons are using temp files to migrate messages from the
+ * spool file to the mbox file. This can cause messages incorrectly
+ * to be bounced with 'quota exceeded'
+ */
+ /* get the amount of storage space in a directory */
+ static unsigned long bytesused ( directory )=20
+ char *directory;
+ {
+ DIR* dir;
+ struct dirent* entry;
+ char entry_path[PATH_MAX + 1];
+ size_t path_len,file_len;
+ struct stat st;
+ unsigned long bytecount;
+=20
+ bytecount =3D 0;
+=20
+ /* copy path for local use */
+ strncpy(entry_path, directory, sizeof(entry_path));
+ path_len =3D strlen(entry_path);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Dir: %s, %d chars\n", entry_path, path_len);
+ #endif
+ /* append / at end if not exists */
+ if (entry_path[path_len - 1] !=3D '/') {
+ entry_path[path_len] =3D '/';
+ entry_path[path_len + 1] =3D '\0';
+ ++path_len;
+ }
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Corrected dir: %s\n", entry_path);
+ #endif
+ /* get the directory listing */
+ dir =3D opendir(entry_path);
+ /* and loop through it */
+ while ((entry =3D readdir(dir)) !=3D NULL) {
+ /* stat the found entries to add size to total */=09
+ strncpy(entry_path + path_len, entry->d_name, sizeof(entry_path) - =
path_len);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Statting: %s\n", entry_path);
+ #endif
+ lstat(entry_path, &st);
+ if(S_ISDIR(st.st_mode)) {
+ /* get the size of files in this directory */
+ file_len =3D strlen(entry_path);
+ if(strcmp(&entry_path[file_len - 2],"/.") &&
+ strcmp(&entry_path[file_len - 3], "/..") ) { =09
+ /* if(entry_path[file_len - 1] !=3D '.') { */
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Recursing into %s\n", entry_path);
+ #endif
+ bytecount +=3D bytesused(entry_path);
+ }
+ }
+ if(S_ISREG(st.st_mode)) {
+ /* a regular file, add size */
+ bytecount +=3D (unsigned long)st.st_size;
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Size: %d, total %d\n", st.st_size, bytecount);
+ #endif
+=20
+ }
+ }
+ closedir(dir);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Returning: %u bytes\n", bytecount);
+ #endif
+ return bytecount;
+ }
+ #endif /* NOTDEF */
+=20
+ /* get the amount of storage space in use by a user
+ * since we do not allow folders, we know we have only the spool file
+ * and mbox file in the homedir.
+ * Thus, we simply look at these files only...
+ *
+ * We deduct the full pathnames from the home dir and username entries
+ * in the passwd structure...
+ */
+ static unsigned long bytesused2 ( pw )=20
+ const struct passwd *pw;
+ {
+ char entry_path[PATH_MAX + 1];
+ size_t path_len,file_len;
+ struct stat st;
+ unsigned long bytecount;
+=20
+ bytecount =3D 0;
+=20
+ /* copy directory for local use */
+ strncpy(entry_path, pw->pw_dir, sizeof(entry_path));
+ path_len =3D strlen(entry_path);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Dir: %s, %d chars\n", entry_path, path_len);
+ #endif
+ /* append / at end if not exists */
+ if (entry_path[path_len - 1] !=3D '/') {
+ entry_path[path_len] =3D '/';
+ entry_path[path_len + 1] =3D '\0';
+ ++path_len;
+ }
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Corrected dir: %s\n", entry_path);
+ #endif
+=20
+ /* go check mbox file */
+ strcat(entry_path,MBOXFILE);
+ /* and stat it to get the size */
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Statting: %s\n", entry_path);
+ #endif
+ if(!stat(entry_path, &st) && (S_ISREG(st.st_mode))) {
+ /* a regular file, add size */
+ bytecount +=3D (unsigned long)st.st_size;
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Size: %d, total %u\n", st.st_size, bytecount);
+ #endif
+ }
+=20
+ /* go check spool file; get directory path back */
+ entry_path[path_len] =3D '\0';
+ /* add the name from the password structure; this is case-adjusted! =
*/
+ strcat(entry_path,pw->pw_name);
+ /* and stat it to get the size */
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Statting: %s\n", entry_path);
+ #endif
+ if(!stat(entry_path, &st) && (S_ISREG(st.st_mode))) {
+ /* a regular file, add size */
+ bytecount +=3D (unsigned long)st.st_size;
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Size: %d, total %u\n", st.st_size, bytecount);
+ #endif
+ }
+=20
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Returning: %u bytes\n", bytecount);
+ #endif
+ return bytecount;
+ }
+=20
+ /* get the quota for a user */
+ static unsigned long getuserquota( name )
+ char *name;
+ {
+ FILE* fp;
+ unsigned long quota;
+ char *equal;
+ char line[1024];
+=20
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Opening quotafile: %s\n",QUOTAFILE);
+ #endif
+ quota =3D 0;
+ fp =3D fopen (QUOTAFILE,"r");
+ if(fp =3D=3D NULL)
+ return 0;
+ while (fgets(line, sizeof(line), fp) !=3D (char *) NULL) {
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Read %d chars: %s", strlen(line),line);
+ #endif
+ /* ignore comments */
+ if(line[0] !=3D '#') {
+ /* look for valid format and parse */
+ if((equal =3D strchr(line,'=3D')) !=3D NULL) {
+ *equal =3D '\0';
+ #ifdef JKRDEBUG
+ fprintf(stderr,"User found: %s\n", line);
+ #endif
+=20
+ if(!strcasecmp(line,DEFAULTUSER)) {
+ quota =3D atol(equal+1);
+ }
+ if(!strcasecmp(line,name)) {
+ quota =3D atol(equal+1);
+ break;
+ }
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Quota now: %u\n", quota);
+ #endif
+ }
+ }
+ }
+ fclose(fp);
+ #ifdef JKRDEBUG
+ fprintf(stderr,"Returning quota: %u\n", quota);
+ #endif
+ return quota;
+ }
+=20
#else
int lmtp_dummy_var; /* to prevent insanity in some linkers */
#endif
------=_NextPart_000_0023_01C2FEA4.3D352560--