|
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 } |