Subject: Quota Support in UW Imap

From: "Dan Mahoney, System Admin" <[email-masked]>
Date: Sun, 19 Dec 2004 02:20:10 -0500 (EST)

Hey all,

I was wondering if UW Imap had the ability to report the system quota of 
the mail directory (i.e. the home directory) to users who were using the 
QUOTA extension.

I know this is a pretty standard system call (I think)...so it shouldn't 
be too difficult to implement.  I know imapd can recognize when it's going 
to go over, but I don't know if it can be used, for example, with an imap 
mail client (I"m using SquirrelMail) that's asking what the quota is.

Any response on this would be much appreciated.

-Dan Mahoney

--

--------Dan Mahoney--------
Techie,  Sysadmin,  WebGeek
Gushi on efnet/undernet IRC
ICQ: 13735144   AIM: LarpGM
Site:  http://www.gushi.org
---------------------------

-- 
------------------------------------------------------------------
 For information about this mailing list, and its archives, see: 
 http://www.washington.edu/imap/c-client-list.html
------------------------------------------------------------------

---

From: Mark Crispin <[email-masked]>
Date: Sun, 19 Dec 2004 02:07:19 -0800 (PST)

On Sun, 19 Dec 2004, Dan Mahoney, System Admin wrote:
> I was wondering if UW Imap had the ability to report the system quota of the 
> mail directory (i.e. the home directory) to users who were using the QUOTA 
> extension.

At the current time, the answer is "no".

> I know this is a pretty standard system call (I think)...so it shouldn't be 
> too difficult to implement.

Unfortunately, this is not the case; there are at least three different 
implementations of UNIX quotas that I am aware of, each with different 
semantics.  It would be fairly easy to assume that "all the world is 
Linux" and implement the QUOTA extension for Linux only; but imapd runs on 
many platforms besides Linux.

A different problem is that the QUOTA extension does more than report 
quotas; it also allows setting them.  To make things more complicated, 
QUOTA allows the redistribution of quotas within a user's space (the 
concept of quota root).

Finally, IMAP QUOTA isn't defined in terms that directly map to UNIX 
quota; the suggested resources are STORAGE and MESSAGE, however on UNIX it 
is typically by disk block.  There are also independent concepts of 
"grace" space which isn't represented in IMAP QUOTA.

None of this mean that "it can't be done"; however, to be useful and 
correct is quite a bit more complicated than the obvious simple 
implementation.

> I know imapd can recognize when it's going to go 
> over, but I don't know if it can be used, for example, with an imap mail 
> client (I"m using SquirrelMail) that's asking what the quota is.

QUOTA is an optional extension; clients are expected to handle servers 
which do not offer an particular extension.

-- Mark --

http://staff.washington.edu/mrc
Science does not emerge from voting, party politics, or public debate.
Si vis pacem, para bellum.

---

From: David B Funk <[email-masked]>
Date: Mon, 20 Dec 2004 16:42:45 -0600 (CST)

> many platforms besides Linux.
[snip..]
> correct is quite a bit more complicated than the obvious simple

I fully respect Mark's comments & opinions (after all, he's the person
who has to deal with complaints about UW Imap not working as expected. ;)

Be that as it may, if you're willing to settle for a "gas gauge" kind of
implementation (IE a general indication of 'how close to empty' rather
than an exact impementation of the standard) then the Unix quota can be
used for a practical QUOTA system. (Particularly for webmail systems
where the users will probably never be able to get an exact handle on
the mailboxes, they just want a general "how close am I" kind of metric).

We have found this to be quite useful even if not technically 'correct'.

I hacked UW Imap to add two kinds of QUOTA info.
The first (see code defined by QUOTA_CHECK) regularly checks the
user's quota and sends IMAP ALERT messages if they're over.
It sends the text contained in one of two different files
(see QUOTA_CHECK_WARN_MESS_FILE & QUOTA_CHECK_ERROR_MESS_FILE)
via 'palert'.
The second is a quick-and-dirty implementation of the QUOTA extension
(see code defined by DO_QUOTA).

