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(&quotatab_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(&quotatab_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