1 /*
2  * ProFTPD: mod_ratio -- Support upload/download ratios.
3  * Portions Copyright (c) 1998-1999 Johnie Ingram.
4  * Copyright (c) 2002 James Dogopoulos.
5  * Copyright (c) 2008-2017 The ProFTPD Project team
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  * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
13  * and other respective copyright holders give permission to link this program
14  * with OpenSSL, and distribute the resulting executable, without including
15  * the source code for OpenSSL in the source distribution.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
25  */
26 
27 #define MOD_RATIO_VERSION "mod_ratio/3.3"
28 
29 /* This is mod_ratio, contrib software for proftpd 1.2.0 and above.
30    For more information contact James Dogopoulos <jd@dynw.com> or
31    Johnie Ingram <johnie@netgod.net>.
32 
33    History Log:
34 
35    * 2002-05-24: v3.3: Fixed numerous bugs and compatibility issues with
36      changing code within ProFTPD. In other words, it /works/ again!
37      Added default AnonRatio support (AnonRatio * ...).
38 
39    * 2000-08-01: v3.2: Fixed CwdRatio directive. Ratios are no longer part
40      of CWD or NOOP commands. Fixed file byte ratio printing. It is now
41      UL:DL - the way it should be. Updated README.ratio file. - jD
42 
43    * 2000-02-15: v3.1: Added support to save user stats to plain text file.
44      (See README.ratio) Changed display to MBytes rather than Bytes. Tracks
45      to the nearest Kilobyte rather than byte. - jD
46 
47    * 1999-10-03: v3.0: Uses generic API to access SQL data at runtime.
48      Supports negative ratios (upload X to get 1) by popular demand.
49      Added proper SITE command and help.  Various presentation
50      idiosyncracies fixed.
51 
52    * 1999-06-13: v2.2: fixed ratio display, it was printing ratios in
53      reverse order.
54 
55    * 1999-05-03: v2.1: mod_mysql bugfix; rearranged CWD reply so
56      Netscape shows ratios; fixed recalculation in XRATIO.  Added
57      CwdRatioMsg directive for showing equivalent URLs (always
58      enabled).
59 
60    * 1999-04-08: v2.0: Reformat and rewrite.  Add FileRatioErrMsg,
61      ByteRatioErrMsg, and LeechRatioMsg directives and support for
62      proftpd mod_mysql.
63 
64    * 1998-09-08: v1.0: Accepted into CVS as a contrib module.
65 
66 */
67 
68 #include "conf.h"
69 
70 int gotratuser,fileerr;
71 
72 static struct
73 {
74   int fstor, fretr, frate, fcred, brate, bcred;
75   int files;
76 
77   off_t bstor, bretr;
78   off_t bytes;
79 
80   char ftext [64],btext [64];
81 
82 } stats;
83 
84 static struct
85 {
86   int enable;
87   int save;
88   char user [PR_TUNABLE_LOGIN_MAX];
89 
90   const char *rtype;          /* The ratio type currently in effect. */
91 
92   const char *filemsg;
93   const char *bytemsg;
94   const char *leechmsg;
95   const char *ratiofile;
96   const char *ratiotmp;
97 } g;
98 
99 #define RATIO_ENFORCE (stats.frate || stats.brate)
100 
101 #define RATIO_STUFFS "-%d/%lu +%d/%lu (%d %d %d %d) = %d/%lu%s%s", \
102 	    stats.fretr, (unsigned long) (stats.bretr / 1024), \
103             stats.fstor, (unsigned long) (stats.bstor / 1024), \
104             stats.frate, stats.fcred, stats.brate, stats.bcred, \
105             stats.files, (unsigned long) (stats.bytes / 1024), \
106 	    (stats.frate && stats.files < 1) ? " [NO F]" : "", \
107 	    (stats.brate && (stats.bytes / 1024) < 5) ? " [LO B]" : ""
108 #define SHORT_RATIO_STUFFS "-%d/%lu +%d/%lu = %d/%lu%s%s", \
109 	    stats.fretr, (unsigned long) (stats.bretr / 1024), \
110             stats.fstor, (unsigned long) (stats.bstor / 1024), \
111             stats.files, (unsigned long) (stats.bytes / 1024), \
112 	    (stats.frate && stats.files < 1) ? " [NO F]" : "", \
113 	    (stats.brate && (stats.bytes / 1024) < 5) ? " [LO B]" : ""
114 
115 static cmd_rec *
_make_cmd(pool * cp,int argc,...)116 _make_cmd (pool * cp, int argc, ...)
117 {
118   va_list args;
119   cmd_rec *c;
120   int i;
121 
122   pool *newpool = NULL;
123   newpool = make_sub_pool( cp );
124   c = pcalloc(newpool, sizeof(cmd_rec));
125 
126   c->pool = newpool;
127   c->argv = pcalloc(newpool, sizeof(void *) * (argc + 1));
128 
129   c->argc = argc;
130   c->stash_index = -1;
131 
132   c->argv[0] = MOD_RATIO_VERSION;
133   va_start (args, argc);
134   for (i = 0; i < argc; i++)
135     c->argv[i + 1] = (void *) va_arg (args, char *);
136   va_end (args);
137 
138   return c;
139 }
140 
141 static modret_t *
_dispatch_ratio(cmd_rec * cmd,char * match)142 _dispatch_ratio (cmd_rec * cmd, char *match)
143 {
144   authtable *m;
145   modret_t *mr = NULL;
146 
147   m = pr_stash_get_symbol2 (PR_SYM_AUTH, match, NULL, &cmd->stash_index,
148     &cmd->stash_hash);
149   while (m)
150     {
151       mr = pr_module_call (m->m, m->handler, cmd);
152       if (MODRET_ISHANDLED (mr) || MODRET_ISERROR (mr))
153 	break;
154       m = pr_stash_get_symbol2 (PR_SYM_AUTH, match, m, &cmd->stash_index,
155         &cmd->stash_hash);
156     }
157 
158   if (MODRET_ISERROR(mr))
159       pr_log_debug(DEBUG0, MOD_RATIO_VERSION ": internal error: %s",
160           MODRET_ERRMSG(mr));
161 
162   return mr;
163 }
164 
165 static modret_t *
_dispatch(cmd_rec * cmd,char * match)166 _dispatch (cmd_rec * cmd, char *match)
167 {
168   cmd_rec *cr;
169   modret_t *mr = 0;
170 
171   cr = _make_cmd (cmd->tmp_pool, 0);
172   mr = _dispatch_ratio (cr, match);
173   if (cr->tmp_pool)
174     destroy_pool (cr->tmp_pool);
175   return mr;
176 }
177 
178 static void
set_stats(const char * fstor,const char * fretr,const char * bstor,const char * bretr)179 set_stats (const char *fstor, const char *fretr, const char *bstor,
180   const char *bretr)
181 {
182 
183   if (fstor)
184     stats.fstor = atoi(fstor);
185 
186   if (fretr)
187     stats.fretr = atoi(fretr);
188 
189 #ifdef HAVE_STRTOULL
190   if (bstor) {
191     char *tmp = NULL;
192     off_t res;
193 
194     res = strtoull(bstor, &tmp, 10);
195     if (tmp == NULL)
196       stats.bstor = res;
197   }
198 
199   if (bretr) {
200     char *tmp = NULL;
201     off_t res;
202 
203     res = strtoull(bretr, &tmp, 10);
204     if (tmp == NULL)
205       stats.bretr = res;
206   }
207 #else
208   if (bstor) {
209     char *tmp = NULL;
210     off_t res;
211 
212     res = strtoul(bstor, &tmp, 10);
213     if (tmp == NULL)
214       stats.bstor = res;
215   }
216 
217   if (bretr) {
218     char *tmp = NULL;
219     off_t res;
220 
221     res = strtoul(bretr, &tmp, 10);
222     if (tmp == NULL)
223       stats.bretr = res;
224   }
225 #endif /* HAVE_STRTOULL */
226 }
227 
update_ratios(const char * frate,const char * fcred,const char * brate,const char * bcred)228 static void update_ratios(const char *frate, const char *fcred,
229     const char *brate, const char *bcred) {
230   stats.frate = stats.fcred = stats.brate = stats.bcred = 0;
231 
232   if (frate)
233     stats.frate = atoi (frate);
234   if (fcred)
235     stats.fcred = atoi (fcred);
236   if (brate)
237     stats.brate = atoi (brate);
238   if (bcred)
239     stats.bcred = atoi (bcred);
240 
241   if (stats.frate >= 0)
242     {
243       stats.files = (stats.frate * stats.fstor) + stats.fcred - stats.fretr;
244       memset(stats.ftext, '\0', sizeof(stats.ftext));
245       pr_snprintf (stats.ftext, sizeof(stats.ftext)-1, "1:%dF", stats.frate);
246     }
247   else
248     {
249       stats.files = (stats.fstor / (stats.frate * -1)) + stats.fcred - stats.fretr;
250       memset(stats.ftext, '\0', sizeof(stats.ftext));
251       pr_snprintf (stats.ftext, sizeof(stats.ftext)-1, "%d:1F", stats.frate * -1);
252     }
253 
254   if (stats.brate >= 0)
255     {
256       stats.bytes = (stats.brate * stats.bstor) + stats.bcred - stats.bretr;
257       memset(stats.btext, '\0', sizeof(stats.btext));
258       pr_snprintf (stats.btext, sizeof(stats.btext)-1, "1:%dB", stats.brate);
259     }
260   else
261     {
262       stats.bytes = (stats.bstor / (stats.brate * -1)) + stats.bcred - stats.bretr;
263       memset(stats.btext, '\0', sizeof(stats.btext));
264       pr_snprintf (stats.btext, sizeof(stats.btext)-1, "%d:1B", stats.brate * -1);
265     }
266 }
267 
268 
calc_ratios(cmd_rec * cmd)269 MODRET calc_ratios (cmd_rec * cmd)
270 {
271   modret_t *mr = 0;
272   config_rec *c;
273   char buf[1024] = {'\0'};
274   char *mask;
275   char **data;
276   void *ptr;
277 
278   ptr = get_param_ptr (main_server->conf, "Ratios", FALSE);
279   if (ptr)
280     g.enable = *((int *) ptr);
281 
282   if (!g.enable)
283     return PR_DECLINED (cmd);
284 
285   mr = _dispatch (cmd, "getstats");
286   if (MODRET_HASDATA (mr))
287     {
288       data = mr->data;
289       if (data[4])
290 	pr_log_debug(DEBUG4, MOD_RATIO_VERSION
291           ": warning: getstats on %s not unique", g.user);
292       set_stats (data[0], data[1], data[2], data[3]);
293     }
294 
295   mr = _dispatch (cmd, "getratio");
296   if (MODRET_HASDATA (mr))
297     {
298       data = mr->data;
299       if (data[4])
300 	pr_log_debug(DEBUG4, MOD_RATIO_VERSION
301           ": warning: getratio on %s not unique", g.user);
302       update_ratios(data[0], data[1], data[2], data[3]);
303       g.rtype = "U";
304       return PR_DECLINED (cmd);
305     }
306 
307   c = find_config (main_server->conf, CONF_PARAM, "HostRatio", TRUE);
308   while (c)
309     {
310      mask = buf;
311       if (*(char *) c->argv[0] == '.')
312 	{
313 	  *mask++ = '*';
314 	  sstrncpy (mask, c->argv[0], sizeof (buf));
315 	}
316       else if (*(char *) ((char *) c->argv[0] + (strlen (c->argv[0]) - 1)) == '.')
317 	{
318 	  sstrncpy (mask, c->argv[0], sizeof(buf) - 2);
319 	  sstrcat(buf, "*", sizeof(buf));
320 	}
321       else
322 	sstrncpy (mask, c->argv[0], sizeof (buf));
323 
324       if (!pr_fnmatch (buf, session.c->remote_name, PR_FNM_NOESCAPE | PR_FNM_CASEFOLD) ||
325 	  !pr_fnmatch (buf, pr_netaddr_get_ipstr (session.c->remote_addr),
326 		       PR_FNM_NOESCAPE | PR_FNM_CASEFOLD))
327 	{
328 	  update_ratios(c->argv[1], c->argv[2], c->argv[3], c->argv[4]);
329 	  g.rtype = "h";
330 	  return PR_DECLINED (cmd);
331 	}
332       c = find_config_next (c, c->next, CONF_PARAM, "HostRatio", FALSE);
333     }
334 
335   c = find_config (main_server->conf, CONF_PARAM, "AnonRatio", TRUE);
336   while (c)
337     {
338       if ((session.anon_user && !strcmp (c->argv[0], session.anon_user)) ||
339 		*(char *) c->argv[0] == '*')
340 	{
341 	  update_ratios(c->argv[1], c->argv[2], c->argv[3], c->argv[4]);
342 	  g.rtype = "a";
343 	  return PR_DECLINED (cmd);
344 	}
345       c = find_config_next (c, c->next, CONF_PARAM, "AnonRatio", FALSE);
346     }
347 
348   c = find_config (main_server->conf, CONF_PARAM, "UserRatio", TRUE);
349   while (c)
350     {
351       if (*(char *) c->argv[0] == '*' || !strcmp (c->argv[0], g.user))
352 	{
353 	  update_ratios(c->argv[1], c->argv[2], c->argv[3], c->argv[4]);
354 	  g.rtype = "u";
355 	  return PR_DECLINED (cmd);
356 	}
357       c = find_config_next (c, c->next, CONF_PARAM, "UserRatio", FALSE);
358     }
359 
360   c = find_config(main_server->conf, CONF_PARAM, "GroupRatio", FALSE);
361   while (c) {
362     pr_signals_handle();
363 
364     if (strcmp(c->argv[0], session.group) == 0) {
365       update_ratios(c->argv[1], c->argv[2], c->argv[3], c->argv[4]);
366       g.rtype = "g";
367 
368       return PR_DECLINED(cmd);
369 
370     } else {
371       if (session.groups) {
372         register unsigned int i;
373         char **group_names = session.groups->elts;
374 
375         /* Check the list of supplemental groups for this user as well. */
376         for (i = 0; i < session.groups->nelts-1; i++) {
377           if (strcmp(c->argv[0], group_names[i]) == 0) {
378             update_ratios(c->argv[1], c->argv[2], c->argv[3], c->argv[4]);
379             g.rtype = "g";
380 
381             return PR_DECLINED(cmd);
382           }
383         }
384       }
385     }
386 
387     c = find_config_next(c, c->next, CONF_PARAM, "GroupRatio", FALSE);
388   }
389 
390   return PR_DECLINED (cmd);
391 }
392 
393 static void
log_ratios(cmd_rec * cmd)394 log_ratios (cmd_rec * cmd)
395 {
396   char buf[1024] = {'\0'};
397 
398   memset(buf, '\0', sizeof(buf));
399   pr_snprintf (buf, sizeof(buf)-1, SHORT_RATIO_STUFFS);
400   pr_log_debug(DEBUG0, MOD_RATIO_VERSION ": %s in %s: %s %s%s%s", g.user,
401     session.cwd, cmd->argv[0], cmd->arg, RATIO_ENFORCE ? " :" : "",
402     RATIO_ENFORCE ? buf : "");
403 }
404 
405 static void
update_stats(void)406 update_stats (void)
407 {
408     FILE *usrfile = NULL, *newfile = NULL;
409     char usrstr[256] = {'\0'}, *ratname;
410     int ulfiles,dlfiles,cpc;
411     off_t ulbytes = 0, dlbytes = 0;
412 
413     if (!fileerr) {
414         newfile = fopen(g.ratiotmp, "w");
415         if (newfile == NULL) {
416             pr_log_debug(DEBUG3, MOD_RATIO_VERSION
417                 ": error opening temporary ratios file '%s': %s", g.ratiotmp,
418                 strerror(errno));
419             gotratuser = 1;
420             fileerr = 1;
421             return;
422         }
423     }
424 
425     usrfile = fopen(g.ratiofile, "r");
426 
427     if (usrfile != NULL) {
428         while (fgets(usrstr, sizeof(usrstr), usrfile) != NULL) {
429             char *tok = NULL;
430 
431             pr_signals_handle();
432 
433             tok = strtok(usrstr, "|");
434             ratname = tok;
435 
436             tok = strtok(NULL, "|");
437             ulfiles = atoi(tok);
438 
439             tok = strtok(NULL, "|");
440             if (tok) {
441                 char *tmp = NULL;
442                 off_t res;
443 
444 #ifdef HAVE_STRTOULL
445                 res = strtoull(tok, &tmp, 10);
446 #else
447                 res = strtoul(tok, &tmp, 10);
448 #endif /* HAVE_STRTOULL */
449 
450                 if (tmp == NULL)
451                     ulbytes = res;
452             }
453 
454             tok = strtok(NULL, "|");
455             dlfiles = atoi(tok);
456 
457             tok = strtok(NULL, "|");
458             if (tok) {
459                 char *tmp = NULL;
460                 off_t res;
461 
462 #ifdef HAVE_STRTOULL
463                 res = strtoull(tok, &tmp, 10);
464 #else
465                 res = strtoul(tok, &tmp, 10);
466 #endif /* HAVE_STRTOULL */
467 
468                 if (tmp == NULL)
469                     dlbytes = res;
470             }
471 
472             if (strcmp(ratname, g.user) == 0) {
473                 fprintf(newfile, "%s|%d|%" PR_LU "|%d|%" PR_LU "\n", g.user,
474                     stats.fstor, (pr_off_t) stats.bstor, stats.fretr,
475                     (pr_off_t) stats.bretr);
476 
477             } else {
478                 fprintf(newfile, "%s|%d|%" PR_LU "|%d|%" PR_LU "\n", ratname,
479                     ulfiles, (pr_off_t) ulbytes, dlfiles, (pr_off_t) dlbytes);
480             }
481         }
482 
483     } else {
484         pr_log_debug(DEBUG3, MOD_RATIO_VERSION
485             ": error opening ratios file '%s': %s", g.ratiofile,
486             strerror(errno));
487         gotratuser = 1;
488         fileerr = 1;
489     }
490 
491     if (usrfile)
492         fclose(usrfile);
493 
494     if (newfile)
495         fclose(newfile);
496 
497     newfile = fopen(g.ratiotmp, "rb");
498     if (newfile == NULL) {
499         pr_log_debug(DEBUG3, MOD_RATIO_VERSION
500             ": error opening temporary ratios file '%s': %s", g.ratiotmp,
501             strerror(errno));
502     }
503 
504     usrfile = fopen(g.ratiofile, "wb");
505     if (usrfile == NULL) {
506         pr_log_debug(DEBUG3, MOD_RATIO_VERSION
507             ": error opening ratios file '%s': %s", g.ratiofile,
508             strerror(errno));
509     }
510 
511     if (newfile != NULL &&
512         usrfile != NULL) {
513 
514         while ((cpc = getc(newfile)) != EOF) {
515             pr_signals_handle();
516             putc(cpc, usrfile);
517         }
518     }
519 
520     if (usrfile)
521         fclose(usrfile);
522 
523     if (newfile)
524         fclose(newfile);
525 }
526 
527 MODRET
pre_cmd_retr(cmd_rec * cmd)528 pre_cmd_retr (cmd_rec * cmd)
529 {
530   char *path;
531   int fsize = 0;
532   struct stat sbuf;
533 
534   calc_ratios (cmd);
535   if (!g.enable)
536     return PR_DECLINED (cmd);
537   log_ratios (cmd);
538 
539   if (!RATIO_ENFORCE)
540     return PR_DECLINED (cmd);
541 
542   if (stats.frate && stats.files < 1)
543     {
544       pr_response_add_err (R_550, "%s", g.filemsg);
545       pr_response_add_err (R_550,
546 			"%s: FILE RATIO: %s  Down: %d  Up: only %d!",
547 			cmd->arg, stats.ftext, stats.fretr, stats.fstor);
548       return PR_ERROR (cmd);
549     }
550 
551   if (stats.brate)
552     {
553       path = dir_realpath (cmd->tmp_pool, cmd->arg);
554       if (path
555 	  && dir_check (cmd->tmp_pool, cmd, cmd->group, path, NULL)
556 	  && pr_fsio_stat (path, &sbuf) > -1)
557 	fsize = sbuf.st_size;
558 
559       if ((stats.bytes - (fsize / 1024)) < 0)
560 	{
561 	  pr_response_add_err (R_550, "%s", g.bytemsg);
562 	  pr_response_add_err (R_550,
563               "%s: BYTE RATIO: %s  Down: %lumb  Up: only %lumb!", cmd->arg,
564               stats.btext, (unsigned long) (stats.bretr / 1024),
565               (unsigned long) (stats.bstor / 1024));
566 	  return PR_ERROR (cmd);
567 	}
568     }
569 
570   return PR_DECLINED (cmd);
571 }
572 
ratio_log_pass(cmd_rec * cmd)573 MODRET ratio_log_pass(cmd_rec *cmd) {
574   if (session.anon_user) {
575     sstrncpy(g.user, session.anon_user, sizeof(g.user));
576   }
577 
578   calc_ratios (cmd);
579   if (g.enable) {
580     char buf[256];
581 
582     memset(buf, '\0', sizeof(buf));
583     pr_snprintf(buf, sizeof(buf)-1, RATIO_STUFFS);
584     pr_log_pri(PR_LOG_INFO, "Ratio: %s/%s %s[%s]: %s.", g.user,
585       session.group, session.c->remote_name, pr_netaddr_get_ipstr
586       (session.c->remote_addr), buf);
587   }
588 
589   return PR_DECLINED (cmd);
590 }
591 
592 MODRET
pre_cmd(cmd_rec * cmd)593 pre_cmd (cmd_rec * cmd)
594 {
595   if (g.enable)
596     {
597     /*  if (!strcasecmp (cmd->argv[0], "STOR")) */
598       if (strcasecmp (cmd->argv[0], "STOR") || strcasecmp(cmd->argv[0], "RETR"))
599 	calc_ratios (cmd);
600       log_ratios (cmd);
601     }
602   return PR_DECLINED (cmd);
603 }
604 
605 MODRET
cmd_cwd(cmd_rec * cmd)606 cmd_cwd (cmd_rec * cmd)
607 {
608   char *dir;
609   config_rec *c = find_config (main_server->conf, CONF_PARAM, "CwdRatioMsg", TRUE);
610   if (c)
611     {
612       dir = dir_realpath (cmd->tmp_pool, cmd->argv[1]);
613       while (dir && c)
614 	{
615 	  if (!*((char *) c->argv[0]))
616 	    return PR_DECLINED (cmd);
617 	  pr_response_add (R_250, "%s", (char *) c->argv[0]);
618 	  c = find_config_next (c, c->next, CONF_PARAM, "CwdRatioMsg", FALSE);
619 	}
620     }
621   return PR_DECLINED (cmd);
622 }
623 
ratio_post_cmd(cmd_rec * cmd)624 MODRET ratio_post_cmd(cmd_rec *cmd) {
625   FILE *usrfile = NULL, *newfile = NULL;
626   char sbuf1[128] = {'\0'}, sbuf2[128] = {'\0'},
627        sbuf3[128] = {'\0'}, usrstr[256] = {'\0'};
628   char *ratname;
629   int ulfiles,dlfiles,cpc;
630   off_t ulbytes = 0, dlbytes = 0;
631 
632   if (!gotratuser && g.save) {
633 	usrfile = fopen(g.ratiofile, "r");
634 	if (usrfile == NULL) {
635 	    pr_log_debug(DEBUG3, MOD_RATIO_VERSION
636                 ": error opening ratios file '%s': %s", g.ratiofile,
637                 strerror(errno));
638 	    gotratuser = 1;
639 	    fileerr = 1;
640         }
641   }
642 
643   if (session.anon_user)
644      sstrncpy(g.user, session.anon_user, sizeof(g.user));
645 
646   if (strlen(g.user) == 0)
647      sstrncpy(g.user, "NOBODY", sizeof(g.user));
648 
649   if (!gotratuser && !fileerr && g.save) {
650       if (!usrfile)
651           usrfile = fopen(g.ratiofile, "r");
652 
653       if (usrfile) {
654           while (fgets(usrstr, sizeof(usrstr), usrfile) != NULL) {
655               char *tok = NULL;
656 
657               pr_signals_handle();
658 
659               tok = strtok(usrstr, "|");
660               ratname = tok;
661 
662               tok = strtok(NULL, "|");
663               ulfiles = atoi(tok);
664 
665               tok = strtok(NULL, "|");
666               if (tok) {
667                   char *tmp = NULL;
668                   off_t res;
669 
670 #ifdef HAVE_STRTOULL
671                   res = strtoull(tok, &tmp, 10);
672 #else
673                   res = strtoul(tok, &tmp, 10);
674 #endif /* HAVE_STRTOULL */
675 
676                   if (tmp == NULL)
677                       ulbytes = res;
678               }
679 
680               tok = strtok(NULL, "|");
681               dlfiles = atoi(tok);
682 
683               tok = strtok(NULL, "|");
684               if (tok) {
685                   char *tmp = NULL;
686                   off_t res;
687 
688 #ifdef HAVE_STRTOULL
689                   res = strtoull(tok, &tmp, 10);
690 #else
691                   res = strtoul(tok, &tmp, 10);
692 #endif /* HAVE_STRTOULL */
693 
694                   if (tmp == NULL)
695                       dlbytes = res;
696               }
697 
698               if (strcmp(ratname, g.user) == 0) {
699                   stats.fretr += dlfiles;
700                   stats.bretr += dlbytes;
701                   stats.fstor += ulfiles;
702                   stats.bstor += ulbytes;
703                   gotratuser = 1;
704               }
705           }
706           fclose(usrfile);
707 
708       } else {
709           pr_log_debug(DEBUG3, MOD_RATIO_VERSION
710               ": error opening ratios file '%s': %s", g.ratiofile,
711               strerror(errno));
712           gotratuser = 1;
713           fileerr = 1;
714       }
715 
716       /* Entry for user must not exist, create... */
717       if (!gotratuser && !fileerr) {
718           newfile = fopen(g.ratiotmp, "w");
719           if (newfile == NULL) {
720               pr_log_debug(DEBUG3, MOD_RATIO_VERSION
721                   ": error opening temporary ratios file '%s': %s",
722                   g.ratiotmp, strerror(errno));
723               gotratuser = 1;
724               fileerr = 1;
725           }
726       }
727 
728       if (!gotratuser && !fileerr) {
729           usrfile = fopen(g.ratiofile, "r");
730           if (usrfile) {
731 
732               /* Copy the existing lines into the temporary file. */
733               while (fgets(usrstr, sizeof(usrstr), usrfile) != NULL) {
734                   pr_signals_handle();
735                   fprintf(newfile, "%s", usrstr);
736               }
737 
738               fprintf(newfile, "%s|%d|%" PR_LU "|%d|%" PR_LU "\n", g.user,
739                   stats.fstor, (pr_off_t) stats.bstor, stats.fretr,
740                   (pr_off_t) stats.bretr);
741 
742               fclose(usrfile);
743               fclose(newfile);
744 
745               /* Copy the temporary file to the actual file. */
746               newfile = fopen(g.ratiotmp, "rb");
747               usrfile = fopen(g.ratiofile, "wb");
748 
749               if (newfile != NULL &&
750                   usrfile != NULL) {
751 
752                   while ((cpc = getc(newfile)) != EOF) {
753                       pr_signals_handle();
754                       putc(cpc, usrfile);
755                   }
756               }
757 
758               if (usrfile)
759                   fclose(usrfile);
760 
761               if (newfile)
762                   fclose(newfile);
763           }
764       }
765   }
766 
767   if (g.enable) {
768       int cwding = !strcasecmp (cmd->argv[0], "CWD");
769     char *r = (cwding) ? R_250 : R_DUP;
770       sbuf1[0] = sbuf2[0] = sbuf3[0] = 0;
771       if (cwding || !strcasecmp (cmd->argv[0], "PASS"))
772 	calc_ratios (cmd);
773 
774       pr_snprintf(sbuf1, sizeof(sbuf1), "Down: %d Files (%lumb)  Up: %d Files (%lumb)",
775 	stats.fretr, (unsigned long) (stats.bretr / 1024),
776         stats.fstor, (unsigned long) (stats.bstor / 1024));
777       if (stats.frate)
778 	pr_snprintf (sbuf2, sizeof(sbuf2),
779 		  "   %s CR: %d", stats.ftext, stats.files);
780       if (stats.brate)
781 	pr_snprintf (sbuf3, sizeof(sbuf3), "   %s CR: %lu", stats.btext,
782           (unsigned long) (stats.bytes / 1024));
783 
784       if (RATIO_ENFORCE)
785 	{
786 	  pr_response_add (r, "%s%s%s", sbuf1, sbuf2, sbuf3);
787 	  if (stats.frate && stats.files < 0)
788 	    pr_response_add (r, "%s", g.filemsg);
789 	  if (stats.brate && stats.bytes < 0)
790 	    pr_response_add (r, "%s", g.bytemsg);
791 	}
792       else
793 	pr_response_add (r, "%s%s%s", sbuf1, g.leechmsg ? "  " : "", g.leechmsg);
794   }
795 
796   return PR_DECLINED(cmd);
797 }
798 
799 MODRET
cmd_site(cmd_rec * cmd)800 cmd_site (cmd_rec * cmd)
801 {
802   char buf[128] = {'\0'};
803 
804   if (cmd->argc < 2)
805     return PR_DECLINED(cmd);
806 
807   if (strcasecmp(cmd->argv[1], "RATIO") == 0) {
808     calc_ratios(cmd);
809     pr_snprintf(buf, sizeof(buf), RATIO_STUFFS);
810     pr_response_add(R_214, "Current Ratio: ( %s )", buf);
811     if(stats.frate)
812       pr_response_add(R_214,
813 		   "Files: %s  Down: %d  Up: %d  CR: %d file%s",
814 		   stats.ftext, stats.fretr, stats.fstor,
815 		   stats.files, (stats.files != 1) ? "s" : "");
816     if(stats.brate)
817       pr_response_add(R_214,
818 		   "Bytes: %s  Down: %lumb  Up: %lumb  CR: %lu Mbytes",
819 		   stats.btext, (unsigned long) (stats.bretr / 1024),
820                    (unsigned long) (stats.bstor / 1024),
821                    (unsigned long) (stats.bytes / 1024));
822     return PR_HANDLED(cmd);
823   }
824 
825   if (strcasecmp (cmd->argv[1], "HELP") == 0) {
826     pr_response_add(R_214,
827 		 "The following SITE extensions are recognized:");
828     pr_response_add(R_214, "RATIO " "-- show all ratios in effect");
829   }
830 
831   return PR_DECLINED (cmd);
832 }
833 
834 /* FIXME: because of how ratio and sql interact, the status sent after
835    STOR and RETR commands is always out-of-date.  Reorder module loading?  */
836 
ratio_post_retr(cmd_rec * cmd)837 MODRET ratio_post_retr(cmd_rec *cmd) {
838   stats.fretr++;
839   stats.bretr += (session.xfer.total_bytes / 1024);
840 
841   calc_ratios (cmd);
842 
843   if (!fileerr && g.save) {
844       update_stats ();
845   }
846 
847   return ratio_post_cmd(cmd);
848 }
849 
ratio_post_stor(cmd_rec * cmd)850 MODRET ratio_post_stor(cmd_rec *cmd) {
851   stats.fstor++;
852   stats.bstor += (session.xfer.total_bytes / 1024);
853 
854   calc_ratios (cmd);
855 
856   if (!fileerr && g.save) {
857       update_stats ();
858   }
859 
860   return ratio_post_cmd(cmd);
861 }
862 
863 MODRET
cmd_user(cmd_rec * cmd)864 cmd_user (cmd_rec * cmd)
865 {
866   if (!g.user[0])
867     sstrncpy (g.user, cmd->argv[1], PR_TUNABLE_LOGIN_MAX);
868   return PR_DECLINED (cmd);
869 }
870 
871 /* **************************************************************** */
872 
873 MODRET
add_ratiodata(cmd_rec * cmd)874 add_ratiodata (cmd_rec * cmd)
875 {
876   CHECK_ARGS (cmd, 5);
877   CHECK_CONF (cmd,
878 	      CONF_ROOT | CONF_VIRTUAL | CONF_ANON | CONF_DIR | CONF_GLOBAL);
879   add_config_param_str (cmd->argv[0], 5, (void *) cmd->argv[1],
880 			(void *) cmd->argv[2], (void *) cmd->argv[3],
881 			(void *) cmd->argv[4], (void *) cmd->argv[5]);
882   return PR_HANDLED (cmd);
883 }
884 
set_ratios(cmd_rec * cmd)885 MODRET set_ratios(cmd_rec *cmd) {
886   int bool;
887   config_rec *c;
888 
889   CHECK_ARGS(cmd, 1);
890   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON|CONF_DIR);
891 
892   bool = get_boolean(cmd, 1);
893   if (bool == -1) {
894     CONF_ERROR(cmd, "expected Boolean parameter");
895   }
896 
897   c = add_config_param(cmd->argv[0], 1, NULL);
898   c->argv[0] = pcalloc(c->pool, sizeof(int));
899   *((int *) c->argv[0]) = bool;
900   c->flags |= CF_MERGEDOWN;
901 
902   return PR_HANDLED(cmd);
903 }
904 
905 MODRET
add_saveratios(cmd_rec * cmd)906 add_saveratios (cmd_rec * cmd)
907 {
908   int b;
909   config_rec *c;
910 
911   CHECK_ARGS (cmd, 1);
912   CHECK_CONF (cmd, CONF_ROOT | CONF_VIRTUAL
913               | CONF_ANON | CONF_DIR | CONF_GLOBAL);
914   b = get_boolean (cmd, 1);
915   if (b == -1)
916     CONF_ERROR (cmd, "requires a boolean value");
917   c = add_config_param (cmd->argv[0], 1, NULL);
918   c->argv[0] = pcalloc(c->pool, sizeof(int));
919   *((int *) c->argv[0]) = b;
920   c->flags |= CF_MERGEDOWN;
921   return PR_HANDLED (cmd);
922 }
923 
924 MODRET
add_str(cmd_rec * cmd)925 add_str (cmd_rec * cmd)
926 {
927   CHECK_ARGS (cmd, 1);
928   CHECK_CONF (cmd, CONF_ROOT | CONF_VIRTUAL
929 	      | CONF_ANON | CONF_DIR | CONF_GLOBAL);
930   add_config_param_str (cmd->argv[0], 1, (void *) cmd->argv[1]);
931   return PR_HANDLED (cmd);
932 }
933 
934 /* **************************************************************** */
935 
ratio_sess_init(void)936 static int ratio_sess_init(void) {
937   void *ptr;
938 
939   memset(&g, 0, sizeof (g));
940 
941   ptr = get_param_ptr(TOPLEVEL_CONF, "Ratios", FALSE);
942   if (ptr) {
943     g.enable = *((int *) ptr);
944   }
945 
946   ptr = get_param_ptr (TOPLEVEL_CONF, "SaveRatios", FALSE);
947   if (ptr)
948     g.save = *((int *) ptr);
949 
950   if (!(g.filemsg = get_param_ptr (TOPLEVEL_CONF, "FileRatioErrMsg", FALSE)))
951     g.filemsg = "Too few files uploaded to earn file -- please upload more.";
952 
953   if (!(g.ratiofile = get_param_ptr (TOPLEVEL_CONF, "RatioFile", FALSE)))
954     g.ratiofile = "";
955 
956   if (!(g.ratiotmp = get_param_ptr (TOPLEVEL_CONF, "RatioTempFile", FALSE)))
957     g.ratiotmp = "";
958 
959   if (!(g.bytemsg = get_param_ptr (TOPLEVEL_CONF, "ByteRatioErrMsg", FALSE)))
960     g.bytemsg = "Too few bytes uploaded to earn more data -- please upload.";
961 
962   if (!(g.leechmsg = get_param_ptr (TOPLEVEL_CONF, "LeechRatioMsg", FALSE)))
963     g.leechmsg = "10,000,000:1  CR: LEECH";
964 
965   return 0;
966 }
967 
968 /* Module API tables
969  */
970 
971 static cmdtable ratio_cmdtab[] = {
972   { PRE_CMD,  C_CWD,	G_NONE, pre_cmd, 	FALSE, FALSE },
973   { CMD,      C_CWD,	G_NONE, cmd_cwd, 	FALSE, FALSE },
974 
975   { PRE_CMD,  C_LIST,	G_NONE, pre_cmd, 	FALSE, FALSE },
976   { POST_CMD, C_LIST,	G_NONE, ratio_post_cmd,	FALSE, FALSE },
977 
978   { PRE_CMD,  C_NLST,	G_NONE, pre_cmd, 	FALSE, FALSE },
979   { POST_CMD, C_NLST,	G_NONE, ratio_post_cmd,	FALSE, FALSE },
980 
981   { PRE_CMD,  C_RETR,   G_NONE, pre_cmd_retr,	FALSE, FALSE },
982   { POST_CMD, C_RETR,   G_NONE, ratio_post_retr,FALSE, FALSE },
983 
984   { PRE_CMD,  C_STOR,	G_NONE, pre_cmd, 	FALSE, FALSE },
985   { POST_CMD, C_STOR,	G_NONE, ratio_post_stor,FALSE, FALSE },
986 
987   { CMD,      C_SITE,	G_NONE, cmd_site, 	FALSE, FALSE },
988 
989   { CMD,      C_USER,	G_NONE, cmd_user, 	FALSE, FALSE },
990 
991   { POST_CMD, C_PASS,	G_NONE, ratio_post_cmd, FALSE, FALSE },
992   { LOG_CMD,  C_PASS,	G_NONE, ratio_log_pass, FALSE, FALSE },
993 
994   { 0, NULL }
995 };
996 
997 static conftable ratio_conftab[] = {
998   { "UserRatio",	add_ratiodata,       NULL },
999   { "GroupRatio",	add_ratiodata,       NULL },
1000   { "AnonRatio",	add_ratiodata,       NULL },
1001   { "HostRatio",	add_ratiodata,       NULL },
1002   { "Ratios",	        set_ratios,          NULL },
1003 
1004   { "FileRatioErrMsg",	add_str,             NULL },
1005   { "ByteRatioErrMsg",	add_str,             NULL },
1006   { "LeechRatioMsg",	add_str,             NULL },
1007   { "CwdRatioMsg",	add_str,             NULL },
1008   { "SaveRatios",	add_saveratios,	     NULL },
1009   { "RatioFile",	add_str,	     NULL },
1010   { "RatioTempFile",	add_str,	     NULL },
1011 
1012   { NULL, NULL, NULL }
1013 };
1014 
1015 module ratio_module = {
1016 
1017   /* Always NULL */
1018   NULL, NULL,
1019 
1020   /* Module API version */
1021   0x20,
1022 
1023   /* Module name */
1024   "ratio",
1025 
1026   /* Module configuration handler table */
1027   ratio_conftab,
1028 
1029   /* Module command handler table */
1030   ratio_cmdtab,
1031 
1032   /* Module authentication handler table */
1033   NULL,
1034 
1035   /* Module initialization */
1036   NULL,
1037 
1038   /* Session initialization */
1039   ratio_sess_init,
1040 
1041   /* Module version */
1042   MOD_RATIO_VERSION
1043 };
1044