I've attached two files, the first is a patch to imapd.c (based upon
the 2004a version), the second contains the actual system dependent
quota gathering routines. That code is written for a Sys5 type OS
(we use HP-UX) but there are enough comments in it that a good hacker
should be able to adapt it to other platforms.

patch imapd.c, put "quota_check.c" in src/osdep/unix, add the
defines to the Makefile for imapd, and rebuild.

If you use the QUOTA_CHECK part, you'll have to create two text files
that contain the messages, one 'alert' for going over soft quota
one for hitting hard. suggest keeping the messages to a single line.

Please do not bother Mark about this stuff. I'll try to answer
questions about it but cannot promise anything WRT porting the OS
dependent part to another platform.

Dave

-- 
Dave Funk                                  University of Iowa
<dbfunk (at) engineering.uiowa.edu>        College of Engineering
319/335-5751   FAX: 319/384-0549           1256 Seamans Center
Sys_admin/Postmaster/cell_admin            Iowa City, IA 52242-1527
#include <std_disclaimer.h>
Better is not better, 'standard' is better. B{

*** imapd.c.orig	Tue Jun 29 16:56:17 2004
--- imapd.c	Mon Dec 20 14:59:48 2004
***************
*** 17,22 ****
--- 17,26 ----
   * The full text of our legal notices is contained in the file called
   * CPYRIGHT, included with this Distribution.
   */
+ /*
+  * 2003/12/1 dbf    Add QUOTA capability
+  */
+ 
  
  /* Parameter files */
  
***************
*** 23,28 ****
--- 27,35 ----
  #include <stdio.h>
  #include <ctype.h>
  #include <errno.h>
+ #ifdef QUOTA_CHECK
+ #include <time.h>
+ #endif
  extern int errno;		/* just in case */
  #include <signal.h>
  #include <time.h>
***************
*** 43,48 ****
--- 50,64 ----
  #define SHUTDOWNTIMER 1 MINUTES	/* shutdown dally timer */
  #define IDLETIMER 1 MINUTES	/* IDLE command poll timer */
  #define CHECKTIMER 15 MINUTES	/* IDLE command last checkpoint timer */
+ #ifdef QUOTA_CHECK
+ #define QUOTATIMER 30 MINUTES /* quota check timer, how often to check */
+ #ifndef QUOTA_CHECK_WARN_MESS_FILE	/* error message to send when over quota */
+ #define QUOTA_CHECK_WARN_MESS_FILE "/etc/imap_quota_alert"
+ #endif
+ #ifndef QUOTA_CHECK_ERROR_MESS_FILE
+ #define QUOTA_CHECK_ERROR_MESS_FILE "/etc/imap_quota_error"
+ #endif
+ #endif	/* QUOTA_CHECK */

  #define LITSTKLEN 20		/* length of literal stack */
***************
*** 124,129 ****
--- 140,151 ----
  long crit_set (SEARCHSET **set,unsigned char **arg,unsigned long maxima);
  long crit_number (unsigned long *number,unsigned char **arg);
  long crit_string (STRINGLIST **string,unsigned char **arg);
+ #ifdef DO_QUOTA
+ int get_quota(char *, char *, int *, int*);
+ #endif
+ #ifdef QUOTA_CHECK
+ int quota_check(char *, char *);
+ #endif
  
  void fetch (char *t,unsigned long uid);
  typedef void (*fetchfn_t) (unsigned long i,void *args);
***************
*** 183,192 ****
  
  /* Global storage */
  
! char *version = "2004.352";	/* version number of this server */
  time_t alerttime = 0;		/* time of last alert */
  time_t sysalerttime = 0;	/* time of last system alert */
  time_t useralerttime = 0;	/* time of last user alert */
  time_t lastcheck = 0;		/* time of last checkpoint */
  time_t shutdowntime = 0;	/* time of last shutdown */
  int state = LOGIN;		/* server state */
--- 205,218 ----
  
  /* Global storage */
  
! char *version = "2004a.352L-mod.2";	/* version number of this server */
! 				/* Note local mods in version no. */
  time_t alerttime = 0;		/* time of last alert */
  time_t sysalerttime = 0;	/* time of last system alert */
  time_t useralerttime = 0;	/* time of last user alert */
+ #ifdef QUOTA_CHECK
+ time_t quotatime = 0;		/* time of last quota check */
+ #endif
  time_t lastcheck = 0;		/* time of last checkpoint */
  time_t shutdowntime = 0;	/* time of last shutdown */
  int state = LOGIN;		/* server state */
***************
*** 893,898 ****
--- 919,951 ----
  	  if (stream)		/* allow untagged EXPUNGE */
  	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
  	}
