commands/vcheckquota.cc
changeset 0 6f7a81934006
equal deleted inserted replaced
-1:000000000000 0:6f7a81934006
       
     1 // Copyright (C) 1999,2000 Bruce Guenter <bruceg@em.ca>
       
     2 //
       
     3 // This program is free software; you can redistribute it and/or modify
       
     4 // it under the terms of the GNU General Public License as published by
       
     5 // the Free Software Foundation; either version 2 of the License, or
       
     6 // (at your option) any later version.
       
     7 //
       
     8 // This program is distributed in the hope that it will be useful,
       
     9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    11 // GNU General Public License for more details.
       
    12 //
       
    13 // You should have received a copy of the GNU General Public License
       
    14 // along with this program; if not, write to the Free Software
       
    15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    16 
       
    17 #include <config.h>
       
    18 #include <errno.h>
       
    19 #include <stdlib.h>
       
    20 #include <time.h>
       
    21 #include "config/configrc.h"
       
    22 #include "cli/cli.h"
       
    23 #include "fdbuf/fdbuf.h"
       
    24 #include "vcommand.h"
       
    25 #include "ac/dirent.h"
       
    26 #include "misc/itoa.h"
       
    27 #include "misc/stat_fns.h"
       
    28 #include "misc/md5.h"
       
    29 #include "misc/strtou.h"
       
    30 #include "misc/utoa.h"
       
    31 
       
    32 const char* cli_program = "vcheckquota";
       
    33 const char* cli_help_prefix = "vmailmgr quota enforcement program\n";
       
    34 const char* cli_help_suffix = "
       
    35 Warning: the soft-message is linked into the users maildir once for each
       
    36 message that is received while the account is over its soft quota.  This may
       
    37 result in multiple warning messages.\n";
       
    38 const char* cli_args_usage = "";
       
    39 const int cli_args_min = 0;
       
    40 const int cli_args_max = 0;
       
    41 static unsigned soft_maxsize = 4096;
       
    42 static const char* soft_message = 0;
       
    43 
       
    44 // F<vcheckquota> ensures that the hard and soft quotas are enforced,
       
    45 // that message counts and sizes are appropriately limited.
       
    46 // The limits are set by the vadduser or vchattr command.
       
    47 //
       
    48 // The following rules are applied:
       
    49 //
       
    50 // =over 8
       
    51 //
       
    52 // =item 1
       
    53 //
       
    54 // If the message is larger than the message size limit, it is rejected.
       
    55 //
       
    56 // =item 2
       
    57 //
       
    58 // If the user has too many messages in their mailbox,
       
    59 // further messages are rejected.
       
    60 //
       
    61 // =item 3
       
    62 //
       
    63 // If the user is over their hard quota, all further messages are rejected
       
    64 // and no warning messages are linked in.
       
    65 //
       
    66 // =item 4
       
    67 //
       
    68 // If the user is over their soft quota, and the message is small
       
    69 // (as defined by I<soft-maxsize>), the message is accepted, otherwise
       
    70 // it is rejected.  If I<soft-message> is defined, a warning message
       
    71 // is linked into the mailbox in either case.
       
    72 //
       
    73 // =back
       
    74 
       
    75 cli_option cli_options[] = {
       
    76   { 'a', "soft-maxsize", cli_option::uinteger, 0, &soft_maxsize,
       
    77     "The maximum message size after soft quota is reached", "4096" },
       
    78   { 'm', "soft-message", cli_option::string, 0, &soft_message,
       
    79     "The path to the soft quota warning message", "no message" },
       
    80   {0}
       
    81 };
       
    82 
       
    83 // SEE ALSO
       
    84 //
       
    85 // vadduser(1), vchattr(1)
       
    86 
       
    87 void exit_msg(const char* msg, int code)
       
    88 {
       
    89   fout << "vcheckquota: " << msg << endl;
       
    90   exit(code);
       
    91 }
       
    92 void die_fail(const char* msg) { exit_msg(msg, 100); }
       
    93 void die_temp(const char* msg) { exit_msg(msg, 111); }
       
    94 
       
    95 bool stat_dir(const mystring& dirname, unsigned& count, unsigned long& size) {
       
    96   struct stat buf;
       
    97   DIR* dir = opendir(dirname.c_str());
       
    98   if(!dir) 
       
    99     die_temp("Could not maildir\n");
       
   100 
       
   101   while(dirent* entry = readdir(dir)) {
       
   102     const char* name = entry->d_name;
       
   103     if(name[0] == '.' &&
       
   104        (NAMLEN(entry) == 1 ||
       
   105 	(name[1] == '.' && NAMLEN(entry) == 2)))
       
   106       continue;
       
   107 
       
   108     mystring fullname = dirname + "/" + name;
       
   109 
       
   110     if(stat(fullname.c_str(), &buf) == -1) {
       
   111       fout << "Cannot stat " << fullname.c_str() << "\n";
       
   112       return false;
       
   113     }
       
   114 
       
   115     if(S_ISREG(buf.st_mode)) {
       
   116       ++count;
       
   117       size += buf.st_blocks * 512;
       
   118     }
       
   119   }
       
   120   closedir(dir);
       
   121   return true;
       
   122 }
       
   123 
       
   124 void link_softquota_message(const mystring& mailbox)
       
   125 {
       
   126   mystring newdir = mailbox + "/new/";
       
   127   pid_t pid = getpid();
       
   128   for(;;) {
       
   129     mystring path = newdir + itoa(time(0)) + ".";
       
   130     path = path + itoa(pid) + ".softquota-warning";
       
   131     if(symlink(soft_message, path.c_str()) == 0)
       
   132       return;
       
   133     if(errno != EEXIST)
       
   134       die_temp("Could not create symlink to soft quota warning message");
       
   135     sleep(1);
       
   136   }
       
   137 }
       
   138 
       
   139 void check_quota(mystring mailbox,
       
   140 		 unsigned hardquota, unsigned softquota,
       
   141 		 unsigned maxsize, unsigned maxcount)
       
   142 {
       
   143   /*
       
   144    * There are 4 cases to consider when comparing disk useage (du)
       
   145    * agains hard and soft quotas:
       
   146    *
       
   147    * Case 1: soft = 0, hard = 0: user has no quota set
       
   148    * Case 2: soft = 0, hard > 0: use hard quota
       
   149    * Case 3: soft > 0, hard = 0: treat soft quota as hard
       
   150    * Case 4: soft > 0, hard > 0: if du becomes larger
       
   151    *         then soft quota, allow message in if 
       
   152    *         a) it is small (<2048 bytes), 
       
   153    *         b) it would not put du over hard quota.
       
   154    */
       
   155 
       
   156 
       
   157   //compute message size
       
   158   struct stat st;
       
   159   if(fstat(0, &st) == -1)
       
   160     die_temp("Failed to stat message");
       
   161  
       
   162   unsigned long msgsize = st.st_blocks * 512;
       
   163 
       
   164   if(maxsize != UINT_MAX && msgsize > maxsize)
       
   165     //message is too large
       
   166     die_fail("Sorry, this message is larger than the current maximum message size limit.\n");
       
   167 
       
   168   /* Case 1: no quotas set */
       
   169   if(softquota == UINT_MAX && hardquota == UINT_MAX && maxcount == UINT_MAX)
       
   170     return;
       
   171 
       
   172   mystring dirname = mailbox;
       
   173   mystring newdir  = dirname + "/new";
       
   174   mystring curdir  = dirname + "/cur";
       
   175   unsigned cur_count=0;
       
   176   unsigned long cur_size=0;
       
   177   unsigned new_count=0;
       
   178   unsigned long new_size=0;
       
   179 
       
   180   //treat stat_dir failures as temp errors
       
   181   if(!stat_dir(newdir, new_count, new_size))
       
   182     die_temp("Failed to stat new dir");
       
   183   if(!stat_dir(curdir, cur_count, cur_size))
       
   184     die_temp("Failed to stat cur dir");
       
   185 
       
   186   unsigned long du  = cur_size  + new_size  + msgsize;
       
   187   unsigned msgcount = cur_count + new_count + 1;
       
   188 
       
   189   //too many messages in the mbox
       
   190   if(maxcount != UINT_MAX && msgcount > maxcount)
       
   191     die_fail("Sorry, the person you sent this message has too many messages stored in the mailbox\n");
       
   192 
       
   193   // No total size quotas are set
       
   194   if(hardquota == UINT_MAX)
       
   195     if(softquota == UINT_MAX)
       
   196       return;
       
   197   // Take care of Cases 2 and 3, and make everything look like Case 4
       
   198     else
       
   199       hardquota = softquota;
       
   200   
       
   201   // Check hard quota before soft quota, as it has priority
       
   202   if(du > hardquota) 
       
   203     die_fail("Message would exceed virtual user's disk quota.\n");
       
   204   
       
   205   // Soft quota allows small (4K default) messages
       
   206   // In other words, it only blocks large messages
       
   207   if(du > softquota) {
       
   208     if(soft_message)
       
   209       link_softquota_message(mailbox);
       
   210     if(msgsize > soft_maxsize)
       
   211       die_fail("Sorry, your message cannot be delivered.\n"
       
   212 	       "User's disk quota exceeded.\n"
       
   213 	       "A small message will be delivered should you wish "
       
   214 	       "to inform this person.\n");
       
   215   }
       
   216 }
       
   217 
       
   218 int cli_main(int, char**)
       
   219 {
       
   220 #define ENV_VAR_REQ(VAR,ENV) const char* tmp__##VAR = getenv(#ENV); if(!tmp__##VAR) die_fail(#ENV " is not set");
       
   221 #define ENV_VAR_STR(VAR,ENV) ENV_VAR_REQ(VAR,ENV) mystring VAR = tmp__##VAR;
       
   222 #define ENV_VAR_UINT(VAR,ENV) ENV_VAR_REQ(VAR,ENV) unsigned VAR = strtou(tmp__##VAR, &tmp__##VAR); if(*tmp__##VAR) die_fail(#ENV " is not a valid number");
       
   223 
       
   224   ENV_VAR_STR(maildir,  MAILDIR);
       
   225   // Always succeed for aliases.
       
   226   if(!maildir) 
       
   227     return 0;
       
   228 
       
   229   ENV_VAR_UINT(maxsize,   VUSER_MSGSIZE);
       
   230   ENV_VAR_UINT(maxcount,  VUSER_MSGCOUNT);
       
   231   ENV_VAR_UINT(hardquota, VUSER_HARDQUOTA);
       
   232   ENV_VAR_UINT(softquota, VUSER_SOFTQUOTA);
       
   233 
       
   234   check_quota(maildir, hardquota, softquota, maxsize, maxcount);
       
   235   return 0;
       
   236 }