source: src/router/proftpd/contrib/mod_quotatab.c @ 17876

Last change on this file since 17876 was 17876, checked in by BrainSlayer, 19 months ago

update proftp

File size: 121.4 KB
Line 
1/*
2 * ProFTPD: mod_quotatab -- a module for managing FTP byte/file quotas via
3 *                          centralized tables
4 *
5 * Copyright (c) 2001-2011 TJ Saunders
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20 *
21 * As a special exemption, TJ Saunders gives permission to link this program
22 * with OpenSSL, and distribute the resulting executable, without including
23 * the source code for OpenSSL in the source distribution.
24 *
25 * This is mod_quotatab, contrib software for proftpd 1.2 and above.
26 * For more information contact TJ Saunders <tj@castaglia.org>.  It is based
27 * the ideas in Eric Estabrook's mod_quota, available from
28 * ftp://pooh.urbanrage.com/pub/c/.  This module, however, has been written
29 * from scratch to implement quotas in a different way.
30 *
31 * $Id: mod_quotatab.c,v 1.76 2011/05/26 23:14:01 castaglia Exp $
32 */
33
34#include "mod_quotatab.h"
35
36typedef struct regtab_obj {
37  struct regtab_obj *prev, *next;
38 
39  /* Table source type name */
40  const char *regtab_name;
41
42  /* Initialization function for this type of table source */
43  quota_table_t *(*regtab_open)(pool *, quota_tabtype_t, const char *);
44
45  /* Flags for this type of table: QUOTATAB_LIMIT_SRC, QUOTATAB_TALLY_SRC.
46   * Used to restrict some sources to only certain table types (ie
47   * LDAP to limit tables).
48   */
49  unsigned int regtab_srcs;
50
51} quota_regtab_t;
52
53module quotatab_module;
54
55/* Quota objects for the current session */
56static quota_table_t *limit_tab = NULL;
57static quota_limit_t sess_limit;
58
59static quota_table_t *tally_tab = NULL;
60static quota_tally_t sess_tally;
61
62/* Memory pools for this module */
63static pool *quotatab_pool = NULL;
64static pool *quotatab_backend_pool = NULL;
65
66/* List of registered quotatab sources */
67static quota_regtab_t *quotatab_backends = NULL;
68static unsigned int quotatab_nbackends = 0;
69
70/* Logging data */
71static int quota_logfd = -1;
72static char *quota_logname = NULL;
73
74static unsigned char allow_site_quota = TRUE;
75static unsigned char use_dirs = FALSE;
76static unsigned char use_quotas = FALSE;
77static unsigned char have_quota_entry = FALSE;
78static unsigned char have_quota_limit_table = FALSE;
79static unsigned char have_quota_tally_table = FALSE;
80
81static unsigned long have_quota_update = 0;
82#define QUOTA_HAVE_READ_UPDATE                  10000
83#define QUOTA_HAVE_WRITE_UPDATE                 20000
84
85#ifdef PR_USE_REGEX
86static pr_regex_t *quota_exclude_pre = NULL;
87static const char *quota_exclude_filter = NULL;
88#endif
89
90static quota_units_t byte_units = BYTE;
91
92/* For transmitting number of bytes/files from PRE_CMD to POST_CMD handlers
93 * (for use by APPE, DELE, MKD, RMD, RNTO, STOR, STOU, XMKD, and XRMD
94 * commands)
95 */
96static off_t quotatab_disk_nbytes;
97static unsigned int quotatab_disk_nfiles;
98
99/* For handling deletes of files which not belong to us. */
100static struct stat quotatab_dele_st;
101static int quotatab_have_dele_st = FALSE;
102
103/* For locking (e.g. during tally creation). */
104static int quota_lockfd = -1;
105
106#define QUOTA_MAX_LOCK_ATTEMPTS         10
107
108/* Used to indicate whether a transfer was aborted via the ABORT command.
109 * This is needed, in conjunction with checking the SF_ABORT/SF_POST_ABORT
110 * session flags, since it is possible for clients to abort transfers without
111 * using the OOB signal byte.
112 */
113static unsigned char have_aborted_transfer = FALSE;
114
115/* It is the case where sometimes a command may be denied by a PRE_CMD
116 * handler of this module, in which case an appropriate error response is
117 * added to the response chain.  When the matching POST_CMD_ERR handler
118 * then runs, it will add another response.  Sometimes, however, the
119 * POST_CMD_ERR handlers may be run for other reasons, and the response
120 * added is not a duplicate.  Use this flag to signal when a response has
121 * already been added.
122 */
123static unsigned char have_err_response = FALSE;
124
125/* convenience macros */
126#define DISPLAY_BYTES_IN(x) \
127    quota_display_bytes((x)->tmp_pool, \
128    sess_tally.bytes_in_used, sess_limit.bytes_in_avail, IN)
129
130#define DISPLAY_BYTES_OUT(x) \
131    quota_display_bytes((x)->tmp_pool, \
132    sess_tally.bytes_out_used, sess_limit.bytes_out_avail, OUT)
133
134#define DISPLAY_BYTES_XFER(x) \
135    quota_display_bytes((x)->tmp_pool, \
136    sess_tally.bytes_xfer_used, sess_limit.bytes_xfer_avail, XFER)
137
138#define DISPLAY_FILES_IN(x) \
139    quota_display_files((x)->tmp_pool, \
140    sess_tally.files_in_used, sess_limit.files_in_avail, IN)
141
142#define DISPLAY_FILES_OUT(x) \
143    quota_display_files((x)->tmp_pool, \
144    sess_tally.files_out_used, sess_limit.files_out_avail, OUT)
145
146#define DISPLAY_FILES_XFER(x) \
147    quota_display_files((x)->tmp_pool, \
148    sess_tally.files_xfer_used, sess_limit.files_xfer_avail, XFER)
149
150#define QUOTATAB_TALLY_READ \
151  if (!sess_limit.quota_per_session) { \
152    if (quotatab_read(&sess_tally) < 0) { \
153      quotatab_log("error: unable to read tally: %s", strerror(errno)); \
154    } \
155  }
156
157#define QUOTATAB_TALLY_WRITE(bi, bo, bx, fi, fo, fx) \
158  { \
159    if (quotatab_write(&sess_tally, (bi), (bo), (bx), (fi), (fo), (fx)) < 0) { \
160      quotatab_log("error: unable to write tally: %s", strerror(errno)); \
161    } \
162    have_quota_update = 0; \
163  }
164
165static unsigned long quotatab_opts = 0UL;
166#define QUOTA_OPT_SCAN_ON_LOGIN         0x0001
167
168#define QUOTA_SCAN_FL_VERBOSE           0x0001
169
170/* necessary prototypes */
171MODRET quotatab_pre_stor(cmd_rec *);
172MODRET quotatab_post_stor(cmd_rec *);
173MODRET quotatab_post_stor_err(cmd_rec *);
174
175static int quotatab_rlock(quota_table_t *);
176static int quotatab_runlock(quota_table_t *);
177static int quotatab_wlock(quota_table_t *);
178static int quotatab_wunlock(quota_table_t *);
179
180/* Support routines
181 */
182
183/* Returns the most appropriate errno value for indicating a "quota exceeded"
184 * state, or -1 if there is no such appropriate errno.
185 */
186static int get_quota_exceeded_errno(int default_errno, char **errstr) {
187  int res;
188
189#if defined(EDQUOT)
190  res = EDQUOT;
191
192#elif defined(EFBIG)
193  res = EFBIG;
194
195#elif defined(ENOSPC)
196  res = ENOSPC;
197
198#else
199  res = default_errno;
200#endif
201
202  if (errstr != NULL) {
203    *errstr = strerror(res);
204  }
205
206  return res;
207}
208
209/* Quota units routines */
210
211static char *quota_display_bytes(pool *p, double bytes_used,
212    double bytes_avail, quota_xfer_t xfer_type) {
213  double adj_used = 0.0, adj_avail = 0.0;
214  char *display = NULL, *xferstr = NULL;
215  unsigned int display_len = 80;
216
217  display = pcalloc(p, display_len);
218
219  switch (xfer_type) {
220    case IN:
221      xferstr = _("upload");
222      break;
223
224    case OUT:
225      xferstr = _("download");
226      break;
227
228    case XFER:
229      xferstr = _("transfer");
230      break;
231
232    default:
233      break;
234  }
235
236  switch (byte_units) {
237
238    case BYTE:
239      /* no manipulation needed */
240      snprintf(display, display_len - 1, _("%.2f of %.2f %s %s"), bytes_used,
241        bytes_avail, xferstr, bytes_avail > 1.0 ? _("bytes") : _("byte"));
242      break;
243
244    case KILO:
245      /* divide by 1024.0 */
246      adj_used = (bytes_used / 1024.0);
247      adj_avail = (bytes_avail / 1024.0);
248      snprintf(display, display_len - 1, _("%.2f of %.2f %s Kb"), adj_used,
249        adj_avail, xferstr);
250      break;
251
252    case MEGA:
253      /* divide by 1024.0 * 1024.0 */
254      adj_used = (bytes_used / (1024.0 * 1024.0));
255      adj_avail = (bytes_avail / (1024.0 * 1024.0));
256      snprintf(display, display_len -1 , _("%.2f of %.2f %s Mb"), adj_used,
257        adj_avail, xferstr);
258      break;
259
260    case GIGA:
261      /* divide by 1024.0 * 1024.0 * 1024.0 */
262      adj_used = (bytes_used / (1024.0 * 1024.0 * 1024.0));
263      adj_avail = (bytes_avail / (1024.0 * 1024.0 * 1024.0));
264      snprintf(display, display_len - 1, _("%.2f of %.2f %s Gb"), adj_used,
265        adj_avail, xferstr);
266      break;
267
268    /* Always have a fallthrough case, even if it _should_ never be reached. */
269    default:
270      quotatab_log("warning: unknown QuotaDisplayUnits");
271      break;
272  }
273
274  return display;
275}
276
277static char *quota_display_files(pool *p, unsigned int files_used,
278    unsigned int files_avail, quota_xfer_t xfer_type) {
279  char *display = NULL, *xferstr = NULL;
280  unsigned int display_len = 80;
281
282  display = pcalloc(p, display_len);
283
284  switch (xfer_type) {
285    case IN:
286      xferstr = _("upload");
287      break;
288
289    case OUT:
290      xferstr = _("download");
291      break;
292
293    case XFER:
294      xferstr = _("transfer");
295      break;
296
297    default:
298      break;
299  }
300
301  snprintf(display, display_len - 1, _("%u of %u %s %s"), files_used,
302    files_avail, xferstr, files_avail > 1.0 ? _("files") : _("file"));
303
304  return display;
305}
306
307static char *quota_display_site_bytes(pool *p, double bytes_used,
308    double bytes_avail, quota_xfer_t xfer_type) {
309  double adj_used = 0.0, adj_avail = 0.0;
310  char *display = NULL;
311  unsigned int display_len = 80;
312
313  display = pcalloc(p, display_len);
314
315  switch (byte_units) {
316    case BYTE:
317      /* no calculation needed */
318
319      if (bytes_avail > 0.0)
320        snprintf(display, display_len - 1, _("bytes:\t%.2f/%.2f"), bytes_used,
321          bytes_avail);
322      else
323        snprintf(display, display_len - 1, _("bytes:\tunlimited"));
324      break;
325
326    case KILO:
327      /* divide by 1024.0 */
328      adj_used = (bytes_used / 1024.0);
329      adj_avail = (bytes_avail / 1024.0);
330
331      if (adj_avail > 0.0)
332        snprintf(display, display_len - 1, _("Kb:\t%s%.2f/%.2f"),
333          xfer_type != IN ? "" : "\t", adj_used, adj_avail);
334      else
335        snprintf(display, display_len - 1, _("Kb:\tunlimited"));
336      break;
337
338    case MEGA:
339      /* divide by 1024.0 * 1024.0 */
340      adj_used = (bytes_used / (1024.0 * 1024.0));
341      adj_avail = (bytes_avail / (1024.0 * 1024.0));
342
343      if (adj_avail > 0.0)
344        snprintf(display, display_len - 1, _("Mb:\t%s%.2f/%.2f"),
345          xfer_type != IN ? "" : "\t", adj_used, adj_avail);
346      else
347        snprintf(display, display_len - 1, _("Mb:\tunlimited"));
348      break;
349
350    case GIGA:
351      /* divide by 1024.0 * 1024.0 * 1024.0 */
352      adj_used = (bytes_used / (1024.0 * 1024.0 * 1024.0));
353      adj_avail = (bytes_avail / (1024.0 * 1024.0 * 1024.0));
354
355      if (adj_avail > 0.0)
356        snprintf(display, display_len - 1, _("Gb:\t%s%.2f/%.2f"),
357          xfer_type != IN ? "" : "\t", adj_used, adj_avail);
358      else
359        snprintf(display, display_len - 1, _("Gb:\tunlimited"));
360      break;
361
362    default:
363      quotatab_log("warning: unknown QuotaDisplayUnits");
364      break;
365  }
366
367  return display;
368}
369
370static char *quota_display_site_files(pool *p, unsigned int files_used,
371    unsigned int files_avail, quota_xfer_t xfer_type) {
372  char *display = NULL;
373  unsigned int display_len = 80;
374
375  display = pcalloc(p, display_len);
376
377  if (files_avail != 0)
378    snprintf(display, display_len - 1, _("files:\t%u/%u"), files_used,
379      files_avail);
380  else
381    snprintf(display, display_len - 1, _("files:\tunlimited"));
382
383  return display;
384}
385
386/* Quota logging routines */
387static int quotatab_closelog(void) {
388  /* sanity check */
389  if (quota_logfd != -1) {
390    close(quota_logfd);
391    quota_logfd = -1;
392    quota_logname = NULL;
393  }
394
395  return 0;
396}
397
398int quotatab_log(const char *fmt, ...) {
399  va_list msg;
400  int res;
401
402  /* sanity check */
403  if (!quota_logname)
404    return 0;
405
406  va_start(msg, fmt);
407  res = pr_log_vwritefile(quota_logfd, MOD_QUOTATAB_VERSION, fmt, msg);
408  va_end(msg);
409
410  return res;
411}
412
413int quotatab_openlog(void) {
414  int res = 0;
415
416  /* Sanity checks. */
417  if (quota_logname)
418    return 0;
419
420  quota_logname = get_param_ptr(main_server->conf, "QuotaLog", FALSE);
421  if (quota_logname == NULL)
422    return 0;
423
424  /* check for "none" */
425  if (strcasecmp(quota_logname, "none") == 0) {
426    quota_logname = NULL;
427    return 0;
428  }
429
430  pr_signals_block();
431  PRIVS_ROOT
432  res = pr_log_openfile(quota_logname, &quota_logfd, 0640);
433  PRIVS_RELINQUISH
434  pr_signals_unblock();
435
436  switch (res) {
437    case -1:
438      pr_log_pri(LOG_NOTICE, MOD_QUOTATAB_VERSION
439        ": unable to open QuotaLog '%s': %s", quota_logname, strerror(errno));
440      break;
441
442    case PR_LOG_WRITABLE_DIR:
443      pr_log_pri(LOG_NOTICE, MOD_QUOTATAB_VERSION
444        ": unable to open QuotaLog '%s': %s", quota_logname,
445        "World-writable directory");
446      break;
447
448    case PR_LOG_SYMLINK:
449      pr_log_pri(LOG_NOTICE, MOD_QUOTATAB_VERSION
450        ": unable to open QuotaLog '%s': %s", quota_logname, "Symbolic link");
451      break;
452  }
453
454  return res;
455}
456
457static int quotatab_close(quota_tabtype_t tab_type) {
458  int res = 0;
459
460  if (tab_type == TYPE_TALLY) {
461    res = tally_tab->tab_close(tally_tab);
462    tally_tab = NULL;
463
464  } else if (tab_type == TYPE_LIMIT) {
465    res = limit_tab->tab_close(limit_tab);
466    limit_tab = NULL;
467  }
468
469  return res;
470}
471
472static int quotatab_verify(quota_tabtype_t tab_type) {
473
474  /* Request the table source object to verify itself */
475  if (tab_type == TYPE_TALLY) {
476    if (tally_tab->tab_verify(tally_tab))
477      return TRUE;
478    else
479      quotatab_log("error: unable to use QuotaTallyTable: bad table header");
480
481  } else if (tab_type == TYPE_LIMIT) {
482    if (limit_tab->tab_verify(limit_tab))
483      return TRUE;
484    else
485      quotatab_log("error: unable to use QuotaLimitTable: bad table header");
486  }
487
488  return FALSE;
489}
490
491static int quotatab_create(quota_tally_t *tally) {
492
493  /* Creation of an entry only ever occurs in the tally table -- the limit
494   * table is considered read-only.
495   */
496
497  /* Obtain a writer lock for the entry in question. */
498  if (quotatab_wlock(tally_tab) < 0)
499    return FALSE;
500
501  if (tally_tab->tab_create(tally_tab, tally) < 0) {
502    quotatab_wunlock(tally_tab);
503    return FALSE;
504  }
505
506  /* Release the lock */
507  if (quotatab_wunlock(tally_tab) < 0)
508    return FALSE;
509
510  return TRUE;
511}
512
513static int quotatab_create_tally(void) {
514  int res = TRUE;
515
516  memset(sess_tally.name, '\0', sizeof(sess_tally.name));
517  snprintf(sess_tally.name, sizeof(sess_tally.name), "%s",
518    sess_limit.name);
519  sess_tally.name[sizeof(sess_tally.name)-1] = '\0';
520
521  sess_tally.quota_type = sess_limit.quota_type;
522
523  /* Initial tally values. */
524  sess_tally.bytes_in_used = 0.0F;
525  sess_tally.bytes_out_used = 0.0F;
526  sess_tally.bytes_xfer_used = 0.0F;
527  sess_tally.files_in_used = 0U;
528  sess_tally.files_out_used = 0U;
529  sess_tally.files_xfer_used = 0U;
530
531  quotatab_log("creating new tally entry to match limit entry");
532
533  res = quotatab_create(&sess_tally);
534  if (res == FALSE) {
535    quotatab_log("error: unable to create tally entry: %s",
536      strerror(errno));
537  }
538
539  return res;
540}
541
542static quota_regtab_t *quotatab_get_backend(const char *backend,
543    unsigned int srcs) {
544  quota_regtab_t *regtab;
545
546  for (regtab = quotatab_backends; regtab; regtab = regtab->next) {
547    if ((regtab->regtab_srcs & srcs) &&
548        strcmp(regtab->regtab_name, backend) == 0) {
549      return regtab;
550    }
551  }
552
553  errno = ENOENT;
554  return NULL;
555}
556
557/* Returns TRUE if the given path is to be ignored, FALSE otherwise. */
558static int quotatab_ignore_path(pool *p, const char *path) {
559  char *abs_path;
560
561  if (path == NULL) {
562    return FALSE;
563  }
564
565#ifdef PR_USE_REGEX
566  if (quota_exclude_pre == NULL) {
567    return FALSE;
568  }
569
570  /* Always apply the filter to the absolute path. */
571  abs_path = dir_abs_path(p, path, TRUE);
572  if (abs_path == NULL) {
573    quotatab_log("unable to resolve absolute path for '%s': %s", path,
574      strerror(errno));
575    abs_path = (char *) path;
576  }
577
578  if (pr_regexp_exec(quota_exclude_pre, abs_path, 0, NULL, 0, 0, 0) == 0) {
579    return TRUE;
580  }
581
582  return FALSE;
583#else
584  return FALSE;
585#endif
586}
587
588static int quotatab_scan_dir(pool *p, const char *path, uid_t uid,
589    gid_t gid, int flags, double *nbytes, unsigned int *nfiles) {
590  struct stat st;
591  DIR *dirh;
592  struct dirent *dent;
593
594  if (!nbytes ||
595      !nfiles) {
596    errno = EINVAL;
597    return -1;
598  }
599
600  if (quotatab_ignore_path(p, path)) {
601    quotatab_log("path '%s' matches QuotaExcludeFilter '%s', ignoring",
602      path, quota_exclude_filter);
603    return 0;
604  }
605
606  if (pr_fsio_lstat(path, &st) < 0) {
607    return -1;
608  }
609
610  if (!S_ISDIR(st.st_mode)) {
611    errno = EINVAL;
612    return -1;
613  }
614
615  dirh = pr_fsio_opendir(path);
616  if (dirh == NULL) {
617    return -1;
618  }
619
620  if (use_dirs) {
621    if (uid != (uid_t) -1 ||
622        gid != (gid_t) -1) {
623
624      if (uid != (uid_t) -1 &&
625          st.st_uid == uid) {
626        *nbytes += st.st_size;
627        *nfiles += 1;
628
629      } else if (gid != (gid_t) -1 &&
630                 st.st_gid == gid) {
631        *nbytes += st.st_size;
632        *nfiles += 1;
633      }
634
635    } else {
636      *nbytes += st.st_size;
637      *nfiles += 1;
638    }
639  }
640
641  while ((dent = pr_fsio_readdir(dirh)) != NULL) {
642    char *file;
643
644    pr_signals_handle();
645
646    if (strcmp(dent->d_name, ".") == 0 ||
647        strcmp(dent->d_name, "..") == 0) {
648      continue;
649    }
650
651    file = pdircat(p, path, dent->d_name, NULL);
652
653    memset(&st, 0, sizeof(st));
654    if (pr_fsio_lstat(file, &st) < 0) {
655      quotatab_log("unable to lstat '%s': %s", file, strerror(errno));
656      continue;
657    }
658
659    if (S_ISREG(st.st_mode) ||
660        S_ISLNK(st.st_mode)) {
661
662      if (uid != (uid_t) -1 ||
663          gid != (gid_t) -1) {
664        if (uid != (uid_t) -1 &&
665            st.st_uid == uid) {
666          *nbytes += st.st_size;
667          *nfiles += 1;
668 
669        } else if (gid != (gid_t) -1 &&
670                   st.st_gid == gid) {
671          *nbytes += st.st_size;
672          *nfiles += 1;
673        }
674
675      } else {
676        *nbytes += st.st_size;
677        *nfiles += 1;
678      }
679
680    } else if (S_ISDIR(st.st_mode)) {
681      pool *sub_pool;
682
683      sub_pool = make_sub_pool(p);
684
685      if (quotatab_scan_dir(sub_pool, file, uid, gid, flags, nbytes,
686          nfiles) < 0) {
687        quotatab_log("error scanning '%s': %s", file, strerror(errno));
688      }
689
690      destroy_pool(sub_pool);
691
692    } else {
693      if (flags & QUOTA_SCAN_FL_VERBOSE) {
694        quotatab_log("file '%s' is not a file, symlink, or directory; skipping",
695          file);
696      }
697    }
698  }
699
700  pr_fsio_closedir(dirh);
701  return 0;
702}
703
704static int quotatab_open(quota_tabtype_t tab_type) {
705
706  if (tab_type == TYPE_TALLY) {
707    register config_rec *c = NULL;
708    register quota_regtab_t *regtab = NULL;
709
710    c = find_config(main_server->conf, CONF_PARAM, "QuotaTallyTable", FALSE);
711    if (!c) {
712      quotatab_log("notice: no QuotaTallyTable configured");
713      return -1;
714    }
715
716    regtab = quotatab_get_backend(c->argv[0], QUOTATAB_TALLY_SRC);
717    if (regtab) {
718      tally_tab = regtab->regtab_open(quotatab_pool, TYPE_TALLY, c->argv[1]);
719      if (!tally_tab)
720        return -1;
721
722    } else {
723      quotatab_log("error: unsupported tally table type: '%s'",
724        (const char *) c->argv[0]);
725      return -1;
726    }
727
728  } else if (tab_type == TYPE_LIMIT) {
729    register config_rec *c = NULL;
730    register quota_regtab_t *regtab = NULL;
731
732    c = find_config(main_server->conf, CONF_PARAM, "QuotaLimitTable", FALSE);
733    if (!c) {
734      quotatab_log("notice: no QuotaLimitTable configured");
735      return -1;
736    }
737
738    /* Look up the table source open routine by name, and invoke it */
739    regtab = quotatab_get_backend(c->argv[0], QUOTATAB_LIMIT_SRC);
740    if (regtab) {
741      limit_tab = regtab->regtab_open(quotatab_pool, TYPE_LIMIT, c->argv[1]);
742      if (!limit_tab)
743        return -1;
744
745    } else {
746      quotatab_log("error: unsupported limit table type: '%s'",
747        (const char *) c->argv[0]);
748      return -1;
749    }
750  }
751
752  return 0;
753}
754
755/* Reads via this function are only ever done on tally tables.  Limit tables
756 * are read via the quotatab_lookup() function.
757 */
758int quotatab_read(quota_tally_t *tally) {
759  int bread = 0;
760
761  /* Make sure the tally table can support reads. */
762  if (!tally_tab || !tally_tab->tab_read) {
763    errno = EPERM;
764    return -1;
765  }
766
767  /* Obtain a reader lock for the entry in question. */
768  if (quotatab_rlock(tally_tab) < 0) {
769    quotatab_log("error: unable to obtain read lock: %s", strerror(errno));
770    return -1;
771  }
772
773  /* Read from the current point in the stream enough information to populate
774   * the quota structs.
775   */
776  bread = tally_tab->tab_read(tally_tab, tally);
777  if (bread < 0) {
778    quotatab_runlock(tally_tab);
779    return -1;
780  }
781
782  /* Release the lock */
783  if (quotatab_runlock(tally_tab) < 0) {
784    quotatab_log("error: unable to release read lock: %s", strerror(errno));
785    return -1;
786  }
787
788  return bread;
789}
790
791/* This function is used by mod_quotatab backends, to register their
792 * individual backend table function pointers with the main mod_quotatab
793 * module.
794 */
795int quotatab_register_backend(const char *backend,
796    quota_table_t *(*srcopen)(pool *, quota_tabtype_t, const char *),
797    unsigned int srcs) {
798  quota_regtab_t *regtab;
799
800  if (!backend || !srcopen) {
801    errno = EINVAL;
802    return -1;
803  }
804
805  if (!quotatab_backend_pool) {
806    quotatab_backend_pool = make_sub_pool(permanent_pool);
807    pr_pool_tag(quotatab_backend_pool, MOD_QUOTATAB_VERSION ": Backend Pool");
808  }
809
810  /* Check to see if this backend has already been registered. */
811  regtab = quotatab_get_backend(backend, srcs);
812  if (regtab) {
813    errno = EEXIST;
814    return -1;
815  }
816
817  regtab = pcalloc(quotatab_backend_pool, sizeof(quota_regtab_t));
818  regtab->regtab_name = pstrdup(quotatab_backend_pool, backend);
819  regtab->regtab_open = srcopen;
820  regtab->regtab_srcs = srcs;
821
822  /* Add this object to the list. */
823  if (quotatab_backends) {
824    quotatab_backends->prev = regtab;
825    regtab->next = quotatab_backends;
826  }
827
828  quotatab_backends = regtab;
829  quotatab_nbackends++;
830
831  return 0;
832}
833
834/* Used by mod_quotatab backends to unregister their backend function pointers
835 * from the main mod_quotatab module.
836 */
837int quotatab_unregister_backend(const char *backend, unsigned int srcs) {
838  quota_regtab_t *regtab;
839
840  if (!backend) {
841    errno = EINVAL;
842    return -1;
843  }
844
845  /* Check to see if this backend has been registered. */
846  regtab = quotatab_get_backend(backend, srcs);
847  if (!regtab) {
848    errno = ENOENT;
849    return -1;
850  }
851
852#if !defined(PR_SHARED_MODULE)
853  /* If there is only one registered backend, it cannot be removed.
854   */
855  if (quotatab_nbackends == 1) {
856    errno = EPERM;
857    return -1;
858  }
859#endif
860
861  /* Remove this backend from the linked list. */
862  if (regtab->prev)
863    regtab->prev->next = regtab->next;
864  else
865    /* This backend is the start of the quotatab_backends list (prev is NULL),
866     * so we need to update the list head pointer as well.
867     */
868    quotatab_backends = regtab->next;
869
870  if (regtab->next)
871    regtab->next->prev = regtab->prev;
872
873  regtab->prev = regtab->next = NULL;
874
875  quotatab_nbackends--;
876
877  /* NOTE: a counter should be kept of the number of unregistrations,
878   * as the memory for a registration is not freed on unregistration.
879   */
880
881  return 0;
882}
883
884/* Note: this function will only find the first occurrence of the given
885 *  name and type in the table.  This means that if there is a malformed
886 *  quota table, with duplicate name/type pairs, the duplicates will be
887 *  ignored.
888 */
889unsigned char quotatab_lookup(quota_tabtype_t tab_type, void *ptr,
890    const char *name, quota_type_t quota_type) {
891
892  if (tab_type == TYPE_TALLY) {
893
894    /* Make sure the requested table can do lookups. */
895    if (!tally_tab ||
896        !tally_tab->tab_lookup) {
897      errno = EPERM;
898      return FALSE;
899    }
900
901    return tally_tab->tab_lookup(tally_tab, ptr, name, quota_type);
902 
903  } else if (tab_type == TYPE_LIMIT) {
904
905    /* Make sure the requested table can do lookups. */
906    if (!limit_tab ||
907        !limit_tab->tab_lookup) {
908      errno = EPERM;
909      return FALSE;
910    }
911
912    return limit_tab->tab_lookup(limit_tab, ptr, name, quota_type);
913  }
914
915  errno = ENOENT;
916  return FALSE;
917}
918
919static int quotatab_mutex_lock(int lock_type) {
920  const char *lock_desc;
921  struct flock lock;
922  unsigned int nattempts = 1;
923
924  if (quota_lockfd < 0)
925    return 0;
926
927  lock.l_type = lock_type;
928  lock.l_whence = SEEK_SET;
929  lock.l_start = 0;
930  lock.l_len = 0;
931
932  lock_desc = (lock.l_type == F_WRLCK ? "write-lock" : "unlock");
933
934  pr_trace_msg("lock", 9, "attempting to %s QuotaLock fd %d", lock_desc,
935    quota_lockfd);
936
937  while (fcntl(quota_lockfd, F_SETLK, &lock) < 0) {
938    int xerrno = errno;
939
940    if (xerrno == EINTR) {
941      pr_signals_handle();
942      continue;
943    }
944
945    pr_trace_msg("lock", 3, "%s of QuotaLock fd %d failed: %s",
946      lock_desc, quota_lockfd, strerror(xerrno));
947 
948    if (xerrno == EACCES) {
949      struct flock locker;
950
951      /* Get the PID of the process blocking this lock. */
952      if (fcntl(quota_lockfd, F_GETLK, &locker) == 0) {
953        pr_trace_msg("lock", 3, "process ID %lu has blocking %s on "
954          "QuotaLock fd %d", (unsigned long) locker.l_pid,
955          (locker.l_type == F_WRLCK ? "write-lock" :
956           locker.l_type == F_RDLCK ? "read-lock" : "unlock"),
957          quota_lockfd);
958      }
959    }
960
961    if (xerrno == EAGAIN ||
962        xerrno == EACCES) {
963      /* Treat this as an interrupted call, call pr_signals_handle() (which
964       * will delay for a few msecs because of EINTR), and try again.
965       * After QUOTA_MAX_LOCK_ATTEMPTS attempts, give up altogether.
966       */
967
968      nattempts++;
969      if (nattempts <= QUOTA_MAX_LOCK_ATTEMPTS) {
970        errno = EINTR;
971
972        pr_signals_handle();
973
974        errno = 0;
975        continue;
976      }
977
978      quotatab_log("unable to acquire %s lock on QuotaLock for user '%s': %s",
979        lock_desc, session.user, strerror(xerrno));
980      errno = xerrno;
981      return -1;
982    }
983  }
984
985  pr_trace_msg("lock", 9, "%s of QuotaLock fd %d succeeded", lock_desc,
986    quota_lockfd);
987  return 0;
988}
989
990static int quotatab_rlock(quota_table_t *tab) {
991
992  if (tab->rlock_count == 0 &&
993      tab->wlock_count == 0) {
994    unsigned int nattempts = 1;
995
996    tab->tab_lockfd = quota_lockfd;
997
998    pr_trace_msg("lock", 9, "attempting to read-lock QuotaLock fd %d",
999      quota_lockfd);
1000   
1001    while (tab->tab_rlock(tab) < 0) {
1002      int xerrno = errno;
1003
1004      if (xerrno == EINTR) {
1005        pr_signals_handle();
1006        continue;
1007      }
1008
1009      if (xerrno == EACCES) {
1010        struct flock locker;
1011
1012        /* Get the PID of the process blocking this lock. */
1013        if (fcntl(quota_lockfd, F_GETLK, &locker) == 0) {
1014          pr_trace_msg("lock", 3, "process ID %lu has blocking %s on "
1015            "QuotaLock fd %d", (unsigned long) locker.l_pid,
1016            (locker.l_type == F_WRLCK ? "write-lock" :
1017             locker.l_type == F_RDLCK ? "read-lock" : "unlock"),
1018            quota_lockfd);
1019        }
1020      }
1021
1022      if (xerrno == EAGAIN ||
1023          xerrno == EACCES) {
1024        /* Treat this as an interrupted call, call pr_signals_handle() (which
1025         * will delay for a few msecs because of EINTR), and try again.
1026         * After QUOTA_MAX_LOCK_ATTEMPTS attempts, give up altogether.
1027         */
1028
1029        nattempts++;
1030        if (nattempts <= QUOTA_MAX_LOCK_ATTEMPTS) {
1031          errno = EINTR;
1032
1033          pr_signals_handle();
1034
1035          errno = 0;
1036          continue;
1037        }
1038      }
1039
1040      quotatab_log("unable to acquire read lock on QuotaLock for user '%s': %s",
1041        session.user, strerror(xerrno));
1042      errno = xerrno;
1043      return -1;
1044    }
1045  }
1046
1047  tab->rlock_count++;
1048  return 0;
1049}
1050
1051static int quotatab_runlock(quota_table_t *tab) {
1052  int res = 0;
1053
1054  if (tab->rlock_count == 1 &&
1055      tab->wlock_count == 0) {
1056    tab->tab_lockfd = quota_lockfd;
1057
1058    while (tab->tab_unlock(tab) < 0) {
1059      int xerrno = errno;
1060
1061      if (xerrno == EINTR) {
1062        pr_signals_handle();
1063        continue;
1064      }
1065
1066      res = -1;
1067      break;
1068    }
1069  }
1070
1071  if (res == 0 &&
1072      tab->rlock_count > 0) {
1073    tab->rlock_count--;
1074  }
1075
1076  return res;
1077}
1078
1079static int quotatab_wlock(quota_table_t *tab) {
1080
1081  if (tab->wlock_count == 0) {
1082    unsigned int nattempts = 1;
1083
1084    tab->tab_lockfd = quota_lockfd;
1085
1086    pr_trace_msg("lock", 9, "attempting to write-lock QuotaLock fd %d",
1087      quota_lockfd);
1088
1089    while (tab->tab_wlock(tab) < 0) {
1090      int xerrno = errno;
1091
1092      if (xerrno == EINTR) {
1093        pr_signals_handle();
1094        continue;
1095      }
1096
1097      if (xerrno == EACCES) {
1098        struct flock locker;
1099
1100        /* Get the PID of the process blocking this lock. */
1101        if (fcntl(quota_lockfd, F_GETLK, &locker) == 0) {
1102          pr_trace_msg("lock", 3, "process ID %lu has blocking %s on "
1103            "QuotaLock fd %d", (unsigned long) locker.l_pid,
1104            (locker.l_type == F_WRLCK ? "write-lock" :
1105             locker.l_type == F_RDLCK ? "read-lock" : "unlock"),
1106            quota_lockfd);
1107        }
1108      }
1109
1110      if (xerrno == EAGAIN ||
1111          xerrno == EACCES) {
1112        /* Treat this as an interrupted call, call pr_signals_handle() (which
1113         * will delay for a few msecs because of EINTR), and try again.
1114         * After QUOTA_MAX_LOCK_ATTEMPTS attempts, give up altogether.
1115         */
1116
1117        nattempts++;
1118        if (nattempts <= QUOTA_MAX_LOCK_ATTEMPTS) {
1119          errno = EINTR;
1120
1121          pr_signals_handle();
1122
1123          errno = 0;
1124          continue;
1125        }
1126      }
1127
1128      quotatab_log("unable to acquire write lock on QuotaLock for "
1129        "user '%s': %s", session.user, strerror(xerrno));
1130      errno = xerrno;
1131      return -1;
1132    }
1133  }
1134
1135  tab->wlock_count++;
1136  return 0;
1137}
1138
1139static int quotatab_wunlock(quota_table_t *tab) {
1140  int res = 0;
1141
1142  /* A write-lock may need to be "downgraded" into a read-lock, depending
1143   * on the other lock attempts that have been made.
1144   */
1145  if (tab->wlock_count == 1) {
1146    tab->tab_lockfd = quota_lockfd;
1147
1148    if (tab->rlock_count == 0) {
1149      /* The write-lock is the only lock left on the table; do a full unlock. */
1150      while (tab->tab_unlock(tab) < 0) {
1151        int xerrno = errno;
1152
1153        if (xerrno == EINTR) {
1154          pr_signals_handle();
1155          continue;
1156        }
1157
1158        res = -1;
1159        break;
1160      }
1161
1162    } else {
1163      /* We have some read-locks on the table; downgrade the write-lock to
1164       * a read-lock.  The call to the tab_rlock() callback does NOT go
1165       * through the quotatab_rlock() function, which means it does not affect
1166       * the table lock counters; this is by design.
1167       */
1168      res = tab->tab_rlock(tab);
1169    }
1170  }
1171
1172  if (res == 0 &&
1173      tab->wlock_count > 0) {
1174    tab->wlock_count--;
1175  }
1176
1177  return res;
1178}
1179
1180int quotatab_write(quota_tally_t *tally,
1181    double bytes_in_inc, double bytes_out_inc, double bytes_xfer_inc,
1182    int files_in_inc, int files_out_inc, int files_xfer_inc) {
1183
1184  /* Make sure the tally table can support writes. */
1185  if (!tally_tab || !tally_tab->tab_write) {
1186    errno = EPERM;
1187    return -1;
1188  }
1189
1190  /* Obtain a writer lock for the entry in question */
1191  if (quotatab_wlock(tally_tab) < 0) {
1192    quotatab_log("error: unable to obtain write lock: %s", strerror(errno));
1193    return -1;
1194  }
1195
1196  /* Make sure the deltas are cleared. */
1197  memset(&quotatab_deltas, '\0', sizeof(quotatab_deltas));
1198
1199  /* Read in the tally (to catch any possible updates by other processes). */
1200  QUOTATAB_TALLY_READ
1201
1202  /* Only update the tally if the value is not "unlimited". */
1203  if (sess_limit.bytes_in_avail > 0.0) {
1204    sess_tally.bytes_in_used += bytes_in_inc;
1205
1206    /* Prevent underflows. */
1207    if (sess_tally.bytes_in_used < 0.0)
1208      sess_tally.bytes_in_used = 0.0;
1209
1210    quotatab_deltas.bytes_in_delta = bytes_in_inc;
1211  }
1212
1213  /* Only update the tally if the value is not "unlimited". */
1214  if (sess_limit.bytes_out_avail > 0.0) {
1215    sess_tally.bytes_out_used += bytes_out_inc;
1216
1217    /* Prevent underflows. */
1218    if (sess_tally.bytes_out_used < 0.0)
1219      sess_tally.bytes_out_used = 0.0;
1220
1221    quotatab_deltas.bytes_out_delta = bytes_out_inc;
1222  }
1223
1224  /* Only update the tally if the value is not "unlimited". */
1225  if (sess_limit.bytes_xfer_avail > 0.0) {
1226    sess_tally.bytes_xfer_used += bytes_xfer_inc;
1227
1228    /* Prevent underflows. */
1229    if (sess_tally.bytes_xfer_used < 0.0)
1230      sess_tally.bytes_xfer_used = 0.0;
1231
1232    quotatab_deltas.bytes_xfer_delta = bytes_xfer_inc;
1233  }
1234
1235  /* Only update the tally if the value is not "unlimited". */
1236  if (sess_limit.files_in_avail != 0) {
1237
1238    /* Prevent underflows. As this is an unsigned data type, the
1239     * underflow check is not as straightforward as checking for a value
1240     * less than zero.
1241     */
1242    if (!(sess_tally.files_in_used == 0 && files_in_inc < 0))
1243      sess_tally.files_in_used += files_in_inc;
1244
1245    quotatab_deltas.files_in_delta = files_in_inc;
1246  }
1247
1248  /* Only update the tally if the value is not "unlimited". */
1249  if (sess_limit.files_out_avail != 0) {
1250
1251    /* Prevent underflows. As this is an unsigned data type, the
1252     * underflow check is not as straightforward as checking for a value
1253     * less than zero.
1254     */
1255    if (!(sess_tally.files_out_used == 0 && files_out_inc < 0))
1256      sess_tally.files_out_used += files_out_inc;
1257
1258    quotatab_deltas.files_out_delta = files_out_inc;
1259  }
1260
1261  /* Only update the tally if the value is not "unlimited". */
1262  if (sess_limit.files_xfer_avail != 0) {
1263
1264    /* Prevent underflows. As this is an unsigned data type, the
1265     * underflow check is not as straightforward as checking for a value
1266     * less than zero.
1267     */
1268    if (!(sess_tally.files_xfer_used == 0 && files_xfer_inc < 0))
1269      sess_tally.files_xfer_used += files_xfer_inc;
1270
1271    quotatab_deltas.files_xfer_delta = files_xfer_inc;
1272  }
1273
1274  /* No need to write out to the stream if per-session quotas are in effect. */
1275  if (sess_limit.quota_per_session) {
1276    memset(&quotatab_deltas, '\0', sizeof(quotatab_deltas));
1277    quotatab_wunlock(tally_tab);
1278    return 0;
1279  }
1280
1281  if (tally_tab->tab_write(tally_tab, tally) < 0) {
1282    quotatab_log("error: unable to update tally entry: %s", strerror(errno));
1283    quotatab_wunlock(tally_tab);
1284    memset(&quotatab_deltas, '\0', sizeof(quotatab_deltas));
1285    return -1;
1286  }
1287
1288  /* Release the lock */
1289  if (quotatab_wunlock(tally_tab) < 0) {
1290    quotatab_log("error: unable to release write lock: %s", strerror(errno));
1291    memset(&quotatab_deltas, '\0', sizeof(quotatab_deltas));
1292    return -1;
1293  }
1294
1295  memset(&quotatab_deltas, '\0', sizeof(quotatab_deltas));
1296  return 0;
1297}
1298
1299/* FSIO handlers
1300 */
1301
1302static int quotatab_fsio_write(pr_fh_t *fh, int fd, const char *buf,
1303    size_t bufsz) {
1304  int res;
1305
1306  res = write(fd, buf, bufsz);
1307  if (res < 0)
1308    return res;
1309
1310  /* Check to see if we've exceeded our upload limit.  mod_xfer will
1311   * have called pr_data_xfer(), which will have updated
1312   * session.xfer.total_bytes, before calling pr_fsio_write(), so
1313   * we do not have to worry about updated/changing session.xfer.total_bytes
1314   * ourselves.
1315   *
1316   * Note that there is a race condition here: it is possible for the same
1317   * user to be writing to the same file in chunks from multiple
1318   * simultaneous connections.
1319   */
1320
1321  if (sess_limit.bytes_in_avail > 0.0 &&
1322      sess_tally.bytes_in_used + session.xfer.total_bytes > sess_limit.bytes_in_avail) {
1323    int xerrno;
1324    char *errstr = NULL;
1325
1326    xerrno = get_quota_exceeded_errno(EIO, &errstr);
1327    quotatab_log("quotatab write(): limit exceeded, returning %s", errstr);
1328
1329    errno = xerrno;
1330    return -1;
1331  }
1332
1333  if (sess_limit.bytes_xfer_avail > 0.0 &&
1334      sess_tally.bytes_xfer_used + session.xfer.total_bytes > sess_limit.bytes_xfer_avail) {
1335    int xerrno;
1336    char *errstr = NULL;
1337
1338    xerrno = get_quota_exceeded_errno(EIO, &errstr);
1339    quotatab_log("quotatab write(): transfer limit exceeded, returning %s",
1340      errstr);
1341
1342    errno = xerrno;
1343    return -1;
1344  }
1345
1346  return res;
1347}
1348
1349/* Configuration handlers
1350 */
1351
1352/* usage: QuotaDirectoryTally <on|off> */
1353MODRET set_quotadirtally(cmd_rec *cmd) {
1354  int bool = -1;
1355  config_rec *c = NULL;
1356
1357  CHECK_ARGS(cmd, 1);
1358  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1359
1360  bool = get_boolean(cmd, 1);
1361  if (bool == -1)
1362    CONF_ERROR(cmd, "expected boolean argument");
1363
1364  c = add_config_param(cmd->argv[0], 1, NULL);
1365  c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
1366  *((unsigned char *) c->argv[0]) = (unsigned char) bool;
1367
1368  return PR_HANDLED(cmd);
1369}
1370
1371/* usage: QuotaDisplayUnits <b|Kb|Mb|Gb> */
1372MODRET set_quotadisplayunits(cmd_rec *cmd) {
1373  quota_units_t units = BYTE;
1374  config_rec *c = NULL;
1375
1376  CHECK_ARGS(cmd, 1);
1377  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1378
1379  if (strcasecmp(cmd->argv[1], "b") == 0) {
1380    /* No need to assign to the units variable, as it defaults to
1381     * displaying bytes
1382     */
1383    ;
1384
1385  } else if (strcasecmp(cmd->argv[1], "Kb") == 0) {
1386    units = KILO;
1387
1388  } else if (strcasecmp(cmd->argv[1], "Mb") == 0) {
1389    units = MEGA;
1390
1391  } else if (strcasecmp(cmd->argv[1], "Gb") == 0) {
1392    units = GIGA;
1393
1394  } else
1395    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown display units: ",
1396      cmd->argv[1], NULL));
1397
1398  c = add_config_param(cmd->argv[0], 1, NULL);
1399  c->argv[0] = palloc(c->pool, sizeof(quota_units_t));
1400  *((quota_units_t *) c->argv[0]) = units;
1401
1402  return PR_HANDLED(cmd);
1403}
1404
1405/* usage: QuotaEngine <on|off> */
1406MODRET set_quotaengine(cmd_rec *cmd) {
1407  int bool = -1;
1408  config_rec *c = NULL;
1409
1410  CHECK_ARGS(cmd, 1);
1411  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1412
1413  bool = get_boolean(cmd, 1);
1414  if (bool == -1)
1415    CONF_ERROR(cmd, "expected boolean argument");
1416
1417  c = add_config_param(cmd->argv[0], 1, NULL);
1418  c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
1419  *((unsigned char *) c->argv[0]) = (unsigned char) bool;
1420
1421  return PR_HANDLED(cmd);
1422}
1423
1424/* usage: QuotaExcludeFilter regex|"none" */
1425MODRET set_quotaexcludefilter(cmd_rec *cmd) {
1426#ifdef PR_USE_REGEX
1427  pr_regex_t *pre = NULL;
1428  config_rec *c;
1429  int res;
1430
1431  CHECK_ARGS(cmd, 1);
1432  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1433
1434  if (strcasecmp(cmd->argv[1], "none") == 0) {
1435    add_config_param(cmd->argv[0], 0);
1436    return PR_HANDLED(cmd);
1437  }
1438
1439  pre = pr_regexp_alloc(&quotatab_module);
1440
1441  res = pr_regexp_compile(pre, cmd->argv[1], REG_EXTENDED|REG_NOSUB);
1442  if (res != 0) {
1443    char errstr[256] = {'\0'};
1444
1445    pr_regexp_error(res, pre, errstr, sizeof(errstr));
1446    pr_regexp_free(NULL, pre);
1447
1448    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "'", cmd->argv[1], "' failed regex "
1449      "compilation: ", errstr, NULL));
1450  }
1451
1452  c = add_config_param(cmd->argv[0], 2, NULL, NULL);
1453  c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
1454  c->argv[1] = (void *) pre;
1455  return PR_HANDLED(cmd);
1456
1457#else
1458  CONF_ERROR(cmd, "The QuotaExcludeFilter directive cannot be used on this "
1459    "system, as you do not have POSIX compliant regex support");
1460#endif
1461}
1462
1463/* usage: QuotaLock file */
1464MODRET set_quotalock(cmd_rec *cmd) {
1465  CHECK_ARGS(cmd, 1);
1466  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1467
1468  /* Check for non-absolute paths */
1469  if (*cmd->argv[1] != '/')
1470    CONF_ERROR(cmd, "absolute path required");
1471
1472  add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
1473
1474  return PR_HANDLED(cmd);
1475}
1476
1477/* usage: QuotaLog path|"none" */
1478MODRET set_quotalog(cmd_rec *cmd) {
1479  CHECK_ARGS(cmd, 1);
1480  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1481
1482  add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
1483
1484  return PR_HANDLED(cmd);
1485}
1486
1487/* usage: QuotaOptions opt1 opt2 ... */
1488MODRET set_quotaoptions(cmd_rec *cmd) {
1489  config_rec *c;
1490  register unsigned int i;
1491  unsigned long opts = 0UL;
1492
1493  if (cmd->argc-1 == 0)
1494    CONF_ERROR(cmd, "wrong number of parameters");
1495
1496  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1497
1498  c = add_config_param(cmd->argv[0], 1, NULL);
1499
1500  for (i = 1; i < cmd->argc; i++) {
1501    if (strcmp(cmd->argv[i], "ScanOnLogin") == 0) {
1502      opts |= QUOTA_OPT_SCAN_ON_LOGIN;
1503
1504    } else {
1505      CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown QuotaOption: '",
1506        cmd->argv[i], "'", NULL));
1507    }
1508  }
1509
1510  c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
1511  *((unsigned long *) c->argv[0]) = opts;
1512
1513  return PR_HANDLED(cmd);
1514}
1515
1516/* usage: QuotaShowQuotas <on|off> */
1517MODRET set_quotashowquotas(cmd_rec *cmd) {
1518  int bool = -1;
1519  config_rec *c = NULL;
1520
1521  CHECK_ARGS(cmd, 1);
1522  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1523
1524  bool = get_boolean(cmd, 1);
1525  if (bool == -1)
1526    CONF_ERROR(cmd, "expected boolean argument");
1527
1528  c = add_config_param(cmd->argv[0], 1, NULL);
1529  c->argv[0] = pcalloc(c->pool, sizeof(unsigned char));
1530  *((unsigned char *) c->argv[0]) = (unsigned char) bool;
1531
1532  return PR_HANDLED(cmd);
1533}
1534
1535/* usage: Quota{Limit,Tally}Table <source-type:source-info> */
1536MODRET set_quotatable(cmd_rec *cmd) {
1537  char *tmp = NULL;
1538  unsigned int tabflag = 0;
1539#if !defined(PR_SHARED_MODULE)
1540  register quota_regtab_t *regtab = NULL;
1541#endif /* PR_SHARED_MODULE */
1542
1543  CHECK_ARGS(cmd, 1);
1544  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
1545
1546  /* Separate the parameter into the separate pieces.  The parameter is
1547   * given as one string to enhance its similarity to URL syntax.
1548   */
1549  tmp = strchr(cmd->argv[1], ':');
1550  if (tmp == NULL)
1551    CONF_ERROR(cmd, "badly formatted parameter");
1552
1553  *tmp++ = '\0';
1554
1555  /* Verify that the requested source type has been registered, and supports
1556   * the table type (limit or tally).
1557   */
1558  if (strcasecmp(cmd->argv[0], "QuotaLimitTable") == 0)
1559    tabflag = QUOTATAB_LIMIT_SRC;
1560
1561  else if (strcasecmp(cmd->argv[0], "QuotaTallyTable") == 0)
1562    tabflag = QUOTATAB_TALLY_SRC;
1563
1564#if !defined(PR_SHARED_MODULE)
1565  regtab = quotatab_get_backend(cmd->argv[1], tabflag);
1566  if (!regtab)
1567    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported table source type: '",
1568      cmd->argv[1], "'", NULL));
1569#endif /* PR_SHARED_MODULE */
1570
1571  add_config_param_str(cmd->argv[0], 2, cmd->argv[1], tmp);
1572  return PR_HANDLED(cmd);
1573}
1574
1575/* Variable handlers
1576 */
1577
1578static const char *quota_get_bytes_str(void *data, size_t datasz) {
1579  const char *res = NULL;
1580  double adj = 0.0, bytes = *((double *) data);
1581
1582  switch (byte_units) {
1583    case BYTE:
1584      /* no calculation needed */
1585
1586      if (bytes > 0.0) {
1587        char buf[PR_TUNABLE_BUFFER_SIZE];
1588        memset(buf, '\0', sizeof(buf));
1589        snprintf(buf, sizeof(buf), "%.2f", bytes);
1590        res = pstrdup(session.pool, buf);
1591
1592      } else
1593        res = pstrdup(session.pool, "(unlimited)");
1594
1595      break;
1596
1597    case KILO:
1598      /* Divide by 1024.0 */
1599      adj = (bytes / 1024.0);
1600
1601      if (adj > 0.0) {
1602        char buf[PR_TUNABLE_BUFFER_SIZE];
1603        memset(buf, '\0', sizeof(buf));
1604        snprintf(buf, sizeof(buf), "%.2f KB", adj);
1605        res = pstrdup(session.pool, buf);
1606
1607      } else
1608        res = pstrdup(session.pool, "(unlimited)");
1609
1610      break;
1611
1612    case MEGA:
1613      /* Divide by 1024.0 * 1024.0 */
1614      adj = (bytes / (1024.0 * 1024.0));
1615
1616      if (adj > 0.0) {
1617        char buf[PR_TUNABLE_BUFFER_SIZE];
1618        memset(buf, '\0', sizeof(buf));
1619        snprintf(buf, sizeof(buf), "%.2f MB", adj);
1620        res = pstrdup(session.pool, buf);
1621
1622      } else
1623        res = pstrdup(session.pool, "(unlimited)");
1624
1625      break;
1626
1627    case GIGA:
1628      /* Divide by 1024.0 * 1024.0 * 1024.0 */
1629      adj = (bytes / (1024.0 * 1024.0 * 1024.0));
1630
1631      if (adj > 0.0) {
1632        char buf[PR_TUNABLE_BUFFER_SIZE];
1633        memset(buf, '\0', sizeof(buf));
1634        snprintf(buf, sizeof(buf), "%.2f GB", adj);
1635        res = pstrdup(session.pool, buf);
1636
1637      } else
1638        res = pstrdup(session.pool, "(unlimited)");
1639
1640      break;
1641
1642    default:
1643      quotatab_log("warning: unknown QuotaDisplayUnits");
1644      break;
1645  }
1646
1647  return res;
1648}
1649
1650static const char *quota_get_files_str(void *data, size_t datasz) {
1651  const char *res;
1652  unsigned int files = *((unsigned int *) data);
1653
1654  if (files != 0) {
1655    char buf[PR_TUNABLE_BUFFER_SIZE];
1656    memset(buf, '\0', sizeof(buf));
1657    snprintf(buf, sizeof(buf), "%u", files);
1658    res = pstrdup(session.pool, buf);
1659
1660  } else
1661    res = pstrdup(session.pool, "(unlimited)");
1662
1663  return res;
1664}
1665
1666/* Command handlers
1667 */
1668
1669MODRET quotatab_post_abor(cmd_rec *cmd) {
1670  have_aborted_transfer = TRUE;
1671  return PR_DECLINED(cmd);
1672}
1673
1674MODRET quotatab_pre_appe(cmd_rec *cmd) {
1675  struct stat st;
1676
1677  have_aborted_transfer = FALSE;
1678  have_err_response = FALSE;
1679
1680  /* Sanity check */
1681  if (!use_quotas)
1682    return PR_DECLINED(cmd);
1683
1684  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
1685    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
1686      cmd->argv[0], cmd->arg, quota_exclude_filter);
1687    return PR_DECLINED(cmd);
1688  }
1689
1690  /* Refresh the tally */
1691  QUOTATAB_TALLY_READ
1692
1693  /* Check quotas to see if bytes upload or total quota has been reached.
1694   * Block command if so.
1695   */
1696  if (sess_limit.bytes_in_avail > 0.0 &&
1697      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
1698
1699    /* Report the exceeding of the threshold. */
1700    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
1701      DISPLAY_BYTES_IN(cmd));
1702    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
1703      cmd->argv[0], DISPLAY_BYTES_IN(cmd));
1704    have_err_response = TRUE;
1705
1706    /* Set an appropriate errno value. */
1707    errno = get_quota_exceeded_errno(EPERM, NULL);
1708
1709    return PR_ERROR(cmd);
1710
1711  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
1712      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
1713
1714    /* Report the exceeding of the threshold. */
1715    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
1716      DISPLAY_BYTES_XFER(cmd));
1717    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
1718      cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
1719    have_err_response = TRUE;
1720
1721    /* Set an appropriate errno value. */
1722    errno = get_quota_exceeded_errno(EPERM, NULL);
1723
1724    return PR_ERROR(cmd);
1725  }
1726
1727  /* Briefly cache the size (in bytes) of the file being appended to, so that
1728   * if successful, the byte counts can be adjusted correctly.
1729   */
1730  pr_fs_clear_cache();
1731  if (pr_fsio_lstat(cmd->arg, &st) < 0) {
1732    quotatab_disk_nbytes = 0;
1733
1734  } else {
1735    quotatab_disk_nbytes = st.st_size;
1736  }
1737
1738  have_quota_update = QUOTA_HAVE_WRITE_UPDATE;
1739  return PR_DECLINED(cmd);
1740}
1741
1742MODRET quotatab_post_appe(cmd_rec *cmd) {
1743  struct stat st;
1744  off_t append_bytes = session.xfer.total_bytes;
1745
1746  /* sanity check */
1747  if (!use_quotas) {
1748    have_quota_update = 0;
1749    return PR_DECLINED(cmd);
1750  }
1751
1752  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
1753    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
1754      cmd->argv[0], cmd->arg, quota_exclude_filter);
1755    have_quota_update = 0;
1756    return PR_DECLINED(cmd);
1757  }
1758
1759  /* Check on the size of the appended-to file again, and use the difference
1760   * in file size as the increment.  Make sure that no caching effects
1761   * mess with the stat.
1762   */
1763  pr_fs_clear_cache();
1764  if (pr_fsio_lstat(cmd->arg, &st) >= 0) {
1765    append_bytes = st.st_size - quotatab_disk_nbytes;
1766
1767  } else {
1768    if (errno == ENOENT) {
1769      append_bytes = 0;
1770
1771    } else {
1772      quotatab_log("%s: error checking '%s': %s", cmd->argv[0], cmd->arg,
1773        strerror(errno));
1774    }
1775  }
1776
1777  /* Write out an updated quota entry. */
1778  QUOTATAB_TALLY_WRITE(append_bytes, 0, session.xfer.total_bytes, 0, 0, 0)
1779
1780  /* Check the bytes quotas to see if any have been reached.  Report this
1781   * to the user if so.
1782   */
1783  if (sess_limit.bytes_in_avail > 0.0 &&
1784      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
1785
1786    if (!have_err_response) {
1787      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
1788        DISPLAY_BYTES_IN(cmd));
1789      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
1790        cmd->argv[0], DISPLAY_BYTES_IN(cmd));
1791    }
1792
1793    if (sess_tally.bytes_in_used > sess_limit.bytes_in_avail &&
1794        sess_limit.quota_limit_type == HARD_LIMIT) {
1795      int res;
1796
1797      res = pr_fsio_unlink(cmd->arg);
1798      if (res < 0 &&
1799          errno == EISDIR &&
1800          use_dirs == TRUE) {
1801        res = pr_fsio_rmdir(cmd->arg);
1802      }
1803
1804      if (res < 0) {
1805        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
1806          strerror(errno));
1807
1808      } else {
1809        QUOTATAB_TALLY_WRITE(-append_bytes, 0, -session.xfer.total_bytes,
1810          -1, 0, -1);
1811
1812        /* Report the removal of the file. */
1813        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
1814        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
1815          cmd->argv[0], cmd->arg);
1816      }
1817    }
1818
1819  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
1820      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
1821
1822    if (!have_err_response) {
1823      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
1824        DISPLAY_BYTES_XFER(cmd));
1825      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
1826        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
1827    }
1828
1829    if (sess_tally.bytes_xfer_used > sess_limit.bytes_xfer_avail &&
1830        sess_limit.quota_limit_type == HARD_LIMIT) {
1831      if (pr_fsio_unlink(cmd->arg) < 0) {
1832        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
1833          strerror(errno));
1834
1835      } else {
1836        QUOTATAB_TALLY_WRITE(-append_bytes, 0, -session.xfer.total_bytes,
1837          -1, 0, -1);
1838
1839        /* Report the removal of the file. */
1840        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
1841        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
1842          cmd->argv[0], cmd->arg);
1843      }
1844    }
1845  }
1846
1847  have_quota_update = 0;
1848  return PR_DECLINED(cmd);
1849}
1850
1851MODRET quotatab_post_appe_err(cmd_rec *cmd) {
1852  struct stat st;
1853  off_t append_bytes = session.xfer.total_bytes;
1854
1855  /* sanity check */
1856  if (!use_quotas) {
1857    have_quota_update = 0;
1858    return PR_DECLINED(cmd);
1859  }
1860
1861  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
1862    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
1863      cmd->argv[0], cmd->arg, quota_exclude_filter);
1864    have_quota_update = 0;
1865    return PR_DECLINED(cmd);
1866  }
1867
1868  /* Check on the size of the appended-to file again, and use the difference
1869   * in file size as the increment.  Make sure that no caching effects
1870   * mess with the stat.
1871   */
1872  pr_fs_clear_cache();
1873  if (pr_fsio_lstat(cmd->arg, &st) >= 0) {
1874    append_bytes = st.st_size - quotatab_disk_nbytes;
1875
1876  } else {
1877    if (errno == ENOENT) {
1878      append_bytes = 0;
1879
1880    } else {
1881      quotatab_log("%s: error checking '%s': %s", cmd->argv[0], cmd->arg,
1882        strerror(errno));
1883    }
1884  }
1885
1886  /* Write out an updated quota entry */
1887  QUOTATAB_TALLY_WRITE(append_bytes, 0, session.xfer.total_bytes, 0, 0, 0)
1888
1889  /* Check the bytes quotas to see if any have been reached.  Report this
1890   * to the user if so.
1891   */
1892  if (sess_limit.bytes_in_avail > 0.0 &&
1893      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
1894
1895    if (!have_err_response) {
1896      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
1897        DISPLAY_BYTES_IN(cmd));
1898      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
1899        cmd->argv[0], DISPLAY_BYTES_IN(cmd));
1900    }
1901
1902    if (sess_tally.bytes_in_used > sess_limit.bytes_in_avail &&
1903        sess_limit.quota_limit_type == HARD_LIMIT) {
1904      int res;
1905
1906      res = pr_fsio_unlink(cmd->arg);
1907      if (res < 0 &&
1908          errno == EISDIR &&
1909          use_dirs == TRUE) {
1910        res = pr_fsio_rmdir(cmd->arg);
1911      }
1912
1913      if (res < 0) {
1914        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
1915          strerror(errno));
1916
1917      } else {
1918        QUOTATAB_TALLY_WRITE(-append_bytes, 0, -session.xfer.total_bytes,
1919          -1, 0, -1);
1920
1921        /* Report the removal of the file. */
1922        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
1923        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
1924          cmd->argv[0], cmd->arg);
1925      }
1926    }
1927
1928  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
1929      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
1930
1931    if (!have_err_response) {
1932      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
1933        DISPLAY_BYTES_XFER(cmd));
1934      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
1935        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
1936    }
1937
1938    if (sess_tally.bytes_xfer_used > sess_limit.bytes_xfer_avail &&
1939        sess_limit.quota_limit_type == HARD_LIMIT) {
1940      int res;
1941
1942      res = pr_fsio_unlink(cmd->arg);
1943      if (res < 0 &&
1944          errno == EISDIR &&
1945          use_dirs == TRUE) {
1946        res = pr_fsio_rmdir(cmd->arg);
1947      }
1948
1949      if (res < 0) {
1950        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
1951          strerror(errno));
1952
1953      } else {
1954        QUOTATAB_TALLY_WRITE(-append_bytes, 0, -session.xfer.total_bytes,
1955          -1, 0, -1);
1956
1957        /* Report the removal of the file. */
1958        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
1959        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
1960          cmd->argv[0], cmd->arg);
1961      }
1962    }
1963  }
1964
1965  have_quota_update = 0;
1966  return PR_DECLINED(cmd);
1967}
1968
1969MODRET quotatab_pre_copy(cmd_rec *cmd) {
1970  struct stat st;
1971
1972  have_aborted_transfer = FALSE;
1973  have_err_response = FALSE;
1974
1975  /* Sanity check */
1976  if (!use_quotas)
1977    return PR_DECLINED(cmd);
1978
1979  if (quotatab_ignore_path(cmd->tmp_pool, cmd->argv[1])) {
1980    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
1981      cmd->argv[0], cmd->argv[1], quota_exclude_filter);
1982    return PR_DECLINED(cmd);
1983  }
1984
1985  /* Refresh the tally */
1986  QUOTATAB_TALLY_READ
1987
1988  /* Check quotas to see if bytes upload or total quota has been reached.
1989   * Block command if so.
1990   */
1991  if (sess_limit.bytes_in_avail > 0.0 &&
1992      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
1993
1994    /* Report the exceeding of the threshold. */
1995    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
1996      DISPLAY_BYTES_IN(cmd));
1997    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
1998      cmd->argv[0], DISPLAY_BYTES_IN(cmd));
1999    have_err_response = TRUE;
2000
2001    /* Set an appropriate errno value. */
2002    errno = get_quota_exceeded_errno(EPERM, NULL);
2003
2004    return PR_ERROR(cmd);
2005
2006  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
2007             sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
2008
2009    /* Report the exceeding of the threshold. */
2010    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2011      DISPLAY_BYTES_XFER(cmd));
2012    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
2013      cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
2014    have_err_response = TRUE;
2015
2016    /* Set an appropriate errno value. */
2017    errno = get_quota_exceeded_errno(EPERM, NULL);
2018
2019    return PR_ERROR(cmd);
2020  }
2021
2022  /* Briefly cache the size (in bytes) of the file being overwritten, so that
2023   * if successful, the byte counts can be adjusted correctly.
2024   */
2025  pr_fs_clear_cache();
2026  if (pr_fsio_stat(cmd->argv[2], &st) < 0) {
2027    quotatab_disk_nbytes = 0;
2028
2029    if (errno == ENOENT) {
2030      quotatab_disk_nfiles = 1;
2031
2032    } else {
2033      quotatab_disk_nfiles = 0;
2034    }
2035
2036  } else {
2037
2038    if (!S_ISDIR(st.st_mode) ||
2039        (S_ISDIR(st.st_mode) && use_dirs == TRUE)) {
2040      quotatab_disk_nbytes = st.st_size;
2041      quotatab_disk_nfiles = 0;
2042    }
2043  }
2044
2045  if (quotatab_disk_nfiles == 1) {
2046    /* Check quotas to see if files upload or total quota has been reached,
2047     * but only if the destination file did not already exist.  Block the
2048     * command if the file quotas were exceeded.
2049     */
2050    if (sess_limit.files_in_avail != 0 &&
2051        sess_tally.files_in_used >= sess_limit.files_in_avail) {
2052
2053      /* Report the exceeding of the threshold. */
2054      quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2055        DISPLAY_FILES_IN(cmd));
2056      pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
2057        cmd->argv[0], DISPLAY_FILES_IN(cmd));
2058      have_err_response = TRUE;
2059
2060      /* Set an appropriate errno value. */
2061      errno = get_quota_exceeded_errno(EPERM, NULL);
2062
2063      return PR_ERROR(cmd);
2064
2065    } else if (sess_limit.files_xfer_avail != 0 &&
2066               sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
2067
2068      /* Report the exceeding of the threshold. */
2069      quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2070        DISPLAY_FILES_XFER(cmd));
2071      pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
2072        cmd->argv[0], DISPLAY_FILES_XFER(cmd));
2073      have_err_response = TRUE;
2074
2075      /* Set an appropriate errno value. */
2076      errno = get_quota_exceeded_errno(EPERM, NULL);
2077
2078      return PR_ERROR(cmd);
2079    }
2080  }
2081
2082  have_quota_update = 0;
2083  return PR_DECLINED(cmd);
2084}
2085
2086MODRET quotatab_post_copy(cmd_rec *cmd) {
2087  struct stat st;
2088  off_t copy_bytes = 0;
2089  int dst_truncated = FALSE;
2090
2091  /* Sanity check */
2092  if (!use_quotas)
2093    return PR_DECLINED(cmd);
2094
2095  if (quotatab_ignore_path(cmd->tmp_pool, cmd->argv[2])) {
2096    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
2097      cmd->argv[0], cmd->argv[2], quota_exclude_filter);
2098    return PR_DECLINED(cmd);
2099  }
2100
2101  pr_fs_clear_cache();
2102  if (pr_fsio_stat(cmd->argv[2], &st) == 0) {
2103    if (quotatab_disk_nfiles == 0) {
2104
2105      if (!S_ISDIR(st.st_mode) ||
2106          (S_ISDIR(st.st_mode) && use_dirs == TRUE)) {
2107        /* If the destination file already existed, the number of bytes
2108         * copied is the current size less its previous size.  Unless its
2109         * current size is smaller than its previous size...
2110         */
2111
2112        if (st.st_size >= quotatab_disk_nbytes) {
2113          copy_bytes = st.st_size - quotatab_disk_nbytes;
2114
2115        } else {
2116          copy_bytes = quotatab_disk_nbytes - st.st_size;
2117          dst_truncated = TRUE;
2118        }
2119      }
2120 
2121    } else {
2122      if (!S_ISDIR(st.st_mode) ||
2123          (S_ISDIR(st.st_mode) && use_dirs == TRUE)) {
2124        /* ... otherwise, its the entire size of the destination file. */
2125        copy_bytes = st.st_size;
2126      }
2127    }
2128  }
2129
2130  /* Write out an updated quota entry.  If the destination file was truncated
2131   * (i.e. made smaller) because of this copy, then we need to write
2132   * negative byte values, not positive.
2133   */
2134  if (!dst_truncated) {
2135    QUOTATAB_TALLY_WRITE(copy_bytes, 0, copy_bytes,
2136      quotatab_disk_nfiles, 0, quotatab_disk_nfiles)
2137
2138  } else {
2139    QUOTATAB_TALLY_WRITE(-copy_bytes, 0, -copy_bytes,
2140      quotatab_disk_nfiles, 0, quotatab_disk_nfiles)
2141  }
2142
2143  /* Check quotas to see if bytes upload or total quota has been reached.
2144   * Report this to user if so.  If it is a hard bytes limit, delete the
2145   * uploaded file.
2146   */
2147  if (sess_limit.bytes_in_avail > 0.0 &&
2148      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
2149
2150    if (!have_err_response) {
2151      /* Report the reaching of the threshold. */
2152      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
2153        DISPLAY_BYTES_IN(cmd));
2154      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
2155        cmd->argv[0], DISPLAY_BYTES_IN(cmd));
2156    }
2157
2158    if (sess_tally.bytes_in_used > sess_limit.bytes_in_avail &&
2159        sess_limit.quota_limit_type == HARD_LIMIT) {
2160      int res;
2161
2162      res = pr_fsio_unlink(cmd->argv[2]);
2163      if (res < 0 &&
2164          errno == EISDIR &&
2165          use_dirs == TRUE) {
2166        res = pr_fsio_rmdir(cmd->argv[2]);
2167      }
2168
2169      if (res < 0) {
2170        quotatab_log("notice: unable to unlink '%s': %s", cmd->argv[2],
2171          strerror(errno));
2172
2173      } else {
2174        QUOTATAB_TALLY_WRITE(-copy_bytes, 0, -copy_bytes,
2175          -quotatab_disk_nfiles, 0, -quotatab_disk_nfiles);
2176
2177        /* Report the removal of the file. */
2178        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0],
2179          cmd->argv[2]);
2180        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
2181          cmd->argv[0], cmd->argv[2]);
2182      }
2183    }
2184
2185  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
2186             sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
2187
2188    if (!have_err_response) {
2189      /* Report the reaching of the threshold. */
2190      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
2191        DISPLAY_BYTES_XFER(cmd));
2192      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
2193        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
2194    }
2195
2196    if (sess_tally.bytes_xfer_used > sess_limit.bytes_xfer_avail &&
2197        sess_limit.quota_limit_type == HARD_LIMIT) {
2198      int res;
2199
2200      res = pr_fsio_unlink(cmd->argv[2]);
2201      if (res < 0 &&
2202          errno == EISDIR &&
2203          use_dirs == TRUE) {
2204        res = pr_fsio_rmdir(cmd->argv[2]);
2205      }
2206
2207      if (res < 0) {
2208        quotatab_log("notice: unable to unlink '%s': %s", cmd->argv[2],
2209          strerror(errno));
2210
2211      } else {
2212        QUOTATAB_TALLY_WRITE(-copy_bytes, 0, -copy_bytes,
2213          -quotatab_disk_nfiles, 0, -quotatab_disk_nfiles);
2214
2215        /* Report the removal of the file. */
2216        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0],
2217          cmd->argv[2]);
2218        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
2219          cmd->argv[0], cmd->argv[2]);
2220      }
2221    }
2222  }
2223
2224  if (quotatab_disk_nfiles == 1) {
2225    /* Check quotas to see if files upload or total quota has been reached,
2226     * but only if the destination file did not already exist.  Report this
2227     * to user if the file quotas were exceeded.
2228     */
2229    if (sess_limit.files_in_avail != 0 &&
2230        sess_tally.files_in_used >= sess_limit.files_in_avail) {
2231
2232      if (!have_err_response) {
2233        /* Report the reaching of the threshold. */
2234        quotatab_log("%s: quota reached: used %s", cmd->argv[0],
2235          DISPLAY_FILES_IN(cmd));
2236        pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
2237          cmd->argv[0], DISPLAY_FILES_IN(cmd));
2238      }
2239
2240    } else if (sess_limit.files_xfer_avail != 0 &&
2241               sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
2242
2243      if (!have_err_response) {
2244        /* Report the reaching of the threshold. */
2245        quotatab_log("%s: quota reached: used %s", cmd->argv[0],
2246          DISPLAY_FILES_XFER(cmd));
2247        pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
2248          cmd->argv[0], DISPLAY_FILES_XFER(cmd));
2249      }
2250    }
2251  }
2252
2253  /* Clear the cached bytes/files. */
2254  quotatab_disk_nbytes = 0;
2255  quotatab_disk_nfiles = 0;
2256 
2257  return PR_DECLINED(cmd);
2258}
2259
2260MODRET quotatab_post_copy_err(cmd_rec *cmd) {
2261  /* Sanity check */
2262  if (!use_quotas)
2263    return PR_DECLINED(cmd);
2264
2265  /* Clear the cached bytes/files. */
2266  quotatab_disk_nbytes = 0;
2267  quotatab_disk_nfiles = 0;
2268 
2269  return PR_DECLINED(cmd);
2270}
2271
2272MODRET quotatab_pre_dele(cmd_rec *cmd) {
2273  char *path;
2274
2275  /* sanity check */
2276  if (!use_quotas)
2277    return PR_DECLINED(cmd);
2278
2279  /* The real DELE handler makes sure that, if a symlink is being deleted,
2280   * only the symlink (and not the pointed-to file) is deleted.  We need
2281   * to do the same here, so that the bytes are correct.
2282   */
2283  path = dir_canonical_path(cmd->tmp_pool,
2284    pr_fs_decode_path(cmd->tmp_pool, cmd->arg));
2285
2286  quotatab_have_dele_st = FALSE;
2287
2288  if (path) {
2289    if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
2290      quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
2291        cmd->argv[0], cmd->arg, quota_exclude_filter);
2292      return PR_DECLINED(cmd);
2293    }
2294
2295    /* Briefly cache the size (in bytes) of the file to be deleted, so that
2296     * if successful, the byte counts can be adjusted correctly.
2297     */
2298    pr_fs_clear_cache();
2299    if (pr_fsio_lstat(path, &quotatab_dele_st) < 0) {
2300      quotatab_disk_nbytes = 0;
2301
2302    } else {
2303      quotatab_disk_nbytes = quotatab_dele_st.st_size;
2304      quotatab_have_dele_st = TRUE;
2305      have_quota_update = QUOTA_HAVE_WRITE_UPDATE;
2306    }
2307
2308  } else {
2309    quotatab_disk_nbytes = 0;
2310  }
2311
2312  return PR_DECLINED(cmd);
2313}
2314
2315MODRET quotatab_post_dele(cmd_rec *cmd) {
2316
2317  /* sanity check */
2318  if (!use_quotas)
2319    return PR_DECLINED(cmd);
2320
2321  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
2322    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
2323      cmd->argv[0], cmd->arg, quota_exclude_filter);
2324    return PR_DECLINED(cmd);
2325  }
2326
2327  if (quotatab_have_dele_st) {
2328
2329    /* Check the ownership of the deleted file against the currently
2330     * logged-in user/group.  If the file belongs to us, we can update
2331     * our tally as normal.  Otherwise, we try to give the credit for the
2332     * deleted bytes to the owner of the file.
2333     */
2334
2335    if (quotatab_dele_st.st_uid == session.uid) {
2336      /* Write out an updated quota entry. */
2337      QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, 0, -1, 0, 0)
2338
2339    } else {
2340      const char *group_owner, *user_owner, *path;
2341      quota_limit_t dele_limit;
2342      quota_tally_t dele_tally;
2343
2344      path = dir_canonical_path(cmd->tmp_pool,
2345        pr_fs_decode_path(cmd->tmp_pool, cmd->arg));
2346
2347      /* 1. Get the user name for the st.st_uid.
2348       * 2. Check for a USER_QUOTA limit for this user.
2349       * 3. If have limit, check for USER_QUOTA tally.
2350       * 4. If have tally, update _that_ tally.
2351       *
2352       * Do the same check for the st.st_gid, too.
2353       */
2354
2355      user_owner = pr_auth_uid2name(cmd->tmp_pool, quotatab_dele_st.st_uid);
2356      group_owner = pr_auth_gid2name(cmd->tmp_pool, quotatab_dele_st.st_gid);
2357
2358      quotatab_log("deleted file '%s' belongs to user '%s' (UID %lu), "
2359        "not the current user '%s' (UID %lu); attempting to credit user '%s' "
2360        "for the deleted bytes", path, user_owner,
2361        (unsigned long) quotatab_dele_st.st_uid, session.user,
2362        (unsigned long) session.uid, user_owner);
2363
2364      quotatab_mutex_lock(F_WRLCK);
2365
2366      if (quotatab_lookup(TYPE_LIMIT, &dele_limit, user_owner, USER_QUOTA)) {
2367        quotatab_log("found limit entry for user '%s'", user_owner);
2368
2369        if (quotatab_lookup(TYPE_TALLY, &dele_tally, user_owner, USER_QUOTA)) {
2370          quotatab_log("found tally entry for user '%s'", user_owner);
2371          quotatab_mutex_lock(F_UNLCK);
2372
2373          if (quotatab_write(&dele_tally, -quotatab_disk_nbytes, 0, 0, -1,
2374              0, 0) < 0) {
2375            quotatab_log("error: unable to write tally: %s", strerror(errno));
2376
2377          } else {
2378            quotatab_log("credited user '%s' for the deleted file/bytes",
2379              user_owner);
2380          }
2381
2382          have_quota_update = 0;
2383
2384        } else {
2385          quotatab_log("no tally entry found for user '%s'", user_owner);
2386          quotatab_mutex_lock(F_UNLCK);
2387
2388          /* Credit the current user for these bytes anyway; that's the
2389           * default behavior anyway.
2390           */
2391          QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, 0, -1, 0, 0)
2392        }
2393
2394      } else {
2395        quotatab_log("no limit entry found for user '%s'", user_owner);
2396
2397        /* Look for a quota for the group owner, too. */
2398        if (quotatab_lookup(TYPE_LIMIT, &dele_limit, group_owner,
2399            GROUP_QUOTA)) {
2400          quotatab_log("found limit entry for group '%s'", group_owner);
2401
2402          if (quotatab_lookup(TYPE_TALLY, &dele_tally, group_owner,
2403              GROUP_QUOTA)) {
2404            quotatab_log("found tally entry for group '%s'", group_owner);
2405            quotatab_mutex_lock(F_UNLCK);
2406
2407            if (quotatab_write(&dele_tally, -quotatab_disk_nbytes, 0, 0, -1,
2408                0, 0) < 0) {
2409              quotatab_log("error: unable to write tally: %s", strerror(errno));
2410
2411            } else {
2412              quotatab_log("credited group '%s' for the deleted file/bytes",
2413                group_owner);
2414            }
2415
2416            have_quota_update = 0;
2417
2418          } else {
2419            quotatab_log("no tally entry found for group '%s'", group_owner);
2420            quotatab_mutex_lock(F_UNLCK);
2421
2422            /* Credit the current user for these bytes anyway; that's the
2423             * default behavior anyway.
2424             */
2425            QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, 0, -1, 0, 0)
2426          }
2427
2428        } else {
2429          quotatab_mutex_lock(F_UNLCK);
2430
2431          /* Credit the current user for these bytes anyway; that's the
2432           * default behavior anyway.
2433           */
2434          QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, 0, -1, 0, 0)
2435        }
2436      }
2437    }
2438  }
2439
2440  /* NOTE: if use_dirs is TRUE, also take into consideration the decreased
2441   * disk usage caused by any decrease in the size of the containing directory.
2442   */
2443
2444  /* Clear the cached bytes. */
2445  quotatab_disk_nbytes = 0;
2446
2447  /* Clear the update flag as well. */
2448  have_quota_update = 0;
2449
2450  return PR_DECLINED(cmd);
2451}
2452
2453MODRET quotatab_post_dele_err(cmd_rec *cmd) {
2454
2455  /* sanity check */
2456  if (!use_quotas)
2457    return PR_DECLINED(cmd);
2458
2459  /* Clear the cached bytes. */
2460  quotatab_disk_nbytes = 0;
2461
2462  /* Clear the update flag as well. */
2463  have_quota_update = 0;
2464
2465  return PR_DECLINED(cmd);
2466}
2467
2468MODRET quotatab_pre_mkd(cmd_rec *cmd) {
2469
2470  /* Sanity check. */
2471  if (!use_dirs)
2472    return PR_DECLINED(cmd);
2473
2474  /* Use quotatab_pre_stor() for most of the work. */
2475  return quotatab_pre_stor(cmd);
2476}
2477
2478MODRET quotatab_post_mkd(cmd_rec *cmd) {
2479
2480  /* Sanity check. */
2481  if (!use_dirs)
2482    return PR_DECLINED(cmd);
2483
2484  /* Use quotatab_post_stor() for most of the work. */
2485  return quotatab_post_stor(cmd);
2486}
2487
2488MODRET quotatab_post_mkd_err(cmd_rec *cmd) {
2489
2490  /* Sanity check. */
2491  if (!use_dirs)
2492    return PR_DECLINED(cmd);
2493
2494  /* Use quotatab_post_stor_err() for most of the work. */
2495  return quotatab_post_stor_err(cmd);
2496}
2497
2498MODRET quotatab_post_pass(cmd_rec *cmd) {
2499  unsigned char have_limit_entry = FALSE;
2500  have_quota_entry = FALSE;
2501
2502  /* Be done now if there is no quota table */
2503  if (!use_quotas ||
2504      !have_quota_limit_table ||
2505      !have_quota_tally_table) {
2506    use_quotas = FALSE;
2507    quotatab_log("turning QuotaEngine off");
2508    return PR_DECLINED(cmd);
2509  }
2510
2511  quotatab_mutex_lock(F_WRLCK);
2512
2513  /* Check for a limit and a tally entry for this user. */
2514  if (quotatab_lookup(TYPE_LIMIT, &sess_limit, session.user, USER_QUOTA)) {
2515    quotatab_log("found limit entry for user '%s'", session.user);
2516    have_limit_entry = TRUE;
2517
2518    if (quotatab_lookup(TYPE_TALLY, &sess_tally, session.user, USER_QUOTA)) {
2519      quotatab_log("found tally entry for user '%s'", session.user);
2520      have_quota_entry = TRUE;
2521
2522    } else {
2523      if (quotatab_create_tally()) {
2524        quotatab_log("created tally entry for user '%s'", session.user);
2525        have_quota_entry = TRUE;
2526      }
2527    }
2528
2529    quotatab_mutex_lock(F_UNLCK);
2530
2531    if (have_quota_entry) {
2532      if ((quotatab_opts & QUOTA_OPT_SCAN_ON_LOGIN) &&
2533          (sess_limit.bytes_in_avail > 0 ||
2534           sess_limit.files_in_avail > 0)) {
2535        double byte_count = 0;
2536        unsigned int file_count = 0;
2537        time_t then;
2538
2539        quotatab_log("ScanOnLogin enabled, scanning current directory '%s' "
2540          "for files owned by user '%s'", pr_fs_getcwd(), session.user);
2541
2542        time(&then);
2543        if (quotatab_scan_dir(cmd->tmp_pool, pr_fs_getcwd(), session.uid, -1,
2544            0, &byte_count, &file_count) < 0) {
2545          quotatab_log("unable to scan '%s': %s", pr_fs_getcwd(),
2546            strerror(errno));
2547
2548        } else {
2549          double bytes_diff = (double)
2550            (byte_count - sess_tally.bytes_in_used);
2551          int files_diff = file_count - sess_tally.files_in_used;
2552
2553          quotatab_log("found %0.2lf bytes in %u %s for user '%s' "
2554            "in %lu secs", byte_count, file_count,
2555            file_count != 1 ? "files" : "file", session.user,
2556            (unsigned long) time(NULL) - then);
2557
2558          quotatab_log("updating tally (%0.2lf bytes, %d %s difference)",
2559            bytes_diff, files_diff, files_diff != 1 ? "files" : "file");
2560
2561          /* Write out an updated quota entry */
2562          QUOTATAB_TALLY_WRITE(bytes_diff, 0, 0, files_diff, 0, 0);
2563        }
2564      }
2565    }
2566  }
2567
2568  /* Check for a limit and a tally entry for these groups. */
2569  if (!have_limit_entry) {
2570    char *group_name = session.group;
2571    gid_t group_id = session.gid;
2572
2573    if (quotatab_lookup(TYPE_LIMIT, &sess_limit, group_name, GROUP_QUOTA)) {
2574      quotatab_log("found limit entry for group '%s'", group_name);
2575      have_limit_entry = TRUE;
2576
2577    } else {
2578      if (session.groups) {
2579        register int i = 0;
2580
2581        char **group_names = session.groups->elts;
2582        gid_t *group_ids = session.gids->elts;
2583
2584        /* Scan the list of supplemental group memberships for this user. */
2585        for (i = 0; i < session.groups->nelts; i++) {
2586          group_name = group_names[i];
2587          group_id = group_ids[i];
2588
2589          if (quotatab_lookup(TYPE_LIMIT, &sess_limit, group_name,
2590              GROUP_QUOTA)) {
2591            quotatab_log("found limit entry for group '%s'", group_name);
2592            have_limit_entry = TRUE;
2593            break;
2594          }
2595        }
2596      }
2597    }
2598
2599    if (have_limit_entry) {
2600      if (quotatab_lookup(TYPE_TALLY, &sess_tally, group_name, GROUP_QUOTA)) {
2601        quotatab_log("found tally entry for group '%s'", group_name);
2602        have_quota_entry = TRUE;
2603
2604      } else {
2605        if (quotatab_create_tally()) {
2606          quotatab_log("created tally entry for group '%s'", group_name);
2607          have_quota_entry = TRUE;
2608        }
2609      }
2610
2611      quotatab_mutex_lock(F_UNLCK);
2612
2613      if (have_quota_entry) {
2614        if ((quotatab_opts & QUOTA_OPT_SCAN_ON_LOGIN) &&
2615            (sess_limit.bytes_in_avail > 0 ||
2616             sess_limit.files_in_avail > 0)) {
2617          double byte_count = 0;
2618          unsigned int file_count = 0;
2619          time_t then;
2620
2621          quotatab_log("ScanOnLogin enabled, scanning current directory '%s' "
2622            "for files owned by group '%s'", pr_fs_getcwd(), group_name);
2623
2624          time(&then);
2625          if (quotatab_scan_dir(cmd->tmp_pool, pr_fs_getcwd(), -1, group_id,
2626              0, &byte_count, &file_count) < 0) {
2627            quotatab_log("unable to scan '%s': %s", pr_fs_getcwd(),
2628              strerror(errno));
2629
2630          } else {
2631            double bytes_diff = byte_count - sess_tally.bytes_in_used;
2632            int files_diff = file_count - sess_tally.files_in_used;
2633
2634            quotatab_log("found %0.2lf bytes in %u %s for group '%s' "
2635              "in %lu secs", byte_count, file_count,
2636              file_count != 1 ? "files" : "file", group_name,
2637              (unsigned long) time(NULL) - then);
2638
2639            quotatab_log("updating tally (%0.2lf bytes, %d %s difference)",
2640              bytes_diff, files_diff, files_diff != 1 ? "files" : "file");
2641
2642            /* Write out an updated quota entry */
2643            QUOTATAB_TALLY_WRITE(bytes_diff, 0, 0, files_diff, 0, 0);
2644          }
2645        }
2646      }
2647    }
2648  }
2649
2650  /* Check for a limit and a tally entry for this class. */
2651  if (!have_limit_entry &&
2652      session.class) {
2653    if (quotatab_lookup(TYPE_LIMIT, &sess_limit, session.class->cls_name,
2654        CLASS_QUOTA)) {
2655      quotatab_log("found limit entry for class '%s'", session.class->cls_name);
2656      have_limit_entry = TRUE;
2657
2658      if (quotatab_lookup(TYPE_TALLY, &sess_tally, session.class->cls_name,
2659          CLASS_QUOTA)) {
2660        quotatab_log("found tally entry for class '%s'",
2661          session.class->cls_name);
2662        have_quota_entry = TRUE;
2663
2664      } else {
2665        if (quotatab_create_tally()) {
2666          quotatab_log("created tally entry for class '%s'",
2667            session.class->cls_name);
2668          have_quota_entry = TRUE;
2669        }
2670      }
2671
2672      quotatab_mutex_lock(F_UNLCK);
2673
2674      if (have_quota_entry) {
2675        if ((quotatab_opts & QUOTA_OPT_SCAN_ON_LOGIN) &&
2676            (sess_limit.bytes_in_avail > 0 ||
2677             sess_limit.files_in_avail > 0)) {
2678          double byte_count = 0;
2679          unsigned int file_count = 0;
2680          time_t then;
2681
2682          quotatab_log("ScanOnLogin enabled, scanning current directory '%s' "
2683            "for files owned by class '%s'", pr_fs_getcwd(),
2684            session.class->cls_name);
2685
2686          time(&then);
2687          if (quotatab_scan_dir(cmd->tmp_pool, pr_fs_getcwd(), -1, -1, 0,
2688              &byte_count, &file_count) < 0) {
2689            quotatab_log("unable to scan '%s': %s", pr_fs_getcwd(),
2690            strerror(errno));
2691
2692          } else {
2693            double bytes_diff = (double)
2694              (byte_count - sess_tally.bytes_in_used);
2695            int files_diff = file_count - sess_tally.files_in_used;
2696
2697            quotatab_log("found %0.2lf bytes in %u %s for class '%s' "
2698              "in %lu secs", byte_count, file_count,
2699              file_count != 1 ? "files" : "file", session.class->cls_name,
2700              (unsigned long) time(NULL) - then);
2701
2702            quotatab_log("updating tally (%0.2lf bytes, %d %s difference)",
2703              bytes_diff, files_diff, files_diff != 1 ? "files" : "file");
2704
2705            /* Write out an updated quota entry */
2706            QUOTATAB_TALLY_WRITE(bytes_diff, 0, 0, files_diff, 0, 0);
2707          }
2708        }
2709      }
2710    }
2711  }
2712
2713  /* Check for a limit and a tally entry for everyone. */
2714  if (!have_limit_entry) {
2715    if (quotatab_lookup(TYPE_LIMIT, &sess_limit, NULL, ALL_QUOTA)) {
2716      quotatab_log("found limit entry for all");
2717      have_limit_entry = TRUE;
2718
2719      if (quotatab_lookup(TYPE_TALLY, &sess_tally, NULL, ALL_QUOTA)) {
2720        quotatab_log("found tally entry for all");
2721        have_quota_entry = TRUE;
2722
2723      } else {
2724        if (quotatab_create_tally()) {
2725          quotatab_log("created tally entry for all");
2726          have_quota_entry = TRUE;
2727        }
2728      }
2729
2730      quotatab_mutex_lock(F_UNLCK);
2731
2732      if (have_quota_entry) {
2733        if ((quotatab_opts & QUOTA_OPT_SCAN_ON_LOGIN) &&
2734            (sess_limit.bytes_in_avail > 0 ||
2735             sess_limit.files_in_avail > 0)) {
2736          double byte_count = 0;
2737          unsigned int file_count = 0;
2738          time_t then;
2739
2740          quotatab_log("ScanOnLogin enabled, scanning current directory '%s' "
2741            "for files owned by all", pr_fs_getcwd());
2742
2743          time(&then);
2744          if (quotatab_scan_dir(cmd->tmp_pool, pr_fs_getcwd(), -1, -1, 0,
2745              &byte_count, &file_count) < 0) {
2746            quotatab_log("unable to scan '%s': %s", pr_fs_getcwd(),
2747            strerror(errno));
2748
2749          } else {
2750            double bytes_diff = (double)
2751              (byte_count - sess_tally.bytes_in_used);
2752            int files_diff = file_count - sess_tally.files_in_used;
2753
2754            quotatab_log("found %0.2lf bytes in %u %s for all "
2755              "in %lu secs", byte_count, file_count,
2756              file_count != 1 ? "files" : "file",
2757              (unsigned long) time(NULL) - then);
2758
2759            quotatab_log("updating tally (%0.2lf bytes, %d %s difference)",
2760              bytes_diff, files_diff, files_diff != 1 ? "files" : "file");
2761
2762            /* Write out an updated quota entry */
2763            QUOTATAB_TALLY_WRITE(bytes_diff, 0, 0, files_diff, 0, 0);
2764          }
2765        }
2766      }
2767    }
2768  }
2769
2770  /* Note: We _could_ close the limit table here.  However, we keep it
2771   * open anyway, in order to look up limits for _other_ users (e.g. when
2772   * handling deleted files).
2773   */
2774
2775  if (have_quota_entry) {
2776
2777    /* Assume that quotatab_lookup() does the reading in of the necessary
2778     * data from the quota source table as well.
2779     */
2780
2781    /* If per-session quotas are in effect, zero the tally record now */
2782    if (sess_limit.quota_per_session) {
2783      sess_tally.bytes_in_used = 0.0F;
2784      sess_tally.bytes_out_used = 0.0F;
2785      sess_tally.bytes_xfer_used = 0.0F;
2786      sess_tally.files_in_used = 0U;
2787      sess_tally.files_out_used = 0U;
2788      sess_tally.files_xfer_used = 0U;
2789
2790      quotatab_log("per session setting in effect: updates will not be "
2791        "tracked in the QuotaTallyTable");
2792    }
2793
2794    /* If the limit for this user is a hard limit, install our own FS handler,
2795     * one that provides a custom write() function.  We will use this to
2796     * return an error when writing a file causes a limit to be reached.
2797     */
2798    if (sess_limit.quota_limit_type == HARD_LIMIT) {
2799      pr_fs_t *fs = pr_register_fs(main_server->pool, "quotatab", "/");
2800      if (fs) {
2801        quotatab_log("quotatab fs registered");
2802
2803        fs->write = quotatab_fsio_write;
2804
2805      } else
2806        quotatab_log("error registering quotatab fs: %s", strerror(errno));
2807    }
2808
2809  } else {
2810    /* If we didn't even find a limit entry, then make sure that we
2811     * release the QuotaLock.
2812     */
2813    if (!have_limit_entry) {
2814      quotatab_mutex_lock(F_UNLCK);
2815    }
2816
2817    /* No quota entry for this user.  Make sure the quotas are not used. */
2818    quotatab_log("no quota entry found, turning QuotaEngine off");
2819    use_quotas = FALSE;
2820  }
2821
2822  /* Register some Variable entries, for Display files. */
2823  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.bytes_in}",
2824      "Maximum number of uploadable bytes", PR_VAR_TYPE_FUNC,
2825      quota_get_bytes_str, &(sess_limit.bytes_in_avail),
2826      sizeof(double *)) < 0)
2827    quotatab_log("error setting %%{mod_quotatab.limit.bytes_in} variable: %s",
2828      strerror(errno));
2829
2830  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.bytes_out}",
2831      "Maximum number of downloadable bytes", PR_VAR_TYPE_FUNC,
2832      quota_get_bytes_str, &(sess_limit.bytes_out_avail),
2833      sizeof(double *)) < 0)
2834    quotatab_log("error setting %%{mod_quotatab.limit.bytes_out} variable: %s",
2835      strerror(errno));
2836
2837  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.bytes_xfer}",
2838      "Maximum number of transferble bytes", PR_VAR_TYPE_FUNC,
2839      quota_get_bytes_str, &(sess_limit.bytes_xfer_avail),
2840      sizeof(double *)) < 0)
2841    quotatab_log("error setting %%{mod_quotatab.limit.bytes_xfer} variable: %s",
2842      strerror(errno));
2843
2844  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.files_in}",
2845      "Maximum number of uploadable files", PR_VAR_TYPE_FUNC,
2846      quota_get_files_str, &(sess_limit.files_in_avail),
2847      sizeof(unsigned int *)) < 0)
2848    quotatab_log("error setting %%{mod_quotatab.limit.files_in} variable: %s",
2849      strerror(errno));
2850
2851  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.files_out}",
2852      "Maximum number of downloadable files", PR_VAR_TYPE_FUNC,
2853      quota_get_files_str, &(sess_limit.files_out_avail),
2854      sizeof(unsigned int *)) < 0)
2855    quotatab_log("error setting %%{mod_quotatab.limit.files_out} variable: %s",
2856      strerror(errno));
2857
2858  if (pr_var_set(quotatab_pool, "%{mod_quotatab.limit.files_xfer}",
2859      "Maximum number of transferable files", PR_VAR_TYPE_FUNC,
2860      quota_get_files_str, &(sess_limit.files_xfer_avail),
2861      sizeof(unsigned int *)) < 0)
2862    quotatab_log("error setting %%{mod_quotatab.limit.files_xfer} variable: %s",
2863      strerror(errno));
2864
2865  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.bytes_in}",
2866      "Current number of uploaded bytes", PR_VAR_TYPE_FUNC,
2867      quota_get_bytes_str, &(sess_tally.bytes_in_used),
2868      sizeof(double *)) < 0)
2869    quotatab_log("error setting %%{mod_quotatab.tally.bytes_in} variable: %s",
2870      strerror(errno));
2871
2872  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.bytes_out}",
2873      "Current number of downloaded bytes", PR_VAR_TYPE_FUNC,
2874      quota_get_bytes_str, &(sess_tally.bytes_out_used),
2875      sizeof(double *)) < 0)
2876    quotatab_log("error setting %%{mod_quotatab.limit.bytes_out} variable: %s",
2877      strerror(errno));
2878
2879  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.bytes_xfer}",
2880      "Current number of transferred bytes", PR_VAR_TYPE_FUNC,
2881      quota_get_bytes_str, &(sess_tally.bytes_xfer_used),
2882      sizeof(double *)) < 0)
2883    quotatab_log("error setting %%{mod_quotatab.tally.bytes_xfer} variable: %s",
2884      strerror(errno));
2885
2886  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.files_in}",
2887      "Current number of uploaded files", PR_VAR_TYPE_FUNC,
2888      quota_get_files_str, &(sess_tally.files_in_used),
2889      sizeof(unsigned int *)) < 0)
2890    quotatab_log("error setting %%{mod_quotatab.tally.files_in} variable: %s",
2891      strerror(errno));
2892
2893  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.files_out}",
2894      "Current number of downloaded files", PR_VAR_TYPE_FUNC,
2895      quota_get_files_str, &(sess_tally.files_out_used),
2896      sizeof(unsigned int *)) < 0)
2897    quotatab_log("error setting %%{mod_quotatab.tally.files_out} variable: %s",
2898      strerror(errno));
2899
2900  if (pr_var_set(quotatab_pool, "%{mod_quotatab.tally.files_xfer}",
2901      "Current number of transferred files", PR_VAR_TYPE_FUNC,
2902      quota_get_files_str, &(sess_tally.files_xfer_used),
2903      sizeof(unsigned int *)) < 0)
2904    quotatab_log("error setting %%{mod_quotatab.tally.files_xfer} variable: %s",
2905      strerror(errno));
2906
2907  return PR_DECLINED(cmd);
2908}
2909
2910MODRET quotatab_pre_retr(cmd_rec *cmd) {
2911  have_aborted_transfer = FALSE;
2912  have_err_response = FALSE;
2913
2914  /* Sanity check */
2915  if (!use_quotas)
2916    return PR_DECLINED(cmd);
2917
2918  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
2919    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
2920      cmd->argv[0], cmd->arg, quota_exclude_filter);
2921    return PR_DECLINED(cmd);
2922  }
2923
2924  /* Refresh the tally */
2925  QUOTATAB_TALLY_READ
2926
2927  /* Check quotas to see if bytes download or total quota has been reached.
2928   * Block command if so.
2929   */
2930  if (sess_limit.bytes_out_avail > 0.0 &&
2931      sess_tally.bytes_out_used >= sess_limit.bytes_out_avail) {
2932
2933    /* Report the exceeding of the threshold. */
2934    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2935      DISPLAY_BYTES_OUT(cmd));
2936    pr_response_add_err(R_451, _("%s denied: quota exceeded: used %s"),
2937      cmd->argv[0], DISPLAY_BYTES_OUT(cmd));
2938    have_err_response = TRUE;
2939
2940    /* Set an appropriate errno value. */
2941    errno = get_quota_exceeded_errno(EPERM, NULL);
2942
2943    return PR_ERROR(cmd);
2944
2945  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
2946      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
2947
2948    /* Report the exceeding of the threshold. */
2949    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2950      DISPLAY_BYTES_XFER(cmd));
2951    pr_response_add_err(R_451, _("%s denied: quota exceeded: used %s"),
2952      cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
2953    have_err_response = TRUE;
2954
2955    /* Set an appropriate errno value. */
2956    errno = get_quota_exceeded_errno(EPERM, NULL);
2957
2958    return PR_ERROR(cmd);
2959  }
2960
2961  /* Check quotas to see if files download or total quota has been reached.
2962   * Block command if so.
2963   */
2964  if (sess_limit.files_out_avail != 0 &&
2965      sess_tally.files_out_used >= sess_limit.files_out_avail) {
2966
2967    /* Report the exceeding of the threshold. */
2968    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
2969      DISPLAY_FILES_OUT(cmd));
2970    pr_response_add_err(R_451, _("%s denied: quota exceeded: used %s"),
2971      cmd->argv[0], DISPLAY_FILES_OUT(cmd));
2972    have_err_response = TRUE;
2973
2974    /* Set an appropriate errno value. */
2975    errno = get_quota_exceeded_errno(EPERM, NULL);
2976
2977    return PR_ERROR(cmd);
2978
2979  } else if (sess_limit.files_xfer_avail != 0 &&
2980      sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
2981
2982    /* Report the exceeding of the threshold. */
2983    quotatab_log("%s: denied: quota exceeded: used %s", cmd->argv[0],
2984      DISPLAY_FILES_XFER(cmd));
2985    pr_response_add(R_451, _("%s denied: quota exceeded: used %s"),
2986      cmd->argv[0], DISPLAY_FILES_XFER(cmd));
2987    have_err_response = TRUE;
2988
2989    /* Set an appropriate errno value. */
2990    errno = get_quota_exceeded_errno(EPERM, NULL);
2991
2992    return PR_ERROR(cmd);
2993  }
2994
2995  have_quota_update = QUOTA_HAVE_READ_UPDATE;
2996  return PR_DECLINED(cmd);
2997}
2998
2999MODRET quotatab_post_retr(cmd_rec *cmd) {
3000
3001  /* Sanity check */
3002  if (!use_quotas)
3003    return PR_DECLINED(cmd);
3004
3005  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3006    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3007      cmd->argv[0], cmd->arg, quota_exclude_filter);
3008    return PR_DECLINED(cmd);
3009  }
3010
3011  /* Write out an updated tally */
3012  QUOTATAB_TALLY_WRITE(0, session.xfer.total_bytes, session.xfer.total_bytes,
3013    0, 1, 1)
3014
3015  /* Check quotas to see if bytes download or total quota has been reached.
3016   * Report this to user if so.
3017   */
3018  if (sess_limit.bytes_out_avail > 0.0 &&
3019      sess_tally.bytes_out_used >= sess_limit.bytes_out_avail) {
3020
3021    /* Report the reaching of the threshold. */
3022    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3023      DISPLAY_BYTES_OUT(cmd));
3024    pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3025      cmd->argv[0], DISPLAY_BYTES_OUT(cmd));
3026
3027  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
3028      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
3029
3030    /* Report the reaching of the threshold. */
3031    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3032      DISPLAY_BYTES_XFER(cmd));
3033    pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3034      cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
3035  }
3036
3037  /* Check quotas to see if files download or total quota has been reached.
3038   * Report this to user if so.
3039   */
3040  if (sess_limit.files_out_avail != 0 &&
3041      sess_tally.files_out_used >= sess_limit.files_out_avail) {
3042
3043    /* Report the reaching of the threshold. */
3044    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3045      DISPLAY_FILES_OUT(cmd));
3046    pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3047      cmd->argv[0], DISPLAY_FILES_OUT(cmd));
3048
3049  } else if (sess_limit.files_xfer_avail != 0 &&
3050    sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
3051
3052    /* Report the reaching of the threshold. */
3053    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3054      DISPLAY_FILES_XFER(cmd));
3055    pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3056      cmd->argv[0], DISPLAY_FILES_XFER(cmd));
3057  }
3058
3059  return PR_DECLINED(cmd);
3060}
3061
3062MODRET quotatab_post_retr_err(cmd_rec *cmd) {
3063
3064  /* Sanity check */
3065  if (!use_quotas)
3066    return PR_DECLINED(cmd);
3067
3068  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3069    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3070      cmd->argv[0], cmd->arg, quota_exclude_filter);
3071    return PR_DECLINED(cmd);
3072  }
3073
3074  /* Write out an updated tally */
3075  QUOTATAB_TALLY_WRITE(0, session.xfer.total_bytes, session.xfer.total_bytes,
3076    0, 0, 0)
3077
3078  /* Check quotas to see if bytes download or total quota has been reached.
3079   * Report this to user if so (if not already reported).
3080   */
3081  if (sess_limit.bytes_out_avail > 0.0 &&
3082      sess_tally.bytes_out_used >= sess_limit.bytes_out_avail) {
3083
3084    if (!have_err_response) {
3085
3086      /* Report the reaching of the threshold. */
3087      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3088        DISPLAY_BYTES_OUT(cmd));
3089      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3090        cmd->argv[0], DISPLAY_BYTES_OUT(cmd));
3091    }
3092
3093  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
3094      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
3095
3096    if (!have_err_response) {
3097
3098      /* Report the reaching of the threshold. */
3099      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3100        DISPLAY_BYTES_XFER(cmd));
3101      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3102        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
3103    }
3104  }
3105
3106  /* Check quotas to see if files download or total quota has been reached.
3107   * Report this to user if so (if not already reported).
3108   */
3109  if (sess_limit.files_out_avail != 0 &&
3110      sess_tally.files_out_used >= sess_limit.files_out_avail) {
3111
3112    if (!have_err_response) {
3113
3114      /* Report the reaching of the treshold. */
3115      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3116        DISPLAY_FILES_OUT(cmd));
3117      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3118        cmd->argv[0], DISPLAY_FILES_OUT(cmd));
3119    }
3120
3121  } else if (sess_limit.files_xfer_avail != 0 &&
3122    sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
3123
3124    if (!have_err_response) {
3125
3126      /* Report the reaching of the treshold. */
3127      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3128        DISPLAY_FILES_XFER(cmd));
3129      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3130        cmd->argv[0], DISPLAY_FILES_XFER(cmd));
3131    }
3132  }
3133
3134  have_err_response = FALSE;
3135  return PR_DECLINED(cmd);
3136}
3137
3138MODRET quotatab_pre_rmd(cmd_rec *cmd) {
3139  struct stat st;
3140
3141  /* Sanity check. */
3142  if (!use_quotas || !use_dirs)
3143    return PR_DECLINED(cmd);
3144
3145  /* Briefly cache the size (in bytes) of the directory to be deleted, so that
3146   * if successful, the byte counts can be adjusted correctly.
3147   */
3148  pr_fs_clear_cache();
3149  if (pr_fsio_lstat(cmd->arg, &st) < 0) {
3150    quotatab_disk_nbytes = 0;
3151
3152  } else {
3153    quotatab_disk_nbytes = st.st_size;
3154  }
3155
3156  return PR_DECLINED(cmd);
3157}
3158
3159MODRET quotatab_post_rmd(cmd_rec *cmd) {
3160
3161  /* Sanity check. */
3162  if (!use_quotas || !use_dirs)
3163    return PR_DECLINED(cmd);
3164
3165  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3166    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3167      cmd->argv[0], cmd->arg, quota_exclude_filter);
3168    return PR_DECLINED(cmd);
3169  }
3170
3171  /* Write out an updated quota entry. */
3172  QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, 0, -1, 0, -1)
3173
3174  /* Clear the cached bytes. */
3175  quotatab_disk_nbytes = 0;
3176
3177  return PR_DECLINED(cmd);
3178}
3179
3180MODRET quotatab_pre_rnto(cmd_rec *cmd) {
3181  struct stat st;
3182
3183  /* Sanity check */
3184  if (!use_quotas)
3185    return PR_DECLINED(cmd);
3186
3187  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3188    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3189      cmd->argv[0], cmd->arg, quota_exclude_filter);
3190    return PR_DECLINED(cmd);
3191  }
3192
3193  /* Briefly cache the size (in bytes) of the file being overwritten, so that
3194   * if successful, the byte counts can be adjusted correctly.
3195   */
3196  pr_fs_clear_cache();
3197  if (pr_fsio_lstat(cmd->arg, &st) < 0) {
3198    quotatab_disk_nbytes = 0;
3199    quotatab_disk_nfiles = 0;
3200
3201  } else {
3202    quotatab_disk_nbytes = st.st_size;
3203    quotatab_disk_nfiles = 1;
3204  }
3205
3206  return PR_DECLINED(cmd);
3207}
3208
3209MODRET quotatab_post_rnto(cmd_rec *cmd) {
3210
3211  /* Sanity check */
3212  if (!use_quotas)
3213    return PR_DECLINED(cmd);
3214
3215  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3216    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3217      cmd->argv[0], cmd->arg, quota_exclude_filter);
3218    return PR_DECLINED(cmd);
3219  }
3220
3221  /* Write out an updated quota entry. */
3222  QUOTATAB_TALLY_WRITE(-quotatab_disk_nbytes, 0, -quotatab_disk_nbytes,
3223    -quotatab_disk_nfiles, 0, -quotatab_disk_nfiles)
3224
3225  /* Clear the cached bytes/files. */
3226  quotatab_disk_nbytes = 0;
3227  quotatab_disk_nfiles = 0;
3228 
3229  return PR_DECLINED(cmd);
3230}
3231
3232MODRET quotatab_pre_stor(cmd_rec *cmd) {
3233  struct stat st;
3234 
3235  have_aborted_transfer = FALSE;
3236  have_err_response = FALSE;
3237
3238  /* Sanity check */
3239  if (!use_quotas) {
3240    have_quota_update = 0;
3241    return PR_DECLINED(cmd);
3242  }
3243
3244  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3245    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3246      cmd->argv[0], cmd->arg, quota_exclude_filter);
3247    have_quota_update = 0;
3248    return PR_DECLINED(cmd);
3249  }
3250
3251  /* Refresh the tally */
3252  QUOTATAB_TALLY_READ
3253
3254  /* Check quotas to see if bytes upload or total quota has been reached.
3255   * Block command if so.
3256   */
3257  if (sess_limit.bytes_in_avail > 0.0 &&
3258      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
3259
3260    /* Report the exceeding of the threshold. */
3261    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
3262      DISPLAY_BYTES_IN(cmd));
3263    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
3264      cmd->argv[0], DISPLAY_BYTES_IN(cmd));
3265    have_err_response = TRUE;
3266
3267    /* Set an appropriate errno value. */
3268    errno = get_quota_exceeded_errno(EPERM, NULL);
3269
3270    return PR_ERROR(cmd);
3271
3272  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
3273      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
3274
3275    /* Report the exceeding of the threshold. */
3276    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
3277      DISPLAY_BYTES_XFER(cmd));
3278    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
3279      cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
3280    have_err_response = TRUE;
3281
3282    /* Set an appropriate errno value. */
3283    errno = get_quota_exceeded_errno(EPERM, NULL);
3284
3285    return PR_ERROR(cmd);
3286  }
3287
3288  /* Check quotas to see if files upload or total quota has been reached.
3289   * Block the command if so.
3290   */
3291  if (sess_limit.files_in_avail != 0 &&
3292      sess_tally.files_in_used >= sess_limit.files_in_avail) {
3293
3294    /* Report the exceeding of the threshold. */
3295    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
3296      DISPLAY_FILES_IN(cmd));
3297    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
3298      cmd->argv[0], DISPLAY_FILES_IN(cmd));
3299    have_err_response = TRUE;
3300
3301    /* Set an appropriate errno value. */
3302    errno = get_quota_exceeded_errno(EPERM, NULL);
3303
3304    return PR_ERROR(cmd);
3305
3306  } else if (sess_limit.files_xfer_avail != 0 &&
3307      sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
3308
3309    /* Report the exceeding of the threshold. */
3310    quotatab_log("%s denied: quota exceeded: used %s", cmd->argv[0],
3311      DISPLAY_FILES_XFER(cmd));
3312    pr_response_add_err(R_552, _("%s denied: quota exceeded: used %s"),
3313      cmd->argv[0], DISPLAY_FILES_XFER(cmd));
3314    have_err_response = TRUE;
3315
3316    /* Set an appropriate errno value. */
3317    errno = get_quota_exceeded_errno(EPERM, NULL);
3318
3319    return PR_ERROR(cmd);
3320  }
3321
3322  /* Briefly cache the size (in bytes) of the file being appended to, so that
3323   * if successful, the byte counts can be adjusted correctly.  If the
3324   * stat fails, it means that a new file is being uploaded, so set the
3325   * disk_nbytes to be zero.
3326   */
3327  pr_fs_clear_cache();
3328  if (pr_fsio_lstat(cmd->arg, &st) < 0) {
3329    quotatab_disk_nbytes = 0;
3330
3331  } else {
3332    quotatab_disk_nbytes = st.st_size;
3333  }
3334
3335  have_quota_update = QUOTA_HAVE_WRITE_UPDATE;
3336  return PR_DECLINED(cmd);
3337}
3338
3339MODRET quotatab_post_stor(cmd_rec *cmd) {
3340  struct stat st;
3341  off_t store_bytes = session.xfer.total_bytes;
3342
3343  /* Sanity check */
3344  if (!use_quotas) {
3345    have_quota_update = 0;
3346    return PR_DECLINED(cmd);
3347  }
3348
3349  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3350    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3351      cmd->argv[0], cmd->arg, quota_exclude_filter);
3352    have_quota_update = 0;
3353    return PR_DECLINED(cmd);
3354  }
3355
3356  /* If the transfer was aborted, AND if DeleteAbortedStores is on, then
3357   * don't update the tally (Bug#3621).
3358   */
3359  if (have_aborted_transfer ||
3360      (session.sf_flags & (SF_ABORT|SF_POST_ABORT))) {
3361    unsigned char *delete_stores;
3362
3363    delete_stores = get_param_ptr(CURRENT_CONF, "DeleteAbortedStores", FALSE);
3364    if (delete_stores != NULL &&
3365        *delete_stores == TRUE) {
3366      quotatab_log("%s: upload aborted and DeleteAbortedStores on, "
3367        "skipping tally update", cmd->argv[0]);
3368      have_quota_update = 0;
3369      return PR_DECLINED(cmd);
3370    }
3371  }
3372
3373  /* Check on the size of the stored file again, and use the difference
3374   * in file size as the increment.  Make sure that no caching effects
3375   * mess with the stat.
3376   */
3377  pr_fs_clear_cache();
3378  if (pr_fsio_lstat(cmd->arg, &st) >= 0) {
3379    store_bytes = st.st_size - quotatab_disk_nbytes;
3380
3381  } else {
3382    if (errno == ENOENT) {
3383      store_bytes = 0;
3384
3385    } else {
3386      quotatab_log("%s: error checking '%s': %s", cmd->argv[0], cmd->arg,
3387        strerror(errno));
3388    }
3389  }
3390
3391  /* NOTE: if use_dirs is TRUE, also take into consideration the increased
3392   * disk usage caused by any increase in the size of the containing directory.
3393   */
3394
3395  /* Write out an updated quota entry */
3396  QUOTATAB_TALLY_WRITE(store_bytes, 0, session.xfer.total_bytes,
3397    quotatab_disk_nbytes ? 0 : 1, 0, 1)
3398
3399  /* Check quotas to see if bytes upload or total quota has been reached.
3400   * Report this to user if so.  If it is a hard bytes limit, delete the
3401   * uploaded file.
3402   */
3403  if (sess_limit.bytes_in_avail > 0.0 &&
3404      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
3405
3406    if (!have_err_response) {
3407      /* Report the reaching of the threshold. */
3408      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3409        DISPLAY_BYTES_IN(cmd));
3410      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3411        cmd->argv[0], DISPLAY_BYTES_IN(cmd));
3412    }
3413
3414    if (sess_tally.bytes_in_used > sess_limit.bytes_in_avail &&
3415        sess_limit.quota_limit_type == HARD_LIMIT) {
3416      int res;
3417
3418      res = pr_fsio_unlink(cmd->arg);
3419      if (res < 0 &&
3420          errno == EISDIR &&
3421          use_dirs == TRUE) {
3422        res = pr_fsio_rmdir(cmd->arg);
3423      }
3424
3425      if (res < 0) {
3426        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
3427          strerror(errno));
3428
3429      } else {
3430        QUOTATAB_TALLY_WRITE(-store_bytes, 0, -session.xfer.total_bytes,
3431          -1, 0, -1);
3432       
3433        /* Report the removal of the file. */
3434        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
3435        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
3436          cmd->argv[0], cmd->arg);
3437      }
3438    }
3439
3440  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
3441      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
3442
3443    if (!have_err_response) {
3444      /* Report the reaching of the threshold. */
3445      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3446        DISPLAY_BYTES_XFER(cmd));
3447      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3448        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
3449    }
3450
3451    if (sess_tally.bytes_xfer_used > sess_limit.bytes_xfer_avail &&
3452        sess_limit.quota_limit_type == HARD_LIMIT) {
3453      int res;
3454
3455      res = pr_fsio_unlink(cmd->arg);
3456      if (res < 0 &&
3457          errno == EISDIR &&
3458          use_dirs == TRUE) {
3459        res = pr_fsio_rmdir(cmd->arg);
3460      }
3461
3462      if (res < 0) {
3463        quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
3464          strerror(errno));
3465
3466      } else {
3467        QUOTATAB_TALLY_WRITE(-store_bytes, 0, -session.xfer.total_bytes,
3468          -1, 0, -1);
3469
3470        /* Report the removal of the file. */
3471        quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0], cmd->arg);
3472        pr_response_add(R_DUP, _("%s: notice: quota reached: '%s' removed"),
3473          cmd->argv[0], cmd->arg);
3474      }
3475    }
3476  }
3477
3478  /* Check quotas to see if files upload or total quota has been reached.
3479   * Report this to user if so.
3480   */
3481  if (sess_limit.files_in_avail != 0 &&
3482      sess_tally.files_in_used >= sess_limit.files_in_avail) {
3483
3484    if (!have_err_response) {
3485      /* Report the reaching of the threshold. */
3486      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3487        DISPLAY_FILES_IN(cmd));
3488      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3489        cmd->argv[0], DISPLAY_FILES_IN(cmd));
3490    }
3491
3492  } else if (sess_limit.files_xfer_avail != 0 &&
3493      sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
3494
3495    if (!have_err_response) {
3496      /* Report the reaching of the threshold. */
3497      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3498        DISPLAY_FILES_XFER(cmd));
3499      pr_response_add(R_DUP, _("%s: notice: quota reached: used %s"),
3500        cmd->argv[0], DISPLAY_FILES_XFER(cmd));
3501    }
3502  }
3503
3504  have_quota_update = 0;
3505  return PR_DECLINED(cmd);
3506}
3507
3508MODRET quotatab_post_stor_err(cmd_rec *cmd) {
3509  struct stat st;
3510  off_t store_bytes = session.xfer.total_bytes;
3511
3512  /* Sanity check */
3513  if (!use_quotas) {
3514    have_quota_update = 0;
3515    return PR_DECLINED(cmd);
3516  }
3517
3518  if (quotatab_ignore_path(cmd->tmp_pool, cmd->arg)) {
3519    quotatab_log("%s: path '%s' matched QuotaExcludeFilter '%s', ignoring",
3520      cmd->argv[0], cmd->arg, quota_exclude_filter);
3521    have_quota_update = 0;
3522    return PR_DECLINED(cmd);
3523  }
3524
3525  /* Check on the size of the stored file again, and use the difference
3526   * in file size as the increment.  Make sure that no caching effects
3527   * mess with the stat.
3528   */
3529  pr_fs_clear_cache();
3530  if (pr_fsio_lstat(cmd->arg, &st) >= 0) {
3531    store_bytes = st.st_size - quotatab_disk_nbytes;
3532
3533  } else {
3534    if (errno == ENOENT) {
3535      store_bytes = 0;
3536
3537    } else {
3538      quotatab_log("%s: error checking '%s': %s", cmd->argv[0], cmd->arg,
3539        strerror(errno));
3540    }
3541  }
3542
3543  /* Write out an updated quota entry */
3544  QUOTATAB_TALLY_WRITE(store_bytes, 0, session.xfer.total_bytes, 0, 0, 0)
3545
3546  /* Check quotas to see if bytes upload or total quota has been reached.
3547   * Report this to user if so (if not already reported).  If it is a hard
3548   * bytes limit, delete the uploaded file.
3549   */
3550  if (sess_limit.bytes_in_avail > 0.0 &&
3551      sess_tally.bytes_in_used >= sess_limit.bytes_in_avail) {
3552
3553    if (!have_err_response) {
3554
3555      /* Report the reaching of the threshold. */
3556      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3557        DISPLAY_BYTES_IN(cmd));
3558      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3559        cmd->argv[0], DISPLAY_BYTES_IN(cmd));
3560    }
3561
3562    if (sess_tally.bytes_in_used > sess_limit.bytes_in_avail) {
3563      if (sess_limit.quota_limit_type == HARD_LIMIT) {
3564        int res;
3565
3566        res = pr_fsio_unlink(cmd->arg);
3567        if (res < 0 &&
3568            errno == EISDIR &&
3569            use_dirs == TRUE) {
3570          res = pr_fsio_rmdir(cmd->arg);
3571        }
3572
3573        if (res < 0) {
3574          quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
3575            strerror(errno));
3576
3577        } else {
3578
3579          /* Report the removal of the file. */
3580          quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0],
3581            cmd->arg);
3582          pr_response_add_err(R_DUP,
3583            _("%s: notice: quota reached: '%s' removed"), cmd->argv[0],
3584            cmd->arg);
3585        }
3586      }
3587
3588      QUOTATAB_TALLY_WRITE(-store_bytes, 0, -session.xfer.total_bytes, 0, 0, 0);
3589    }
3590
3591  } else if (sess_limit.bytes_xfer_avail > 0.0 &&
3592      sess_tally.bytes_xfer_used >= sess_limit.bytes_xfer_avail) {
3593
3594    if (!have_err_response) {
3595
3596      /* Report the reaching of the threshold. */
3597      quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3598        DISPLAY_BYTES_XFER(cmd));
3599      pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3600        cmd->argv[0], DISPLAY_BYTES_XFER(cmd));
3601    }
3602
3603    if (sess_tally.bytes_xfer_used > sess_limit.bytes_xfer_avail) {
3604      if (sess_limit.quota_limit_type == HARD_LIMIT) {
3605        int res;
3606
3607        res = pr_fsio_unlink(cmd->arg);
3608        if (res < 0 &&
3609            errno == EISDIR &&
3610            use_dirs == TRUE) {
3611          res = pr_fsio_rmdir(cmd->arg);
3612        }
3613
3614        if (res < 0) {
3615          quotatab_log("notice: unable to unlink '%s': %s", cmd->arg,
3616            strerror(errno));
3617
3618        } else {
3619
3620          /* Report the removal of the file. */
3621          quotatab_log("%s: quota reached: '%s' removed", cmd->argv[0],
3622            cmd->arg);
3623          pr_response_add_err(R_DUP,
3624            _("%s: notice: quota reached: '%s' removed"), cmd->argv[0],
3625            cmd->arg);
3626        }
3627      }
3628
3629      QUOTATAB_TALLY_WRITE(-store_bytes, 0, -session.xfer.total_bytes, 0, 0, 0);
3630    }
3631  }
3632
3633  /* Check quotas to see if files upload or total quota has been reached.
3634   * Report this to user if so.
3635   */
3636  if (sess_limit.files_in_avail != 0 &&
3637      sess_tally.files_in_used >= sess_limit.files_in_avail) {
3638
3639    /* Report the reaching of the threshold. */
3640    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3641      DISPLAY_FILES_IN(cmd));
3642    pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3643      cmd->argv[0], DISPLAY_FILES_IN(cmd));
3644
3645  } else if (sess_limit.files_xfer_avail != 0 &&
3646      sess_tally.files_xfer_used >= sess_limit.files_xfer_avail) {
3647
3648    /* Report the reaching of the threshold. */
3649    quotatab_log("%s: quota reached: used %s", cmd->argv[0],
3650      DISPLAY_FILES_XFER(cmd));
3651    pr_response_add_err(R_DUP, _("%s: notice: quota reached: used %s"),
3652      cmd->argv[0], DISPLAY_FILES_XFER(cmd));
3653  }
3654
3655  have_quota_update = 0;
3656  return PR_DECLINED(cmd);
3657}
3658
3659MODRET quotatab_pre_site(cmd_rec *cmd) {
3660
3661  /* Make sure it's a valid SITE command */
3662  if (cmd->argc < 2)
3663    return PR_DECLINED(cmd);
3664
3665  if (strncasecmp(cmd->argv[1], "COPY", 5) == 0) {
3666    cmd_rec *copy_cmd;
3667
3668    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, cmd->argv[1], cmd->argv[2],
3669      cmd->argv[3]);
3670    return quotatab_pre_copy(copy_cmd);
3671
3672  } else if (strncasecmp(cmd->argv[1], "CPTO", 5) == 0) {
3673    register unsigned int i;
3674    cmd_rec *copy_cmd;
3675    char *from, *to = "";
3676
3677    if (cmd->argc < 3)
3678      return PR_DECLINED(cmd);
3679
3680    from = pr_table_get(session.notes, "mod_copy.cpfr-path", NULL);
3681    if (from == NULL) {
3682      pr_response_add_err(R_503, _("Bad sequence of commands"));
3683      return PR_ERROR(cmd);
3684    }
3685
3686    /* Construct the target file name by concatenating all the parameters after
3687     * the "SITE CPTO", separating them with spaces.
3688     */
3689    for (i = 2; i <= cmd->argc-1; i++) {
3690      to = pstrcat(cmd->tmp_pool, to, *to ? " " : "",
3691        pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL);
3692    }
3693
3694    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, "COPY", from, to);
3695    return quotatab_pre_copy(copy_cmd);
3696  }
3697
3698  have_quota_update = 0;
3699  return PR_DECLINED(cmd);
3700}
3701
3702MODRET quotatab_site(cmd_rec *cmd) {
3703
3704  /* Make sure it's a valid SITE QUOTA command */
3705  if (cmd->argc < 2)
3706    return PR_DECLINED(cmd);
3707
3708  if (strncasecmp(cmd->argv[1], "QUOTA", 6) == 0) {
3709    char *cmd_name;
3710    unsigned char *authenticated = get_param_ptr(cmd->server->conf,
3711      "authenticated", FALSE);
3712
3713    /* The user is required to be authenticated/logged in, first */
3714    if (!authenticated || *authenticated == FALSE) {
3715      pr_response_send(R_530, _("Please login with USER and PASS"));
3716      return PR_ERROR(cmd);
3717    }
3718
3719    /* Is showing of the user's quota barred by configuration? */
3720    if (!allow_site_quota) {
3721      pr_response_add_err(R_500, _("'SITE QUOTA' not understood."));
3722      return PR_ERROR(cmd);
3723    }
3724
3725    /* Check for <Limit> restrictions. */
3726    cmd_name = cmd->argv[0];
3727    cmd->argv[0] = "SITE_QUOTA";
3728    if (!dir_check(cmd->tmp_pool, cmd, "NONE", session.cwd, NULL)) {
3729      cmd->argv[0] = cmd_name;
3730      pr_response_add_err(R_550, "%s: %s", cmd->arg, strerror(EPERM));
3731      return PR_ERROR(cmd);
3732    }
3733    cmd->argv[0] = cmd_name;
3734
3735    /* Log that the user requested their quota. */
3736    quotatab_log("SITE QUOTA requested by user %s", session.user);
3737
3738    /* If quotas are not in use, no need to do anything. */
3739    if (!use_quotas || !have_quota_entry) {
3740      pr_response_add(R_202, _("No quotas in effect"));
3741      return PR_HANDLED(cmd);
3742    }
3743
3744    /* Refresh the tally. */
3745    QUOTATAB_TALLY_READ
3746
3747    pr_response_add(R_200,
3748      _("The current quota for this session are [current/limit]:"));
3749
3750    pr_response_add(R_DUP, _("Name: %s"), sess_limit.name);
3751    pr_response_add(R_DUP, _("Quota Type: %s"),
3752      (sess_limit.quota_type == USER_QUOTA ? _("User") :
3753       sess_limit.quota_type == GROUP_QUOTA ? _("Group") :
3754       sess_limit.quota_type == CLASS_QUOTA ? _("Class") :
3755       sess_limit.quota_type == ALL_QUOTA ? _("All") : _("(unknown)")));
3756
3757    pr_response_add(R_DUP, _("Per Session: %s"),
3758      sess_limit.quota_per_session ? _("True") : _("False"));
3759
3760    pr_response_add(R_DUP, _("Limit Type: %s"),
3761      (sess_limit.quota_limit_type == HARD_LIMIT ? _("Hard") :
3762       sess_limit.quota_limit_type == SOFT_LIMIT ? _("Soft") :
3763       _("(unknown)")));
3764
3765    pr_response_add(R_DUP, _("  Uploaded %s"),
3766      quota_display_site_bytes(cmd->tmp_pool, sess_tally.bytes_in_used,
3767      sess_limit.bytes_in_avail, IN));
3768    pr_response_add(R_DUP, _("  Downloaded %s"),
3769      quota_display_site_bytes(cmd->tmp_pool, sess_tally.bytes_out_used,
3770      sess_limit.bytes_out_avail, OUT));
3771    pr_response_add(R_DUP, _("  Transferred %s"),
3772      quota_display_site_bytes(cmd->tmp_pool, sess_tally.bytes_xfer_used,
3773      sess_limit.bytes_xfer_avail, XFER));
3774
3775    pr_response_add(R_DUP, _("  Uploaded %s"),
3776      quota_display_site_files(cmd->tmp_pool, sess_tally.files_in_used,
3777      sess_limit.files_in_avail, IN));
3778    pr_response_add(R_DUP, _("  Downloaded %s"),
3779      quota_display_site_files(cmd->tmp_pool, sess_tally.files_out_used,
3780      sess_limit.files_out_avail, OUT));
3781    pr_response_add(R_DUP, _("  Transferred %s"),
3782      quota_display_site_files(cmd->tmp_pool, sess_tally.files_xfer_used,
3783      sess_limit.files_xfer_avail, XFER));
3784
3785    /* Add one final line to preserve the spacing. */
3786    pr_response_add(R_DUP,
3787      _("Please contact %s if these entries are inaccurate"),
3788      cmd->server->ServerAdmin ? cmd->server->ServerAdmin : _("ftp-admin"));
3789
3790    return PR_HANDLED(cmd);
3791  }
3792
3793  if (strncasecmp(cmd->argv[1], "HELP", 5) == 0) {
3794
3795    /* Add a description of SITE QUOTA to the output. */
3796    pr_response_add(R_214, "QUOTA");
3797  }
3798
3799  return PR_DECLINED(cmd);
3800}
3801
3802MODRET quotatab_post_site(cmd_rec *cmd) {
3803
3804  /* Make sure it's a valid SITE command */
3805  if (cmd->argc < 2)
3806    return PR_DECLINED(cmd);
3807
3808  if (strncasecmp(cmd->argv[1], "COPY", 5) == 0) {
3809    cmd_rec *copy_cmd;
3810
3811    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, cmd->argv[1], cmd->argv[2],
3812      cmd->argv[3]);
3813    return quotatab_post_copy(copy_cmd);
3814
3815  } else if (strncasecmp(cmd->argv[1], "CPTO", 5) == 0) {
3816    register unsigned int i;
3817    cmd_rec *copy_cmd;
3818    char *from, *to = "";
3819
3820    if (cmd->argc < 3)
3821      return PR_DECLINED(cmd);
3822
3823    from = pr_table_get(session.notes, "mod_copy.cpfr-path", NULL);
3824    if (from == NULL) {
3825      pr_response_add_err(R_503, _("Bad sequence of commands"));
3826      return PR_ERROR(cmd);
3827    }
3828
3829    /* Construct the target file name by concatenating all the parameters after
3830     * the "SITE CPTO", separating them with spaces.
3831     */
3832    for (i = 2; i <= cmd->argc-1; i++) {
3833      to = pstrcat(cmd->tmp_pool, to, *to ? " " : "",
3834        pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL);
3835    }
3836
3837    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, "COPY", from, to);
3838    return quotatab_post_copy(copy_cmd);
3839  }
3840
3841  return PR_DECLINED(cmd);
3842}
3843
3844MODRET quotatab_post_site_err(cmd_rec *cmd) {
3845
3846  /* Make sure it's a valid SITE command */
3847  if (cmd->argc < 2)
3848    return PR_DECLINED(cmd);
3849
3850  if (strncasecmp(cmd->argv[1], "COPY", 5) == 0) {
3851    cmd_rec *copy_cmd;
3852
3853    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, cmd->argv[1], cmd->argv[2],
3854      cmd->argv[3]);
3855    return quotatab_post_copy_err(copy_cmd);
3856
3857  } else if (strncasecmp(cmd->argv[1], "CPTO", 5) == 0) {
3858    register unsigned int i;
3859    cmd_rec *copy_cmd;
3860    char *from, *to = "";
3861
3862    from = pr_table_get(session.notes, "mod_copy.cpfr-path", NULL);
3863    if (from == NULL) {
3864      pr_response_add_err(R_503, _("Bad sequence of commands"));
3865      return PR_ERROR(cmd);
3866    }
3867
3868    /* Construct the target file name by concatenating all the parameters after
3869     * the "SITE CPTO", separating them with spaces.
3870     */
3871    for (i = 2; i <= cmd->argc-1; i++) {
3872      to = pstrcat(cmd->tmp_pool, to, *to ? " " : "",
3873        pr_fs_decode_path(cmd->tmp_pool, cmd->argv[i]), NULL);
3874    }
3875
3876    copy_cmd = pr_cmd_alloc(cmd->tmp_pool, 3, "COPY", from, to);
3877    return quotatab_post_copy_err(copy_cmd);
3878  }
3879
3880  have_quota_update = 0;
3881  return PR_DECLINED(cmd);
3882}
3883
3884/* Event handlers
3885 */
3886
3887static void quotatab_exit_ev(const void *event_data, void *user_data) {
3888
3889  if (have_quota_update) {
3890    /* The session may be ending abruptly, aborted or somesuch in mid-transfer,
3891     * before the change of quota state has been persisted.
3892     *
3893     * Note that it is IMPORTANT that the have_quota_update flag be cleared
3894     * BEFORE writing to the tally table; see Bug#3328.
3895     */
3896
3897    switch (have_quota_update) {
3898      case QUOTA_HAVE_READ_UPDATE:
3899        have_quota_update = 0;
3900        QUOTATAB_TALLY_WRITE(0, session.xfer.total_bytes,
3901          session.xfer.total_bytes, 0, 1, 1)
3902        break;
3903
3904      case QUOTA_HAVE_WRITE_UPDATE:
3905        have_quota_update = 0;
3906        QUOTATAB_TALLY_WRITE(session.xfer.total_bytes, 0,
3907          session.xfer.total_bytes, 1, 0, 1)
3908        break;
3909    }
3910  }
3911
3912  if (use_quotas &&
3913      have_quota_tally_table) {
3914    if (quotatab_close(TYPE_TALLY) < 0)
3915      quotatab_log("error: unable to close QuotaTallyTable: %s",
3916        strerror(errno));
3917  }
3918
3919  quotatab_closelog();
3920  return;
3921}
3922
3923#if defined(PR_SHARED_MODULE)
3924static void quotatab_mod_unload_ev(const void *event_data, void *user_data) {
3925  if (strcmp("mod_quotatab.c", (const char *) event_data) == 0) {
3926    pr_event_unregister(&quotatab_module, NULL, NULL);
3927    pr_regex_free(NULL, quota_exclude_pre);
3928
3929    if (quotatab_pool) {
3930      destroy_pool(quotatab_pool);
3931      quotatab_pool = NULL;
3932    }
3933
3934    close(quota_logfd);
3935    quota_logfd = -1;
3936    quota_logname = NULL;
3937  }
3938}
3939#endif
3940
3941static void quotatab_restart_ev(const void *event_data, void *user_data) {
3942
3943  /* Reset the module's memory pool. */
3944  destroy_pool(quotatab_pool);
3945  quotatab_pool = make_sub_pool(permanent_pool);
3946  pr_pool_tag(quotatab_pool, MOD_QUOTATAB_VERSION);
3947
3948  return;
3949}
3950
3951/* Initialization routines
3952 */
3953
3954static int quotatab_init(void) {
3955
3956  /* Initialize the module's memory pool. */
3957  if (!quotatab_pool) {
3958    quotatab_pool = make_sub_pool(permanent_pool);
3959    pr_pool_tag(quotatab_pool, MOD_QUOTATAB_VERSION);
3960  }
3961
3962#if defined(PR_SHARED_MODULE)
3963  pr_event_register(&quotatab_module, "core.module-unload",
3964    quotatab_mod_unload_ev, NULL);
3965#endif
3966  pr_event_register(&quotatab_module, "core.restart", quotatab_restart_ev,
3967    NULL);
3968
3969  return 0;
3970}
3971
3972static int quotatab_sess_init(void) {
3973  config_rec *c;
3974  unsigned char *quotatab_engine = NULL, *quotatab_showquotas = NULL,
3975    *quotatab_usedirs = NULL;
3976  quota_units_t *units = NULL;
3977
3978  /* Check to see if quotas are enabled for this server. */
3979  quotatab_engine = get_param_ptr(main_server->conf, "QuotaEngine", FALSE);
3980  if (quotatab_engine != NULL &&
3981      *quotatab_engine == TRUE) {
3982    use_quotas = TRUE;
3983
3984  } else {
3985    use_quotas = FALSE;
3986    return 0;
3987  }
3988
3989  /* Check to see if SITE QUOTA enabled for this server. */
3990  quotatab_showquotas = get_param_ptr(main_server->conf, "QuotaShowQuotas",
3991    FALSE);
3992  if (quotatab_showquotas != NULL &&
3993      *quotatab_showquotas == FALSE) {
3994    allow_site_quota = FALSE;
3995
3996 } else {
3997    allow_site_quota = TRUE;
3998  }
3999
4000  quotatab_openlog();
4001
4002  /* Open the quota limit and tally tables.  This is being done while the
4003   * proces still has root privs, so the tables _can_ be opened.  In the case
4004   * of file quotastreams, the file descriptor is cached, so that this process,
4005   * once root privs are dropped, is still able to read from and write to the
4006   * streams.  Other confstream mechanisms may need to cache something
4007   * similarly.
4008   */
4009  PRIVS_ROOT
4010  if (quotatab_open(TYPE_LIMIT) < 0) {
4011    PRIVS_RELINQUISH
4012    quotatab_log("error: unable to open QuotaLimitTable: %s", strerror(errno));
4013    have_quota_limit_table = FALSE;
4014
4015  } else {
4016    PRIVS_RELINQUISH
4017
4018    /* Verify that it's a valid limit table */
4019    if (quotatab_verify(TYPE_LIMIT))
4020      have_quota_limit_table = TRUE;
4021    else
4022      use_quotas = FALSE;
4023  }
4024
4025  PRIVS_ROOT
4026  if (quotatab_open(TYPE_TALLY) < 0) {
4027    PRIVS_RELINQUISH
4028    quotatab_log("error: unable to open QuotaTallyTable: %s", strerror(errno));
4029    have_quota_tally_table = FALSE;
4030
4031  } else {
4032    PRIVS_RELINQUISH
4033
4034    /* Verify that it's a valid tally table */
4035    if (quotatab_verify(TYPE_TALLY))
4036      have_quota_tally_table = TRUE;
4037    else
4038      use_quotas = FALSE;
4039  }
4040
4041  /* Make sure the tables will be closed when the child exits. */
4042  pr_event_register(&quotatab_module, "core.exit", quotatab_exit_ev, NULL);
4043
4044  /* Check for the units to display for byte quotas. */
4045  units = get_param_ptr(main_server->conf, "QuotaDisplayUnits", FALSE);
4046  byte_units = units ? *units : BYTE;
4047
4048  /* Check to see if directories are to be used for tallies for this server. */
4049  quotatab_usedirs = get_param_ptr(main_server->conf, "QuotaDirectoryTally",
4050    FALSE);
4051  if (quotatab_usedirs != NULL &&
4052      *quotatab_usedirs == TRUE) {
4053    use_dirs = TRUE;
4054
4055  } else {
4056    use_dirs = FALSE;
4057  }
4058
4059  c = find_config(main_server->conf, CONF_PARAM, "QuotaExcludeFilter", FALSE);
4060  if (c &&
4061      c->argc == 2) {
4062     quota_exclude_filter = c->argv[0];
4063     quota_exclude_pre = c->argv[1];
4064  }
4065
4066  c = find_config(main_server->conf, CONF_PARAM, "QuotaOptions", FALSE);
4067  if (c) {
4068    quotatab_opts = *((unsigned long *) c->argv[0]);
4069  }
4070
4071  c = find_config(main_server->conf, CONF_PARAM, "QuotaLock", FALSE);
4072  if (c) {
4073    int fd;
4074    const char *path;
4075
4076    path = c->argv[0];
4077
4078    /* Make sure the QuotaLock file exists. */
4079    PRIVS_ROOT
4080    fd = open(path, O_RDWR|O_CREAT, 0600);
4081    PRIVS_RELINQUISH
4082
4083    if (fd < 0) {
4084      quotatab_log("unable to open QuotaLock '%s': %s", path, strerror(errno));
4085
4086    } else {
4087      /* Make sure that this fd is not one of the main three. */
4088      if (fd <= STDERR_FILENO) {
4089        int res;
4090
4091        res = pr_fs_get_usable_fd(fd);
4092        if (res < 0) {
4093          quotatab_log("warning: unable to find usable fd for lockfd %d: %s",
4094            fd, strerror(errno));
4095
4096        } else {
4097          quota_lockfd = fd;
4098        }
4099
4100      } else {
4101        quota_lockfd = fd;
4102      }
4103    }
4104  }
4105
4106  return 0;
4107}
4108
4109/* Module API tables
4110 */
4111
4112static conftable quotatab_conftab[] = {
4113  { "QuotaDirectoryTally",      set_quotadirtally,      NULL },
4114  { "QuotaDisplayUnits",        set_quotadisplayunits,  NULL },
4115  { "QuotaEngine",              set_quotaengine,        NULL },
4116  { "QuotaExcludeFilter",       set_quotaexcludefilter, NULL },
4117  { "QuotaLimitTable",          set_quotatable,         NULL },
4118  { "QuotaLock",                set_quotalock,          NULL },
4119  { "QuotaLog",                 set_quotalog,           NULL },
4120  { "QuotaOptions",             set_quotaoptions,       NULL },
4121  { "QuotaShowQuotas",          set_quotashowquotas,    NULL },
4122  { "QuotaTallyTable",          set_quotatable,         NULL },
4123  { NULL }
4124};
4125
4126static cmdtable quotatab_cmdtab[] = {
4127  { POST_CMD,           C_ABOR, G_NONE, quotatab_post_abor,     FALSE,  FALSE },
4128  { PRE_CMD,            C_APPE, G_NONE, quotatab_pre_appe,      FALSE,  FALSE },
4129  { POST_CMD,           C_APPE, G_NONE, quotatab_post_appe,     FALSE,  FALSE },
4130  { POST_CMD_ERR,       C_APPE, G_NONE, quotatab_post_appe_err, FALSE,  FALSE },
4131  { PRE_CMD,            C_DELE, G_NONE, quotatab_pre_dele,      FALSE,  FALSE },
4132  { POST_CMD,           C_DELE, G_NONE, quotatab_post_dele,     FALSE,  FALSE },
4133  { POST_CMD_ERR,       C_DELE, G_NONE, quotatab_post_dele_err, FALSE,  FALSE },
4134  { PRE_CMD,            C_MKD,  G_NONE, quotatab_pre_mkd,       FALSE,  FALSE },
4135  { POST_CMD,           C_MKD,  G_NONE, quotatab_post_mkd,      FALSE,  FALSE },
4136  { POST_CMD_ERR,       C_MKD,  G_NONE, quotatab_post_mkd_err,  FALSE,  FALSE },
4137  { POST_CMD,           C_PASS, G_NONE, quotatab_post_pass,     FALSE,  FALSE },
4138  { PRE_CMD,            C_RETR, G_NONE, quotatab_pre_retr,      FALSE,  FALSE },
4139  { POST_CMD,           C_RETR, G_NONE, quotatab_post_retr,     FALSE,  FALSE },
4140  { POST_CMD_ERR,       C_RETR, G_NONE, quotatab_post_retr_err, FALSE,  FALSE },
4141  { PRE_CMD,            C_RMD,  G_NONE, quotatab_pre_rmd,       FALSE,  FALSE },
4142  { POST_CMD,           C_RMD,  G_NONE, quotatab_post_rmd,      FALSE,  FALSE },
4143  { PRE_CMD,            C_RNTO, G_NONE, quotatab_pre_rnto,      FALSE,  FALSE },
4144  { POST_CMD,           C_RNTO, G_NONE, quotatab_post_rnto,     FALSE,  FALSE },
4145  { PRE_CMD,            C_STOR, G_NONE, quotatab_pre_stor,      FALSE,  FALSE },
4146  { POST_CMD,           C_STOR, G_NONE, quotatab_post_stor,     FALSE,  FALSE },
4147  { POST_CMD_ERR,       C_STOR, G_NONE, quotatab_post_stor_err, FALSE,  FALSE },
4148  { PRE_CMD,            C_STOU, G_NONE, quotatab_pre_stor,      FALSE,  FALSE },
4149  { POST_CMD,           C_STOU, G_NONE, quotatab_post_stor,     FALSE,  FALSE },
4150  { POST_CMD_ERR,       C_STOU, G_NONE, quotatab_post_stor_err, FALSE,  FALSE },
4151  { PRE_CMD,            C_XMKD, G_NONE, quotatab_pre_mkd,       FALSE,  FALSE },
4152  { POST_CMD,           C_XMKD, G_NONE, quotatab_post_mkd,      FALSE,  FALSE },
4153  { POST_CMD_ERR,       C_XMKD, G_NONE, quotatab_post_mkd_err,  FALSE,  FALSE },
4154  { PRE_CMD,            C_XRMD, G_NONE, quotatab_pre_rmd,       FALSE,  FALSE },
4155  { POST_CMD,           C_XRMD, G_NONE, quotatab_post_rmd,      FALSE,  FALSE },
4156
4157  { PRE_CMD,            C_SITE, G_NONE, quotatab_pre_site,      FALSE,  FALSE, CL_MISC },
4158  { CMD,                C_SITE, G_NONE, quotatab_site,          FALSE,  FALSE, CL_MISC },
4159  { POST_CMD,           C_SITE, G_NONE, quotatab_post_site,     FALSE,  FALSE, CL_MISC },
4160  { POST_CMD_ERR,       C_SITE, G_NONE, quotatab_post_site_err, FALSE,  FALSE, CL_MISC },
4161
4162  /* Should this be a single HOOK instead of being in the CMD namespace? */
4163  { PRE_CMD,            "COPY", G_NONE, quotatab_pre_copy,      FALSE,  FALSE },
4164  { POST_CMD,           "COPY", G_NONE, quotatab_post_copy,     FALSE,  FALSE },
4165  { POST_CMD_ERR,       "COPY", G_NONE, quotatab_post_copy_err, FALSE,  FALSE },
4166
4167  { 0, NULL }
4168};
4169
4170module quotatab_module = {
4171  NULL, NULL,
4172
4173  /* Module API version 2.0 */
4174  0x20,
4175
4176  /* Module name */
4177  "quotatab",
4178
4179  /* Module configuration handler table */
4180  quotatab_conftab,
4181
4182  /* Module command handler table */
4183  quotatab_cmdtab,
4184
4185  /* Module authentication handler table */
4186  NULL,
4187
4188  /* Module initialization function */
4189  quotatab_init,
4190
4191  /* Session initialization function */
4192  quotatab_sess_init,
4193
4194  /* Module version */
4195  MOD_QUOTATAB_VERSION
4196};
Note: See TracBrowser for help on using the repository browser.