+ #ifdef DO_QUOTA
+ 				/* give user's quota */
+ 	else if (!strcmp(cmd,"GETQUOTA") || !strcmp(cmd,"GETQUOTAROOT")) {
+ 	  int used, limit;
+ 	  char getq_resp[256];
+ 	  response = badarg;	/* assume failure */
+ 				/* get mailbox argument, ignore it, simple-minded hack */
+ 	  if (!(s = snarf (&arg)))
+ 	    response = misarg;
+ 	  else if (arg) response = badarg;
+ 	/* make sure not anonymous and have valid user-name */
+ 	  else if ((!anonymous) && user && 
+ 	          (get_quota(myhomedir(),user, &used, &limit) == 0)) {
+ 		  if (!strcmp(cmd,"GETQUOTAROOT")) {
+ 		     sprintf(getq_resp,"* QUOTAROOT %.80s \"\"\015\012",s);
+ 		     PSOUT(getq_resp);
+ 		     s= "\"\"";
+ 		  }
+ /* build response string: "* QUOTA ROOT (STORAGE use max)" */
+ 		sprintf(getq_resp,"* QUOTA %.80s (STORAGE %d %d)\015\012",s,used,limit);
+ 		PSOUT(getq_resp);
+ 		response = win;
+ 	    }
+ 	  if (stream)		/* allow untagged EXPUNGE */
+ 	    mail_parameters (stream,SET_ONETIMEEXPUNGEATPING,(void *) stream);
+ 	}
+ #endif	/* DO_QUOTA */
  
  				/* find mailboxes */
  	else if (!strcmp (cmd,"FIND")) {
***************
*** 1332,1337 ****
--- 1385,1408 ----
      if (state != LOGIN)		/* do user alert if logged in */
        useralerttime = palert (mailboxfile (tmp,USERALERTFILE),useralerttime);
    }
+ #ifdef QUOTA_CHECK
+   if ((time (0) > quotatime + QUOTATIMER) && (strlen(user) > 1) && (strlen(myhomedir()) > 1 )) {
+     quotatime = time (0);     /* check for quota abuse */
+     switch (quota_check(myhomedir(),user)) { /* possible quota overflows */
+       case 0:                 /* no problem, skip */
+ 	break;
+       case 1:                 /* over soft quota, give alert message */
+ 	(void) palert (QUOTA_CHECK_WARN_MESS_FILE,0);
+ 	break;
+       case 2:                 /* over hard quota, give error message */
+ 	(void) palert (QUOTA_CHECK_WARN_MESS_FILE,0);
+ 	(void) palert (QUOTA_CHECK_ERROR_MESS_FILE,0);
+ 	break;
+       default:                /* ignore errors from quota_check */
+ 	break;
+     }
+   }
+ #endif /* QUOTA_CHECK */
  }
  
  /* Print an alert file
***************
*** 3381,3386 ****
--- 3452,3460 ----
        thr = thr->next;
      }
      if (!anonymous) PSOUT (" MULTIAPPEND");
+ #ifdef DO_QUOTA
+     if (!anonymous) PSOUT (" QUOTA");
+ #endif
    }
    if (flag <= 0) {		/* want pre-authentication capabilities? */
      PSOUT (" SASL-IR LOGIN-REFERRALS");
***************
*** 4043,4045 ****
--- 4117,4122 ----
  	  user ? (char *) user : "???",tcp_clienthost (),
  	  (stream && stream->mailbox) ? stream->mailbox : "???",string);
  }
+ #if defined(QUOTA_CHECK) || defined (DO_QUOTA)
+ #include "../src/osdep/unix/quota_check.c"
+ #endif