1 /*
2 * ProFTPD: mod_quotatab_file -- a mod_quotatab sub-module for managing quota
3 * data via file-based tables
4 * Copyright (c) 2002-2016 TJ Saunders
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders gives permission to link this program
21 * with OpenSSL, and distribute the resulting executable, without including
22 * the source code for OpenSSL in the source distribution.
23 */
24
25 #include "mod_quotatab.h"
26
27 #include <sys/types.h>
28 #include <sys/uio.h>
29 #include <unistd.h>
30
31 /* bloody lack of consistency... */
32 #if defined(FREEBSD4)
33 # define QUOTATAB_IOV_BASE_TYPE (char *)
34 #elif defined(LINUX)
35 # define QUOTATAB_IOV_BASE_TYPE (__ptr_t)
36 #elif defined(SOLARIS2)
37 # define QUOTATAB_IOV_BASE_TYPE (caddr_t)
38 #else
39 # define QUOTATAB_IOV_BASE_TYPE (void *)
40 #endif
41
42 module quotatab_file_module;
43
filetab_close(quota_table_t * filetab)44 static int filetab_close(quota_table_t *filetab) {
45 int res = close(filetab->tab_handle);
46 filetab->tab_handle = -1;
47 return res;
48 }
49
filetab_create(quota_table_t * filetab,void * ptr)50 static int filetab_create(quota_table_t *filetab, void *ptr) {
51 int res = -1;
52 struct iovec quotav[8];
53 off_t current_pos = 0;
54 quota_tally_t *tally = ptr;
55
56 /* Use writev() to make this more efficient. It is done piecewise, rather
57 * than doing a normal write(2) directly from the struct pointer, to avoid
58 * alignment/padding issues.
59 */
60
61 quotav[0].iov_base = QUOTATAB_IOV_BASE_TYPE tally->name;
62 quotav[0].iov_len = sizeof(tally->name);
63
64 quotav[1].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->quota_type);
65 quotav[1].iov_len = sizeof(tally->quota_type);
66
67 quotav[2].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_in_used);
68 quotav[2].iov_len = sizeof(tally->bytes_in_used);
69
70 quotav[3].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_out_used);
71 quotav[3].iov_len = sizeof(tally->bytes_out_used);
72
73 quotav[4].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_xfer_used);
74 quotav[4].iov_len = sizeof(tally->bytes_xfer_used);
75
76 quotav[5].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_in_used);
77 quotav[5].iov_len = sizeof(tally->files_in_used);
78
79 quotav[6].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_out_used);
80 quotav[6].iov_len = sizeof(tally->files_out_used);
81
82 quotav[7].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_xfer_used);
83 quotav[7].iov_len = sizeof(tally->files_xfer_used);
84
85 /* Seek to the end of the table */
86 current_pos = lseek(filetab->tab_handle, (off_t) 0, SEEK_END);
87 if (current_pos < 0) {
88 return -1;
89 }
90
91 while ((res = writev(filetab->tab_handle, quotav, 8)) < 0) {
92 if (errno == EINTR) {
93 pr_signals_handle();
94 continue;
95 }
96
97 return -1;
98 }
99
100 if (res > 0) {
101
102 /* Rewind to the start of the entry. */
103 if (lseek(filetab->tab_handle, current_pos, SEEK_SET) < 0) {
104 quotatab_log("error rewinding to start of tally entry: %s",
105 strerror(errno));
106 return -1;
107 }
108
109 } else if (res == 0) {
110 /* If no bytes were written, it's an error. */
111
112 quotatab_log("error: writev(2) returned zero when creating tally entry, "
113 "returning EPERM");
114 errno = EPERM;
115 res = -1;
116 }
117
118 return res;
119 }
120
filetab_lookup(quota_table_t * filetab,void * ptr,const char * name,quota_type_t quota_type)121 static unsigned char filetab_lookup(quota_table_t *filetab, void *ptr,
122 const char *name, quota_type_t quota_type) {
123
124 /* Make sure the table pointer is positioned at the start of the table,
125 * skipping the magic header value of the table.
126 */
127 if (lseek(filetab->tab_handle, (off_t) sizeof(unsigned int), SEEK_SET) < 0) {
128 quotatab_log("error seeking past table header: %s", strerror(errno));
129 return FALSE;
130 }
131
132 /* Handle tally and limit tables differently... */
133
134 if (filetab->tab_type == TYPE_TALLY) {
135 quota_tally_t *tally = ptr;
136
137 while (filetab->tab_read(filetab, ptr) >= 0) {
138 pr_signals_handle();
139
140 /* Compare quota types. If the quota type is ALL_QUOTA, don't
141 * worry about the name.
142 */
143 if (quota_type == tally->quota_type) {
144
145 /* Match names if need be */
146 if (name &&
147 strcmp(name, tally->name) == 0) {
148 return TRUE;
149 }
150
151 if (quota_type == ALL_QUOTA) {
152 return TRUE;
153 }
154 }
155
156 /* Undo the auto-rewind of a single record's length done by
157 * filetab_read(), so that the while loop actually does iterate through
158 * all the available records.
159 */
160 if (lseek(filetab->tab_handle, filetab->tab_quotalen, SEEK_CUR) < 0) {
161 quotatab_log("error seeking past tally record: %s", strerror(errno));
162 }
163 }
164
165 } else if (filetab->tab_type == TYPE_LIMIT) {
166 quota_limit_t *limit = ptr;
167
168 while (filetab->tab_read(filetab, ptr) >= 0) {
169 pr_signals_handle();
170
171 if (quota_type == limit->quota_type) {
172 if (name &&
173 strcmp(name, limit->name) == 0) {
174 return TRUE;
175 }
176
177 if (quota_type == ALL_QUOTA) {
178 return TRUE;
179 }
180 }
181
182 if (lseek(filetab->tab_handle, filetab->tab_quotalen, SEEK_CUR) < 0) {
183 quotatab_log("error seeking past limit record: %s", strerror(errno));
184 }
185 }
186 }
187
188 /* Default return value */
189 return FALSE;
190 }
191
filetab_read(quota_table_t * filetab,void * ptr)192 static int filetab_read(quota_table_t *filetab, void *ptr) {
193 int res = -1;
194 struct iovec quotav[10];
195
196 /* Mark the current file position. */
197 off_t current_pos = lseek(filetab->tab_handle, (off_t) 0, SEEK_CUR);
198
199 if (current_pos < 0) {
200 return - 1;
201 }
202
203 /* Use readv() to make this more efficient. It is done piecewise, rather
204 * than doing a normal read(2) directly into the struct pointer, to avoid
205 * alignment/padding issues.
206 */
207
208 /* Handle the limit and tally tables differently. */
209
210 if (filetab->tab_type == TYPE_TALLY) {
211 quota_tally_t *tally = ptr;
212
213 quotav[0].iov_base = QUOTATAB_IOV_BASE_TYPE tally->name;
214 quotav[0].iov_len = sizeof(tally->name);
215
216 quotav[1].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->quota_type);
217 quotav[1].iov_len = sizeof(tally->quota_type);
218
219 quotav[2].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_in_used);
220 quotav[2].iov_len = sizeof(tally->bytes_in_used);
221
222 quotav[3].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_out_used);
223 quotav[3].iov_len = sizeof(tally->bytes_out_used);
224
225 quotav[4].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_xfer_used);
226 quotav[4].iov_len = sizeof(tally->bytes_xfer_used);
227
228 quotav[5].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_in_used);
229 quotav[5].iov_len = sizeof(tally->files_in_used);
230
231 quotav[6].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_out_used);
232 quotav[6].iov_len = sizeof(tally->files_out_used);
233
234 quotav[7].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_xfer_used);
235 quotav[7].iov_len = sizeof(tally->files_xfer_used);
236
237 while ((res = readv(filetab->tab_handle, quotav, 8)) < 0) {
238 if (errno == EINTR) {
239 pr_signals_handle();
240 continue;
241 }
242
243 return -1;
244 }
245
246 if (res > 0) {
247
248 /* Always rewind after reading a record. */
249 if (lseek(filetab->tab_handle, current_pos, SEEK_SET) < 0) {
250 quotatab_log("error rewinding to start of tally entry: %s",
251 strerror(errno));
252 return -1;
253 }
254
255 } else if (res == 0) {
256 /* Assume end-of-file. */
257 errno = EOF;
258 res = -1;
259 }
260
261 return res;
262
263 } else if (filetab->tab_type == TYPE_LIMIT) {
264 quota_limit_t *limit = ptr;
265
266 quotav[0].iov_base = QUOTATAB_IOV_BASE_TYPE limit->name;
267 quotav[0].iov_len = sizeof(limit->name);
268
269 quotav[1].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->quota_type);
270 quotav[1].iov_len = sizeof(limit->quota_type);
271
272 quotav[2].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->quota_per_session);
273 quotav[2].iov_len = sizeof(limit->quota_per_session);
274
275 quotav[3].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->quota_limit_type);
276 quotav[3].iov_len = sizeof(limit->quota_limit_type);
277
278 quotav[4].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->bytes_in_avail);
279 quotav[4].iov_len = sizeof(limit->bytes_in_avail);
280
281 quotav[5].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->bytes_out_avail);
282 quotav[5].iov_len = sizeof(limit->bytes_out_avail);
283
284 quotav[6].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->bytes_xfer_avail);
285 quotav[6].iov_len = sizeof(limit->bytes_xfer_avail);
286
287 quotav[7].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->files_in_avail);
288 quotav[7].iov_len = sizeof(limit->files_in_avail);
289
290 quotav[8].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->files_out_avail);
291 quotav[8].iov_len = sizeof(limit->files_out_avail);
292
293 quotav[9].iov_base = QUOTATAB_IOV_BASE_TYPE &(limit->files_xfer_avail);
294 quotav[9].iov_len = sizeof(limit->files_xfer_avail);
295
296 while ((res = readv(filetab->tab_handle, quotav, 10)) < 0) {
297 if (errno == EINTR) {
298 pr_signals_handle();
299 continue;
300 }
301
302 return -1;
303 }
304
305 if (res > 0) {
306
307 /* Always rewind after reading a record. */
308 if (lseek(filetab->tab_handle, current_pos, SEEK_SET) < 0) {
309 quotatab_log("error rewinding to start of limit entry: %s",
310 strerror(errno));
311 return -1;
312 }
313
314 } else if (res == 0) {
315 /* Assume end-of-file. */
316 errno = EOF;
317 res = -1;
318 }
319
320 return res;
321 }
322
323 /* default */
324 errno = EINVAL;
325 return -1;
326 }
327
filetab_verify(quota_table_t * filetab)328 static unsigned char filetab_verify(quota_table_t *filetab) {
329 unsigned int magic = 0L;
330
331 /* Make sure we are positioned at the start of the table. */
332 if (lseek(filetab->tab_handle, (off_t) 0, SEEK_SET) < 0) {
333 quotatab_log("error seeking to start of table: %s", strerror(errno));
334 return FALSE;
335 }
336
337 /* Check the header of this table, to make sure it's valid. */
338 if (read(filetab->tab_handle, &magic, sizeof(magic)) != sizeof(magic)) {
339 return FALSE;
340 }
341
342 if (magic == filetab->tab_magic) {
343 return TRUE;
344 }
345
346 return FALSE;
347 }
348
filetab_write(quota_table_t * filetab,void * ptr)349 static int filetab_write(quota_table_t *filetab, void *ptr) {
350 int res = -1;
351 struct iovec quotav[8];
352 quota_tally_t *tally = ptr;
353
354 /* Mark the current file position. */
355 off_t current_pos = lseek(filetab->tab_handle, (off_t) 0, SEEK_CUR);
356
357 if (current_pos < 0) {
358 return -1;
359 }
360
361 /* Use writev() to make this more efficient. It is done piecewise, rather
362 * than doing a normal write(2) directly from the struct pointer, to avoid
363 * alignment/padding issues.
364 */
365
366 quotav[0].iov_base = QUOTATAB_IOV_BASE_TYPE tally->name;
367 quotav[0].iov_len = sizeof(tally->name);
368
369 quotav[1].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->quota_type);
370 quotav[1].iov_len = sizeof(tally->quota_type);
371
372 quotav[2].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_in_used);
373 quotav[2].iov_len = sizeof(tally->bytes_in_used);
374
375 quotav[3].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_out_used);
376 quotav[3].iov_len = sizeof(tally->bytes_out_used);
377
378 quotav[4].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->bytes_xfer_used);
379 quotav[4].iov_len = sizeof(tally->bytes_xfer_used);
380
381 quotav[5].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_in_used);
382 quotav[5].iov_len = sizeof(tally->files_in_used);
383
384 quotav[6].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_out_used);
385 quotav[6].iov_len = sizeof(tally->files_out_used);
386
387 quotav[7].iov_base = QUOTATAB_IOV_BASE_TYPE &(tally->files_xfer_used);
388 quotav[7].iov_len = sizeof(tally->files_xfer_used);
389
390 while ((res = writev(filetab->tab_handle, quotav, 8)) < 0) {
391 if (errno == EINTR) {
392 pr_signals_handle();
393 continue;
394 }
395
396 return -1;
397 }
398
399 if (res > 0) {
400
401 /* Rewind to the start of the entry. */
402 if (lseek(filetab->tab_handle, current_pos, SEEK_SET) < 0) {
403 quotatab_log("error rewinding to start of tally entry: %s",
404 strerror(errno));
405 return -1;
406 }
407
408 } else if (res == 0) {
409 /* If no bytes were written, it's an error. */
410
411 quotatab_log("error: writev(2) returned zero when updating tally entry, "
412 "returning EPERM");
413 errno = EPERM;
414 res = -1;
415 }
416
417 return res;
418 }
419
filetab_rlock(quota_table_t * filetab)420 static int filetab_rlock(quota_table_t *filetab) {
421 filetab->tab_lock.l_type = F_RDLCK;
422 return fcntl(filetab->tab_handle, F_SETLK, &filetab->tab_lock);
423 }
424
filetab_unlock(quota_table_t * filetab)425 static int filetab_unlock(quota_table_t *filetab) {
426 filetab->tab_lock.l_type = F_UNLCK;
427 return fcntl(filetab->tab_handle, F_SETLK, &filetab->tab_lock);
428 }
429
filetab_wlock(quota_table_t * filetab)430 static int filetab_wlock(quota_table_t *filetab) {
431 filetab->tab_lock.l_type = F_WRLCK;
432 return fcntl(filetab->tab_handle, F_SETLK, &filetab->tab_lock);
433 }
434
filetab_open(pool * parent_pool,quota_tabtype_t tab_type,const char * srcinfo)435 static quota_table_t *filetab_open(pool *parent_pool,
436 quota_tabtype_t tab_type, const char *srcinfo) {
437 quota_table_t *tab = NULL;
438 pool *tab_pool = make_sub_pool(parent_pool);
439
440 tab = (quota_table_t *) pcalloc(tab_pool, sizeof(quota_table_t));
441 tab->tab_pool = tab_pool;
442 tab->tab_type = tab_type;
443
444 if (tab->tab_type == TYPE_TALLY) {
445
446 /* File-based tally table magic number */
447 tab->tab_magic = 0x07644;
448
449 /* File-based tally record length, manually defined to avoid alignment/
450 * padding issues when using sizeof().
451 */
452 tab->tab_quotalen = 121;
453
454 tab->tab_lock.l_whence = SEEK_CUR;
455 tab->tab_lock.l_start = 0;
456 tab->tab_lock.l_len = tab->tab_quotalen;
457
458 /* Open the table handle */
459 if ((tab->tab_handle = open(srcinfo, O_RDWR)) < 0) {
460 destroy_pool(tab->tab_pool);
461 return NULL;
462 }
463
464 } else if (tab->tab_type == TYPE_LIMIT) {
465
466 /* File-based limit table magic number */
467 tab->tab_magic = 0x07626;
468
469 /* File-based limit record length, manually defined to avoid alignment/
470 * padding issues when using sizeof().
471 */
472 tab->tab_quotalen = 126;
473 tab->tab_lock.l_whence = SEEK_CUR;
474 tab->tab_lock.l_start = 0;
475 tab->tab_lock.l_len = tab->tab_quotalen;
476
477 /* Open the table handle */
478 if ((tab->tab_handle = open(srcinfo, O_RDONLY)) < 0) {
479 destroy_pool(tab->tab_pool);
480 return NULL;
481 }
482 }
483
484 /* Set all the necessary function pointers. */
485 tab->tab_close = filetab_close;
486 tab->tab_create = filetab_create;
487 tab->tab_lookup = filetab_lookup;
488 tab->tab_read = filetab_read;
489 tab->tab_verify = filetab_verify;
490 tab->tab_write = filetab_write;
491
492 tab->tab_rlock = filetab_rlock;
493 tab->tab_unlock = filetab_unlock;
494 tab->tab_wlock = filetab_wlock;
495
496 return tab;
497 }
498
499 /* Event handlers
500 */
501
502 #if defined(PR_SHARED_MODULE)
filetab_mod_unload_ev(const void * event_data,void * user_data)503 static void filetab_mod_unload_ev(const void *event_data, void *user_data) {
504 if (strcmp("mod_quotatab_file.c", (const char *) event_data) == 0) {
505 pr_event_unregister("atab_file_module, NULL, NULL);
506 quotatab_unregister_backend("file", QUOTATAB_LIMIT_SRC|QUOTATAB_TALLY_SRC);
507 }
508 }
509 #endif /* PR_SHARED_MODULE */
510
511 /* Initialization routines
512 */
513
filetab_init(void)514 static int filetab_init(void) {
515
516 /* Initialize the quota source objects for type "file".
517 */
518 quotatab_register_backend("file", filetab_open,
519 QUOTATAB_LIMIT_SRC|QUOTATAB_TALLY_SRC);
520
521 #if defined(PR_SHARED_MODULE)
522 pr_event_register("atab_file_module, "core.module-unload",
523 filetab_mod_unload_ev, NULL);
524 #endif /* PR_SHARED_MODULE */
525
526 return 0;
527 }
528
529 module quotatab_file_module = {
530 NULL, NULL,
531
532 /* Module API version 2.0 */
533 0x20,
534
535 /* Module name */
536 "quotatab_file",
537
538 /* Module configuration handler table */
539 NULL,
540
541 /* Module command handler table */
542 NULL,
543
544 /* Module authentication handler table */
545 NULL,
546
547 /* Module initialization function */
548 filetab_init,
549
550 /* Module child initialization function */
551 NULL
552 };
553