1 /*
2  * ProFTPD: mod_shaper -- a module implementing daemon-wide rate throttling
3  *                        via IPC
4  * Copyright (c) 2004-2017 TJ Saunders
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19  *
20  * As a special exemption, TJ Saunders and other respective copyright holders
21  * give permission to link this program with OpenSSL, and distribute the
22  * resulting executable, without including the source code for OpenSSL in the
23  * source distribution.
24  *
25  * This is mod_shaper, contrib software for proftpd 1.2 and above.
26  * For more information contact TJ Saunders <tj@castaglia.org>.
27  */
28 
29 #include "conf.h"
30 #include "privs.h"
31 #include "mod_ctrls.h"
32 
33 #include <sys/mman.h>
34 #include <sys/ipc.h>
35 #include <sys/msg.h>
36 #include <sys/uio.h>
37 
38 #define MOD_SHAPER_VERSION		"mod_shaper/0.6.6"
39 
40 /* Make sure the version of proftpd is as necessary. */
41 #if PROFTPD_VERSION_NUMBER < 0x0001030402
42 # error "ProFTPD 1.3.4rc2 or later required"
43 #endif
44 
45 #ifndef PR_USE_CTRLS
46 # error "Controls support required (use --enable-ctrls)"
47 #endif
48 
49 /* From src/main.c */
50 extern pid_t mpid;
51 
52 module shaper_module;
53 
54 static ctrls_acttab_t shaper_acttab[];
55 static const char *shaper_down_cmds[] = { C_RETR, NULL };
56 static const char *shaper_up_cmds[] = { C_APPE, C_STOR, C_STOU, NULL };
57 static int shaper_engine = FALSE;
58 static char *shaper_log_path = NULL;
59 static int shaper_logfd = -1;
60 static pool *shaper_pool = NULL;
61 static int shaper_qid = -1;
62 static unsigned long shaper_qmaxbytes = 0;
63 static int shaper_scrub_timer_id = -1;
64 static char *shaper_tab_path = NULL;
65 static pool *shaper_tab_pool = NULL;
66 static int shaper_tabfd = -1;
67 
68 #define SHAPER_PROJ_ID		24
69 
70 #if defined(FREEBSD4)
71 #  define SHAPER_IOV_BASE		(char *)
72 #elif defined(LINUX)
73 #  define SHAPER_IOV_BASE		(__ptr_t)
74 #elif defined(SOLARIS2)
75 #  define SHAPER_IOV_BASE		(caddr_t)
76 #else
77 #  define SHAPER_IOV_BASE		(void *)
78 #endif
79 
80 #ifndef HAVE_FLOCK
81 # define LOCK_SH	1
82 # define LOCK_EX	2
83 # define LOCK_NB	4
84 # define LOCK_UN	8
85 #endif /* !HAVE_FLOCK */
86 
87 #define SHAPER_DEFAULT_RATE		-1.0
88 #define SHAPER_DEFAULT_PRIO		10
89 #define SHAPER_DEFAULT_SHARES		5
90 #define SHAPER_DEFAULT_UPSHARES		SHAPER_DEFAULT_SHARES
91 #define SHAPER_DEFAULT_DOWNSHARES	SHAPER_DEFAULT_SHARES
92 
93 #define SHAPER_SCRUB_INTERVAL		60
94 
95 #ifndef SHAPER_MAX_SEND_ATTEMPTS
96 # define SHAPER_MAX_SEND_ATTEMPTS	5
97 #endif
98 
99 struct shaper_sess {
100   pid_t sess_pid;
101   unsigned int sess_prio;
102   int sess_downincr;
103   long double sess_downrate;
104   int sess_upincr;
105   long double sess_uprate;
106 };
107 
108 struct {
109   int def_prio;
110   long double downrate;
111   unsigned int def_downshares;
112   long double uprate;
113   unsigned int def_upshares;
114   unsigned int nsessions;
115   array_header *sess_list;
116 
117 } shaper_tab;
118 
119 /* Define our own structure for messages, since one is not portably defined.
120  */
121 struct shaper_msg {
122   /* Message type */
123   long mtype;
124 
125   /* Message data */
126   char mtext[1];
127 };
128 
129 /* Necessary function prototypes. */
130 static void shaper_msg_clear(pid_t);
131 static int shaper_rate_alter(unsigned int, long double, long double);
132 static void shaper_sess_exit_ev(const void *, void *);
133 static void shaper_sigusr2_ev(const void *, void *);
134 static int shaper_table_send(void);
135 
136 /* Support functions
137  */
138 
shaper_get_key(const char * path)139 static key_t shaper_get_key(const char *path) {
140   pr_fh_t *fh;
141   struct stat st;
142 
143   /* ftok() uses stat(2) on the given path, which means that it needs to exist.
144    */
145   fh = pr_fsio_open(path, O_WRONLY|O_CREAT);
146   if (fh == NULL) {
147     int xerrno = errno;
148 
149     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
150       "error opening '%s': %s", path, strerror(xerrno));
151 
152     errno = xerrno;
153     return -1;
154   }
155 
156   if (pr_fsio_fstat(fh, &st) < 0) {
157     int xerrno = errno;
158 
159     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
160       "error checking '%s': %s", path, strerror(xerrno));
161 
162     pr_fsio_close(fh);
163     errno = xerrno;
164     return -1;
165   }
166 
167   if (S_ISDIR(st.st_mode)) {
168     int xerrno = EISDIR;
169 
170     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
171       "error using '%s': %s", path, strerror(xerrno));
172 
173     pr_fsio_close(fh);
174     errno = xerrno;
175     return -1;
176   }
177 
178   pr_fsio_close(fh);
179 
180   return ftok(path, SHAPER_PROJ_ID);
181 }
182 
shaper_get_queue(const char * path)183 static int shaper_get_queue(const char *path) {
184   int qid;
185 
186   /* Obtain a key for this path. */
187   key_t key = shaper_get_key(path);
188   if (key == (key_t) -1) {
189     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
190       "unable to get key for '%s': %s", path, strerror(errno));
191     return -1;
192   }
193 
194   /* Try first using IPC_CREAT|IPC_EXCL, to check if there is an existing
195    * queue for this key.  If there is, try again, using a flag of zero.
196    */
197   qid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
198   if (qid < 0) {
199     if (errno == EEXIST)
200       qid = msgget(key, 0);
201 
202     else
203       return -1;
204   }
205 
206   return qid;
207 }
208 
shaper_remove_queue(void)209 static int shaper_remove_queue(void) {
210   struct msqid_ds ds;
211   int res;
212 
213   memset(&ds, 0, sizeof(ds));
214 
215   res = msgctl(shaper_qid, IPC_RMID, &ds);
216   if (res < 0) {
217     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
218       "error removing queue ID %d: %s", shaper_qid, strerror(errno));
219 
220   } else {
221     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
222       "removed queue ID %d", shaper_qid);
223     shaper_qid = -1;
224   }
225 
226   return res;
227 }
228 
shaper_msg_recv(void)229 static int shaper_msg_recv(void) {
230   int nmsgs = 0;
231   ssize_t msglen = 0;
232   struct shaper_msg *msg;
233 
234   /* The expected message length consists of a priority (the unsigned int),
235    * the new downrate (the long double), and the new uprate (another long
236    * double).
237    */
238   size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
239     sizeof(long double);
240 
241   msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
242   if (msg == NULL) {
243     pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
244     pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
245   }
246 
247   msglen = msgrcv(shaper_qid, msg, msgsz, getpid(), IPC_NOWAIT|MSG_NOERROR);
248   while (msglen > 0) {
249     unsigned int prio;
250     long double downrate, uprate;
251 
252     pr_signals_handle();
253     nmsgs++;
254 
255     memcpy(&prio, msg->mtext, sizeof(unsigned int));
256     memcpy(&downrate, msg->mtext + sizeof(unsigned int), sizeof(long double));
257     memcpy(&uprate, msg->mtext + sizeof(unsigned int) + sizeof(long double),
258       sizeof(long double));
259 
260     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
261       "received prio %u, rate %3.2Lf down, %3.2Lf up", prio, downrate,
262       uprate);
263 
264     if (shaper_rate_alter(prio, downrate, uprate) < 0) {
265       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
266         "error altering rate for current session: %s", strerror(errno));
267     }
268 
269     msglen = msgrcv(shaper_qid, msg, msgsz, getpid(), IPC_NOWAIT|MSG_NOERROR);
270   }
271 
272   free(msg);
273 
274   if (msglen < 0 &&
275 #ifdef ENOMSG
276       errno != ENOMSG &&
277 #endif /* ENOMSG */
278       errno != EAGAIN)
279     return -1;
280 
281   return nmsgs;
282 }
283 
shaper_msg_send(pid_t dst_pid,unsigned int prio,long double downrate,long double uprate)284 static int shaper_msg_send(pid_t dst_pid, unsigned int prio,
285     long double downrate, long double uprate) {
286   unsigned int error_count = 0;
287   int res;
288   struct shaper_msg *msg;
289   size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
290     sizeof(long double);
291 
292   msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
293   if (msg == NULL) {
294     pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
295     pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
296   }
297 
298   msg->mtype = dst_pid;
299   memcpy(msg->mtext, &prio, sizeof(unsigned int));
300   memcpy(msg->mtext + sizeof(unsigned int), &downrate, sizeof(long double));
301   memcpy(msg->mtext + sizeof(unsigned int) + sizeof(long double), &uprate,
302     sizeof(long double));
303 
304   /* Remove any old messages in the queue for the destination PID.  This
305    * helps keep the queue clear and moving, more resistant to (inadvertent
306    * or not) DoS situations.
307    */
308   shaper_msg_clear(dst_pid);
309 
310   while (msgsnd(shaper_qid, msg, msgsz, IPC_NOWAIT) < 0) {
311     pr_signals_handle();
312 
313     if (errno != EAGAIN) {
314       free(msg);
315       return -1;
316 
317     } else {
318       /* The EAGAIN error happens when there are too many bytes of messages
319        * on the queue.  Check to see what the current number of messages
320        * on the queue is, and log the error.
321        *
322        * If this error is hit too many times in a loop, we may need to give
323        * up permanently.  (XXX in the future, if one queue is too small for
324        * a busy daemon, look into a different queue allocation strategy.)
325        */
326       struct msqid_ds ds;
327 
328       if (msgctl(shaper_qid, IPC_STAT, &ds) < 0) {
329         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
330           "error checking queue ID %d: %s", shaper_qid, strerror(errno));
331 
332       } else {
333         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
334           "unable to send message to PID %lu via queue ID %d, max bytes (%lu) "
335           "reached (%lu messages of %lu bytes currently in queue)",
336           (unsigned long) dst_pid, shaper_qid, shaper_qmaxbytes,
337           (unsigned long) ds.msg_qnum, (unsigned long) ds.msg_qnum * msgsz);
338       }
339 
340       error_count++;
341       if (error_count > SHAPER_MAX_SEND_ATTEMPTS) {
342         free(msg);
343 
344         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
345           "unable to send message to PID %lu via queue ID %d after %u attempts "
346           "(%u max attempts allowed), failing", (unsigned long) dst_pid,
347           shaper_qid, error_count, SHAPER_MAX_SEND_ATTEMPTS);
348 
349         errno = EPERM;
350         return -1;
351       }
352     }
353 
354   }
355   free(msg);
356 
357   /* Send SIGUSR2 to the destination process, to let it know that it should
358    * check the queue for messages.
359    */
360   PRIVS_ROOT
361   res = kill(dst_pid, SIGUSR2);
362   PRIVS_RELINQUISH
363 
364   if (res < 0) {
365     if (errno == ESRCH) {
366       shaper_msg_clear(dst_pid);
367 
368     } else {
369       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
370         "error sending notice: %s", strerror(errno));
371     }
372   }
373 
374 #if 0
375   /* Handle our own signal, if necessary. */
376   if (getpid() == dst_pid)
377     pr_signals_handle();
378 #endif
379 
380   return 0;
381 }
382 
shaper_msg_clear(pid_t dst_pid)383 static void shaper_msg_clear(pid_t dst_pid) {
384   ssize_t msglen = 0;
385   struct shaper_msg *msg;
386   size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
387     sizeof(long double);
388 
389   msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
390   if (msg == NULL) {
391     pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
392     pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
393   }
394 
395   (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
396     "clearing queue ID %d of messages for process ID %lu", shaper_qid,
397     (unsigned long) dst_pid);
398   msglen = msgrcv(shaper_qid, msg, msgsz, dst_pid, IPC_NOWAIT|MSG_NOERROR);
399   while (msglen > 0) {
400     pr_signals_handle();
401 
402     msglen = msgrcv(shaper_qid, msg, msgsz, dst_pid, IPC_NOWAIT|MSG_NOERROR);
403   }
404 
405   free(msg);
406 }
407 
shaper_remove_config(unsigned int prio)408 static void shaper_remove_config(unsigned int prio) {
409   config_rec *c;
410   register unsigned int i;
411   pool *tmp_pool = make_sub_pool(shaper_pool);
412   array_header *list = make_array(tmp_pool, 1, sizeof(config_rec *));
413 
414   /* This function is very similar to remove_config(), except that we
415    * want to remove only TransferRate config_recs, and only those
416    * config_recs whose priority matches the given priority.
417    *
418    * To do this, first we'll do a recursive scan for all TransferRate
419    * config_recs that match our priority, and add them to a list.
420    * Then we'll loop through the list, removing them from the config
421    * tree.  It can't be done in one loop, as we'll not be able to
422    * track which config_recs we've seen and left alone before.
423    */
424 
425   c = find_config(main_server->conf, CONF_PARAM, "TransferRate", TRUE);
426   while (c) {
427     pr_signals_handle();
428 
429     if (*((unsigned int *) c->argv[3]) == prio)
430       *((config_rec **) push_array(list)) = c;
431 
432     c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", TRUE);
433   }
434 
435   for (i = 0; i < list->nelts; i++) {
436     xaset_t *set;
437 
438     c = ((config_rec **) list->elts)[i];
439     set = c->set;
440 
441     xaset_remove(set, (xasetmember_t *) c);
442 
443     if (!set->xas_list) {
444       if (c->parent && c->parent->subset == set)
445         c->parent->subset = NULL;
446 
447       else if (main_server->conf == set)
448         main_server->conf = NULL;
449 
450       destroy_pool(set->pool);
451 
452     } else {
453       destroy_pool(c->pool);
454     }
455   }
456 
457   destroy_pool(tmp_pool);
458   return;
459 }
460 
shaper_rate_alter(unsigned int prio,long double downrate,long double uprate)461 static int shaper_rate_alter(unsigned int prio, long double downrate,
462     long double uprate) {
463   config_rec *c;
464 
465   /* Remove any TransferRate entries at this same priority. */
466   shaper_remove_config(prio);
467 
468   /* Create separate TransferRate entries for the download and upload
469    * rates, for now.  It would be more efficient to have a single config_rec
470    * entry, but only when the downrate and uprate are the same.
471    */
472 
473   if (downrate > 0.0) {
474     c = add_config_param_set(&main_server->conf, "TransferRate", 4, NULL,
475       NULL, NULL, NULL);
476     c->argv[0] = shaper_down_cmds;
477 
478     c->argv[1] = pcalloc(c->pool, sizeof(long double));
479     *((long double *) c->argv[1]) = downrate;
480 
481     /* No freebytes altered via mod_shaper. */
482     c->argv[2] = pcalloc(c->pool, sizeof(off_t));
483     *((off_t *) c->argv[2]) = 0;
484 
485     c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
486     *((unsigned int *) c->argv[3]) = prio;
487 
488     c->flags |= CF_MERGEDOWN_MULTI;
489   }
490 
491   if (uprate > 0.0) {
492     c = add_config_param_set(&main_server->conf, "TransferRate", 4, NULL,
493       NULL, NULL, NULL);
494     c->argv[0] = shaper_up_cmds;
495 
496     c->argv[1] = pcalloc(c->pool, sizeof(long double));
497     *((long double *) c->argv[1]) = uprate;
498 
499     /* No freebytes altered via mod_shaper. */
500     c->argv[2] = pcalloc(c->pool, sizeof(off_t));
501     *((off_t *) c->argv[2]) = 0;
502 
503     c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
504     *((unsigned int *) c->argv[3]) = prio;
505 
506     c->flags |= CF_MERGEDOWN_MULTI;
507   }
508 
509   (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
510     "merging in new TransferRate entries");
511   fixup_dirs(main_server, CF_SILENT);
512 
513   return 0;
514 }
515 
516 /* Flush the ShaperTable out to disk. */
shaper_table_flush(void)517 static int shaper_table_flush(void) {
518   register unsigned int i;
519   int res;
520   struct iovec tab_iov[6];
521   struct shaper_sess *sess_list;
522 
523   /* Seek to the start of the file. */
524   if (lseek(shaper_tabfd, 0, SEEK_SET) == (off_t) -1) {
525     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
526       "error seeking to start of ShaperTable: %s", strerror(errno));
527     return -1;
528   }
529 
530   /* Write out the ShaperTable header. */
531   tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
532   tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
533 
534   tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
535   tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
536 
537   tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
538   tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
539 
540   tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
541   tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
542 
543   tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
544   tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
545 
546   tab_iov[5].iov_base = SHAPER_IOV_BASE &shaper_tab.nsessions;
547   tab_iov[5].iov_len = sizeof(shaper_tab.nsessions);
548 
549   res = writev(shaper_tabfd, tab_iov, 6);
550   if (res < 0) {
551     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
552       "error writing ShaperTable (%d) header: %s", shaper_tabfd,
553       strerror(errno));
554     return -1;
555   }
556 
557   sess_list = shaper_tab.sess_list->elts;
558   for (i = 0; i < shaper_tab.nsessions; i++) {
559     tab_iov[0].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_pid;
560     tab_iov[0].iov_len = sizeof(pid_t);
561 
562     tab_iov[1].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_prio;
563     tab_iov[1].iov_len = sizeof(unsigned int);
564 
565     tab_iov[2].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_downincr;
566     tab_iov[2].iov_len = sizeof(int);
567 
568     tab_iov[3].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_downrate;
569     tab_iov[3].iov_len = sizeof(long double);
570 
571     tab_iov[4].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_upincr;
572     tab_iov[4].iov_len = sizeof(int);
573 
574     tab_iov[5].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_uprate;
575     tab_iov[5].iov_len = sizeof(long double);
576 
577     res = writev(shaper_tabfd, tab_iov, 6);
578     if (res < 0)
579       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
580         "error writing ShaperTable session entry: %s", strerror(errno));
581   }
582 
583   return 0;
584 }
585 
586 #ifndef HAVE_FLOCK
get_lock_type(struct flock * lock)587 static const char *get_lock_type(struct flock *lock) {
588   const char *lock_type;
589 
590   switch (lock->l_type) {
591     case F_RDLCK:
592       lock_type = "read";
593       break;
594 
595     case F_WRLCK:
596       lock_type = "write";
597       break;
598 
599     case F_UNLCK:
600       lock_type = "unlock";
601       break;
602 
603     default:
604       lock_type = "[unknown]";
605   }
606 
607   return lock_type;
608 }
609 #endif /* !HAVE_FLOCK */
610 
shaper_table_lock(int op)611 static int shaper_table_lock(int op) {
612   static int have_lock = FALSE;
613 
614 #ifndef HAVE_FLOCK
615   int flag;
616   struct flock lock;
617 #endif /* !HAVE_FLOCK */
618 
619   if (have_lock &&
620       ((op & LOCK_SH) || (op & LOCK_EX))) {
621     return 0;
622   }
623 
624   if (!have_lock &&
625       (op & LOCK_UN)) {
626     return 0;
627   }
628 
629 #ifdef HAVE_FLOCK
630   pr_trace_msg("lock", 9, "attempting to %s ShaperTable fd %d via flock(2)",
631     op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
632   while (flock(shaper_tabfd, op) < 0) {
633     int xerrno = errno;
634 
635     if (xerrno == EINTR) {
636       pr_signals_handle();
637       continue;
638     }
639 
640     pr_trace_msg("lock", 9, "%s of ShaperTable fd %d failed: %s",
641       op == LOCK_UN ? "unlock" : "lock", shaper_tabfd, strerror(xerrno));
642 
643     errno = xerrno;
644     return -1;
645   }
646 
647   pr_trace_msg("lock", 9, "%s of ShaperTable fd %d successful",
648     op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
649 
650   if ((op & LOCK_SH) ||
651       (op & LOCK_EX)) {
652     have_lock = TRUE;
653 
654   } else if (op & LOCK_UN) {
655     have_lock = FALSE;
656   }
657 
658   return 0;
659 #else
660   flag = F_SETLKW;
661 
662   lock.l_whence = 0;
663   lock.l_start = lock.l_len = 0;
664 
665   if (op & LOCK_SH) {
666     lock.l_type = F_RDLCK;
667 
668   } else if (op & LOCK_EX) {
669     lock.l_type = F_WRLCK;
670 
671   } else if (op & LOCK_UN) {
672     lock.l_type = F_UNLCK;
673 
674   } else {
675     errno = EINVAL;
676     return -1;
677   }
678 
679   if (op & LOCK_NB)
680     flag = F_SETLK;
681 
682   pr_trace_msg("lock", 9, "attempting to %s ShaperTable fd %d via fcntl(2)",
683     op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
684   while (fcntl(shaper_tabfd, flag, &lock) < 0) {
685     int xerrno = errno;
686 
687     if (xerrno == EINTR) {
688       pr_signals_handle();
689       continue;
690     }
691 
692     pr_trace_msg("lock", 9, "%s of ShaperTable fd %d failed: %s",
693       op == LOCK_UN ? "unlock" : "lock", shaper_tabfd, strerror(xerrno));
694 
695     if (xerrno == EACCES) {
696       /* Get the PID of the process blocking this lock. */
697       if (fcntl(shaper_tabfd, F_GETLK, &lock) == 0) {
698         pr_trace_msg("lock", 3, "process ID %lu has blocking %s lock on "
699           "ShaperTable fd %d", (unsigned long) lock.l_pid, get_lock_type(&lock),
700           shaper_tabfd);
701       }
702     }
703 
704     errno = xerrno;
705     return -1;
706   }
707 
708   pr_trace_msg("lock", 9, "%s of ShaperTable fd %d successful",
709     op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
710 
711   if ((op & LOCK_SH) ||
712       (op & LOCK_EX)) {
713     have_lock = TRUE;
714 
715   } else if (op & LOCK_UN) {
716     have_lock = FALSE;
717   }
718 
719   return 0;
720 #endif /* HAVE_FLOCK */
721 }
722 
shaper_table_init(pr_fh_t * fh)723 static int shaper_table_init(pr_fh_t *fh) {
724   unsigned int nsessions = 0;
725   struct stat st;
726   struct iovec tab_iov[6];
727 
728   if (pr_fsio_fstat(fh, &st) < 0) {
729     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
730       "unable to fstat ShaperTable: %s", strerror(errno));
731     errno = EINVAL;
732     return -1;
733   }
734 
735   shaper_tabfd = fh->fh_fd;
736 
737   /* XXX maybe add a shaper control to clear/re-init the table, in cases
738    * where the format changes?
739    */
740 
741   /* If the table already exists (i.e. size > 0), return. */
742   if (st.st_size > 0) {
743     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
744       "ShaperTable '%s' has size %" PR_LU " bytes, is already initialized",
745       fh->fh_path, (pr_off_t) st.st_size);
746     return 0;
747   }
748 
749   tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
750   tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
751 
752   tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
753   tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
754 
755   tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
756   tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
757 
758   tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
759   tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
760 
761   tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
762   tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
763 
764   tab_iov[5].iov_base = SHAPER_IOV_BASE &nsessions;
765   tab_iov[5].iov_len = sizeof(nsessions);
766 
767   if (lseek(fh->fh_fd, 0, SEEK_SET) < 0) {
768     return -1;
769   }
770 
771   if (writev(fh->fh_fd, tab_iov, 6) < 0) {
772     return -1;
773   }
774 
775   (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
776     "initialized ShaperTable with rate %3.2Lf KB/s (down), %3.2Lf KB/s (up), "
777     "default priority %u, default shares %u down, %u up", shaper_tab.downrate,
778     shaper_tab.uprate, shaper_tab.def_prio, shaper_tab.def_downshares,
779     shaper_tab.def_upshares);
780 
781   return 0;
782 }
783 
784 /* Refresh the in-memory ShaperTable from disk. */
shaper_table_refresh(void)785 static int shaper_table_refresh(void) {
786   register unsigned int i;
787   int res;
788   struct iovec tab_iov[6];
789 
790   if (lseek(shaper_tabfd, 0, SEEK_SET) == (off_t) -1) {
791     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
792       "error seeking to start of ShaperTable: %s", strerror(errno));
793     return -1;
794   }
795 
796   /* Read the ShaperTable header. */
797 
798   tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
799   tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
800 
801   tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
802   tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
803 
804   tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
805   tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
806 
807   tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
808   tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
809 
810   tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
811   tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
812 
813   tab_iov[5].iov_base = SHAPER_IOV_BASE &shaper_tab.nsessions;
814   tab_iov[5].iov_len = sizeof(shaper_tab.nsessions);
815 
816   res = readv(shaper_tabfd, tab_iov, 6);
817   if (res < 0) {
818     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
819       "error reading ShaperTable header: %s", strerror(errno));
820     return -1;
821   }
822 
823   /* For every session, read in its information and add it to the list.
824    * For this, we need a pool for the session list.
825    */
826 
827   if (shaper_tab_pool) {
828     destroy_pool(shaper_tab_pool);
829     shaper_tab_pool = NULL;
830     shaper_tab.sess_list = NULL;
831   }
832 
833   shaper_tab_pool = make_sub_pool(shaper_pool);
834   pr_pool_tag(shaper_tab_pool, MOD_SHAPER_VERSION ": ShaperTable pool");
835 
836   shaper_tab.sess_list = make_array(shaper_tab_pool, 0,
837     sizeof(struct shaper_sess));
838 
839   for (i = 0; i < shaper_tab.nsessions; i++) {
840     struct shaper_sess *sess = push_array(shaper_tab.sess_list);
841 
842     tab_iov[0].iov_base = SHAPER_IOV_BASE &sess->sess_pid;
843     tab_iov[0].iov_len = sizeof(sess->sess_pid);
844 
845     tab_iov[1].iov_base = SHAPER_IOV_BASE &sess->sess_prio;
846     tab_iov[1].iov_len = sizeof(sess->sess_prio);
847 
848     tab_iov[2].iov_base = SHAPER_IOV_BASE &sess->sess_downincr;
849     tab_iov[2].iov_len = sizeof(sess->sess_downincr);
850 
851     tab_iov[3].iov_base = SHAPER_IOV_BASE &sess->sess_downrate;
852     tab_iov[3].iov_len = sizeof(sess->sess_downrate);
853 
854     tab_iov[4].iov_base = SHAPER_IOV_BASE &sess->sess_upincr;
855     tab_iov[4].iov_len = sizeof(sess->sess_upincr);
856 
857     tab_iov[5].iov_base = SHAPER_IOV_BASE &sess->sess_uprate;
858     tab_iov[5].iov_len = sizeof(sess->sess_uprate);
859 
860     res = readv(shaper_tabfd, tab_iov, 6);
861     if (res < 0) {
862       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
863         "error reading session entry %u from ShaperTable: %s", i + 1,
864         strerror(errno));
865       return -1;
866     }
867   }
868 
869   return 0;
870 }
871 
872 /* Scan the ShaperTable for any sessions who might have exited in a Bad Way
873  * and not cleaned up their entries.
874  */
shaper_table_scrub(void)875 static void shaper_table_scrub(void) {
876   register unsigned int i;
877   struct shaper_sess *sess_list;
878   array_header *new_sess_list;
879   int send_tab = FALSE;
880 
881   if (shaper_table_lock(LOCK_EX) < 0)
882     return;
883 
884   if (shaper_table_refresh() < 0) {
885     shaper_table_lock(LOCK_UN);
886     return;
887   }
888 
889   if (shaper_tab.nsessions == 0) {
890     /* No sessions in the ShaperTable to be removed. */
891     shaper_table_lock(LOCK_UN);
892     return;
893   }
894 
895   sess_list = shaper_tab.sess_list->elts;
896   new_sess_list = make_array(shaper_tab_pool, 0, sizeof(struct shaper_sess));
897 
898   for (i = 0; i < shaper_tab.nsessions; i++) {
899 
900     /* Check to see if the PID in this entry is valid.  If not, erase
901      * the slot.
902      */
903     if (kill(sess_list[i].sess_pid, 0) < 0) {
904       if (errno == ESRCH) {
905 
906         /* OK, the recorded PID is no longer valid. */
907         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
908           "removed dead session (pid %u) from ShaperTable",
909           (unsigned int) sess_list[i].sess_pid);
910         send_tab = TRUE;
911       }
912 
913     } else {
914       struct shaper_sess *sess = push_array(new_sess_list);
915 
916       sess->sess_pid = sess_list[i].sess_pid;
917       sess->sess_prio = sess_list[i].sess_prio;
918       sess->sess_downincr = sess_list[i].sess_downincr;
919       sess->sess_downrate = sess_list[i].sess_downrate;
920       sess->sess_upincr = sess_list[i].sess_upincr;
921       sess->sess_uprate = sess_list[i].sess_uprate;
922     }
923   }
924 
925   /* Replace the session list.  The memory pointed to by the overwritten
926    * pointer will be freed when shaper_tab_pool is freed, which will be
927    * when the table is next refreshed.
928    */
929 
930   shaper_tab.nsessions = new_sess_list->nelts;
931   shaper_tab.sess_list = new_sess_list;
932 
933   if (send_tab && shaper_table_send() < 0) {
934     shaper_table_lock(LOCK_UN);
935     return;
936   }
937 
938   if (shaper_table_flush() < 0) {
939     shaper_table_lock(LOCK_UN);
940     return;
941   }
942 
943   shaper_table_lock(LOCK_UN);
944   return;
945 }
946 
shaper_table_scrub_cb(CALLBACK_FRAME)947 static int shaper_table_scrub_cb(CALLBACK_FRAME) {
948   shaper_table_scrub();
949 
950   /* Always return 1, resetting the timer. */
951   return 1;
952 }
953 
954 /* Scan the ShaperTable, sending messages to each session for their new rate
955  * and its priority.
956  */
shaper_table_send(void)957 static int shaper_table_send(void) {
958   register unsigned int i;
959   unsigned int total_downshares = 0, total_upshares = 0;
960   long double rate_per_downshare, rate_per_upshare;
961   struct shaper_sess *sess_list = shaper_tab.sess_list->elts;
962 
963   for (i = 0; i < shaper_tab.nsessions; i++) {
964     total_downshares += (shaper_tab.def_downshares +
965       sess_list[i].sess_downincr);
966     total_upshares += (shaper_tab.def_upshares +
967       sess_list[i].sess_upincr);
968   }
969 
970   if (total_downshares == 0) {
971     total_downshares = 1;
972   }
973 
974   if (total_upshares == 0) {
975     total_upshares = 1;
976   }
977 
978   (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
979     "total session shares: %u down, %u up", total_downshares, total_upshares);
980 
981   rate_per_downshare = shaper_tab.downrate / total_downshares;
982   rate_per_upshare = shaper_tab.uprate / total_upshares;
983 
984   (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
985     "rate per share: %3.2Lf down, %3.2Lf up", rate_per_downshare,
986     rate_per_upshare);
987 
988   for (i = 0; i < shaper_tab.nsessions; i++) {
989     sess_list[i].sess_downrate = rate_per_downshare *
990       (shaper_tab.def_downshares + sess_list[i].sess_downincr);
991     sess_list[i].sess_uprate = rate_per_upshare *
992       (shaper_tab.def_upshares + sess_list[i].sess_upincr);
993 
994     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
995       "pid %u has shares of %u down, %u up, sending rates of %3.2Lf down, "
996       "%3.2Lf up", (unsigned int) sess_list[i].sess_pid,
997       shaper_tab.def_downshares + sess_list[i].sess_downincr,
998       shaper_tab.def_upshares + sess_list[i].sess_upincr,
999       sess_list[i].sess_downrate, sess_list[i].sess_uprate);
1000 
1001     if (shaper_msg_send(sess_list[i].sess_pid, sess_list[i].sess_prio,
1002         sess_list[i].sess_downrate, sess_list[i].sess_uprate) < 0)
1003       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1004         "error sending msg to pid %u: %s",
1005         (unsigned int) sess_list[i].sess_pid, strerror(errno));
1006   }
1007 
1008   return 0;
1009 }
1010 
shaper_table_sess_add(pid_t sess_pid,unsigned int prio,int downincr,int upincr)1011 static int shaper_table_sess_add(pid_t sess_pid, unsigned int prio,
1012     int downincr, int upincr) {
1013   struct shaper_sess *sess;
1014 
1015   if (shaper_table_lock(LOCK_EX) < 0) {
1016     return -1;
1017   }
1018 
1019   if (shaper_table_refresh() < 0) {
1020     int xerrno = errno;
1021 
1022     shaper_table_lock(LOCK_UN);
1023     errno = xerrno;
1024     return -1;
1025   }
1026 
1027   shaper_tab.nsessions++;
1028   sess = push_array(shaper_tab.sess_list);
1029   sess->sess_pid = sess_pid;
1030 
1031   if (prio != (unsigned int) -1) {
1032     sess->sess_prio = prio;
1033 
1034   } else {
1035     sess->sess_prio = shaper_tab.def_prio;
1036   }
1037 
1038   sess->sess_downincr = downincr;
1039   sess->sess_downrate = 0.0;
1040   sess->sess_upincr = upincr;
1041   sess->sess_uprate = 0.0;
1042 
1043   if (shaper_table_send() < 0) {
1044     int xerrno = errno;
1045 
1046     shaper_table_lock(LOCK_UN);
1047     errno = xerrno;
1048     return -1;
1049   }
1050 
1051   if (shaper_table_flush() < 0) {
1052     int xerrno = errno;
1053 
1054     shaper_table_lock(LOCK_UN);
1055     errno = xerrno;
1056     return -1;
1057   }
1058 
1059   shaper_table_lock(LOCK_UN);
1060   return 0;
1061 }
1062 
shaper_table_sess_modify(pid_t sess_pid,unsigned int prio,int downincr,int upincr)1063 static int shaper_table_sess_modify(pid_t sess_pid, unsigned int prio,
1064     int downincr, int upincr) {
1065   register unsigned int i;
1066   int found = FALSE, adj_down_ok = FALSE, adj_up_ok = FALSE;
1067   struct shaper_sess *sess_list;
1068 
1069   if (shaper_table_lock(LOCK_EX) < 0)
1070     return -1;
1071 
1072   if (shaper_table_refresh() < 0) {
1073     int xerrno = errno;
1074 
1075     shaper_table_lock(LOCK_UN);
1076     errno = xerrno;
1077     return -1;
1078   }
1079 
1080   /* XXX for large ShaperTables, this linear scan will increase the time
1081    * needed for adjusting sessions.
1082    */
1083   sess_list = shaper_tab.sess_list->elts;
1084   for (i = 0; i < shaper_tab.nsessions; i++) {
1085     if (sess_list[i].sess_pid != sess_pid)
1086       continue;
1087 
1088     found = TRUE;
1089 
1090     if ((shaper_tab.def_downshares + sess_list[i].sess_downincr +
1091         downincr) >= 1) {
1092       adj_down_ok = TRUE;
1093       sess_list[i].sess_downincr += downincr;
1094     }
1095 
1096     if ((shaper_tab.def_upshares + sess_list[i].sess_upincr +
1097         upincr) >= 1) {
1098       adj_up_ok = TRUE;
1099       sess_list[i].sess_upincr += upincr;
1100     }
1101 
1102     if (prio != (unsigned int) -1)
1103       sess_list[i].sess_prio = prio;
1104 
1105     break;
1106   }
1107 
1108   /* If the session was not found, or if the given adjustments were not OK,
1109    * do not send the changes out, but be done now.
1110    */
1111   if (!found || (!adj_down_ok && !adj_up_ok)) {
1112     shaper_table_lock(LOCK_UN);
1113 
1114     if (!found)
1115       errno = ENOENT;
1116 
1117     else if (!adj_down_ok) {
1118       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1119         "error modifying session: shares increment (%s%d) will drop "
1120         "session downshares (%u) below 1", downincr > 0 ? "+" : "", downincr,
1121         shaper_tab.def_downshares);
1122       errno = EINVAL;
1123 
1124     } else if (!adj_up_ok) {
1125       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1126         "error modifying session: shares increment (%s%d) will drop "
1127         "session upshares (%u) below 1", upincr > 0 ? "+" : "", upincr,
1128         shaper_tab.def_upshares);
1129       errno = EINVAL;
1130     }
1131 
1132     return -1;
1133   }
1134 
1135   if (shaper_table_send() < 0) {
1136     int xerrno = errno;
1137 
1138     shaper_table_lock(LOCK_UN);
1139     errno = xerrno;
1140     return -1;
1141   }
1142 
1143   if (shaper_table_flush() < 0) {
1144     int xerrno = errno;
1145 
1146     shaper_table_lock(LOCK_UN);
1147     errno = xerrno;
1148     return -1;
1149   }
1150 
1151   shaper_table_lock(LOCK_UN);
1152   return 0;
1153 }
1154 
shaper_table_sess_remove(pid_t sess_pid)1155 static int shaper_table_sess_remove(pid_t sess_pid) {
1156   register unsigned int i;
1157   int found = FALSE;
1158   struct shaper_sess *sess_list;
1159   array_header *new_sess_list;
1160 
1161   if (shaper_table_lock(LOCK_EX) < 0)
1162     return -1;
1163 
1164   if (shaper_table_refresh() < 0) {
1165     int xerrno = errno;
1166 
1167     shaper_table_lock(LOCK_UN);
1168     errno = xerrno;
1169     return -1;
1170   }
1171 
1172   if (shaper_tab.nsessions == 0) {
1173     /* No sessions in the ShaperTable to be removed. */
1174     shaper_table_lock(LOCK_UN);
1175     return 0;
1176   }
1177 
1178   sess_list = shaper_tab.sess_list->elts;
1179   new_sess_list = make_array(shaper_tab_pool, 0, sizeof(struct shaper_sess));
1180 
1181   for (i = 0; i < shaper_tab.nsessions; i++) {
1182     if (sess_list[i].sess_pid != sess_pid) {
1183       struct shaper_sess *sess = push_array(new_sess_list);
1184 
1185       sess->sess_pid = sess_list[i].sess_pid;
1186       sess->sess_prio = sess_list[i].sess_prio;
1187       sess->sess_downincr = sess_list[i].sess_downincr;
1188       sess->sess_downrate = sess_list[i].sess_downrate;
1189       sess->sess_upincr = sess_list[i].sess_upincr;
1190       sess->sess_uprate = sess_list[i].sess_uprate;
1191 
1192     } else
1193        found = TRUE;
1194   }
1195 
1196   if (found)
1197     shaper_tab.nsessions--;
1198 
1199   /* Replace the session list.  The memory pointed to by the overwritten
1200    * pointer will be freed when shaper_tab_pool is freed, which will be
1201    * when the table is next refreshed.
1202    */
1203 
1204   shaper_tab.sess_list = new_sess_list;
1205 
1206   if (shaper_table_send() < 0) {
1207     int xerrno = errno;
1208 
1209     shaper_table_lock(LOCK_UN);
1210     errno = xerrno;
1211     return -1;
1212   }
1213 
1214   if (shaper_table_flush() < 0) {
1215     int xerrno = errno;
1216 
1217     shaper_table_lock(LOCK_UN);
1218     errno = xerrno;
1219     return -1;
1220   }
1221 
1222   shaper_table_lock(LOCK_UN);
1223   return 0;
1224 }
1225 
1226 /* Control handlers
1227  */
1228 
1229 /* usage: shaper all priority|rate|downrate|uprate|shares|downshares|upshares
1230  *   val
1231  */
shaper_handle_all(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1232 static int shaper_handle_all(pr_ctrls_t *ctrl, int reqargc,
1233     char **reqargv) {
1234   register int i;
1235   int send_tab = TRUE;
1236 
1237   if (reqargc < 2 ||
1238       reqargc > 14 ||
1239       reqargc % 2 != 0) {
1240     pr_ctrls_add_response(ctrl, "wrong number of parameters");
1241     return -1;
1242   }
1243 
1244   if (shaper_table_lock(LOCK_EX) < 0) {
1245     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1246       "error write-locking ShaperTable: %s", strerror(errno));
1247     pr_ctrls_add_response(ctrl, "error handling request");
1248     return -1;
1249   }
1250 
1251   if (shaper_table_refresh() < 0) {
1252     shaper_table_lock(LOCK_UN);
1253     pr_ctrls_add_response(ctrl, "error handling request");
1254     return -1;
1255   }
1256 
1257   for (i = 0; i < reqargc;) {
1258     if (strcmp(reqargv[i], "downrate") == 0) {
1259       char *tmp;
1260       long double rate;
1261 
1262       rate = strtod(reqargv[i+1], &tmp);
1263 
1264       if (tmp && *tmp) {
1265         pr_ctrls_add_response(ctrl, "invalid downrate value (%s)",
1266           reqargv[i+1]);
1267         send_tab = FALSE;
1268         i += 2;
1269         continue;
1270       }
1271 
1272       if (rate < 0.0) {
1273         pr_ctrls_add_response(ctrl, "downrate must be greater than 0 (%3.2Lf)",
1274           rate);
1275         send_tab = FALSE;
1276         i += 2;
1277         continue;
1278       }
1279 
1280       shaper_tab.downrate = rate;
1281       pr_ctrls_add_response(ctrl, "overall downrate (%3.2Lf) set",
1282         shaper_tab.downrate);
1283 
1284       i += 2;
1285 
1286     } else if (strcmp(reqargv[i], "downshares") == 0) {
1287       int shares = atoi(reqargv[i+1]);
1288 
1289       if (shares < 1) {
1290         pr_ctrls_add_response(ctrl, "downshares (%d) must be greater than 1",
1291           shares);
1292         send_tab = FALSE;
1293         i += 2;
1294         continue;
1295       }
1296 
1297       shaper_tab.def_downshares = shares;
1298       pr_ctrls_add_response(ctrl, "default downshares (%u) set",
1299         shaper_tab.def_downshares);
1300 
1301       i += 2;
1302 
1303     } else if (strcmp(reqargv[i], "priority") == 0) {
1304       int prio = atoi(reqargv[i+1]);
1305 
1306       if (prio < 0) {
1307         pr_ctrls_add_response(ctrl, "priority (%d) must be greater than 0",
1308           prio);
1309         send_tab = FALSE;
1310         i += 2;
1311         continue;
1312       }
1313 
1314       shaper_tab.def_prio = prio;
1315       pr_ctrls_add_response(ctrl, "default priority (%u) set",
1316         shaper_tab.def_prio);
1317 
1318       i += 2;
1319 
1320     } else if (strcmp(reqargv[i], "rate") == 0) {
1321       char *tmp;
1322       long double rate;
1323 
1324       rate = strtod(reqargv[i+1], &tmp);
1325 
1326       if (tmp && *tmp) {
1327         pr_ctrls_add_response(ctrl, "invalid rate value (%s)", reqargv[i+1]);
1328         send_tab = FALSE;
1329         i += 2;
1330         continue;
1331       }
1332 
1333       if (rate < 0.0) {
1334         pr_ctrls_add_response(ctrl, "rate must be greater than 0 (%3.2Lf)",
1335           rate);
1336         send_tab = FALSE;
1337         i += 2;
1338         continue;
1339       }
1340 
1341       shaper_tab.downrate = rate;
1342       shaper_tab.uprate = rate;
1343       pr_ctrls_add_response(ctrl, "overall rates (%3.2Lf down, %3.2Lf up) set",
1344         shaper_tab.downrate, shaper_tab.uprate);
1345 
1346       i += 2;
1347 
1348     } else if (strcmp(reqargv[i], "shares") == 0) {
1349       int shares = atoi(reqargv[i+1]);
1350 
1351       if (shares < 1) {
1352         pr_ctrls_add_response(ctrl, "shares (%d) must be greater than 1",
1353           shares);
1354         send_tab = FALSE;
1355         i += 2;
1356         continue;
1357       }
1358 
1359       shaper_tab.def_downshares = shares;
1360       shaper_tab.def_upshares = shares;
1361       pr_ctrls_add_response(ctrl, "default shares (%u down, %u up) set",
1362         shaper_tab.def_downshares, shaper_tab.def_upshares);
1363 
1364       i += 2;
1365 
1366     } else if (strcmp(reqargv[i], "uprate") == 0) {
1367       char *tmp;
1368       long double rate;
1369 
1370       rate = strtod(reqargv[i+1], &tmp);
1371 
1372       if (tmp && *tmp) {
1373         pr_ctrls_add_response(ctrl, "invalid uprate value (%s)", reqargv[i+1]);
1374         send_tab = FALSE;
1375         i += 2;
1376         continue;
1377       }
1378 
1379       if (rate < 0.0) {
1380         pr_ctrls_add_response(ctrl, "uprate must be greater than 0 (%3.2Lf)",
1381           rate);
1382         send_tab = FALSE;
1383         i += 2;
1384         continue;
1385       }
1386 
1387       shaper_tab.uprate = rate;
1388       pr_ctrls_add_response(ctrl, "overall uprate (%3.2Lf) set",
1389         shaper_tab.uprate);
1390 
1391       i += 2;
1392 
1393     } else if (strcmp(reqargv[i], "upshares") == 0) {
1394       int shares = atoi(reqargv[i+1]);
1395 
1396       if (shares < 1) {
1397         pr_ctrls_add_response(ctrl, "upshares (%d) must be greater than 1",
1398           shares);
1399         send_tab = FALSE;
1400         i += 2;
1401         continue;
1402       }
1403 
1404       shaper_tab.def_upshares = shares;
1405       pr_ctrls_add_response(ctrl, "default upshares (%u) set",
1406         shaper_tab.def_upshares);
1407 
1408       i += 2;
1409 
1410     } else {
1411       pr_ctrls_add_response(ctrl, "unknown shaper all option '%s'",
1412         reqargv[i]);
1413       send_tab = FALSE;
1414       i += 2;
1415       continue;
1416     }
1417   }
1418 
1419   if (!send_tab) {
1420     shaper_table_lock(LOCK_UN);
1421     return -1;
1422   }
1423 
1424   if (shaper_table_send() < 0) {
1425     shaper_table_lock(LOCK_UN);
1426     pr_ctrls_add_response(ctrl, "error handling request");
1427     return -1;
1428   }
1429 
1430   if (shaper_table_flush() < 0) {
1431     shaper_table_lock(LOCK_UN);
1432     pr_ctrls_add_response(ctrl, "error handling request");
1433     return -1;
1434   }
1435 
1436   shaper_table_lock(LOCK_UN);
1437   return 0;
1438 }
1439 
1440 /* usage: shaper info */
shaper_handle_info(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1441 static int shaper_handle_info(pr_ctrls_t *ctrl, int reqargc,
1442     char **reqargv) {
1443   register unsigned int i;
1444   struct shaper_sess *sess_list;
1445   unsigned int total_downshares = 0, total_upshares = 0;
1446   char *downbuf = NULL, *upbuf = NULL;
1447   size_t downbufsz = 14, upbufsz = 14;
1448 
1449   if (shaper_table_lock(LOCK_SH) < 0) {
1450     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1451       "unable to read-lock ShaperTable: %s", strerror(errno));
1452     pr_ctrls_add_response(ctrl, "error handling request");
1453     return -1;
1454   }
1455 
1456   if (shaper_table_refresh() < 0) {
1457     int xerrno = errno;
1458     shaper_table_lock(LOCK_UN);
1459 
1460     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1461       "error refreshing ShaperTable: %s", strerror(xerrno));
1462     pr_ctrls_add_response(ctrl, "error handling request");
1463     return -1;
1464   }
1465 
1466   pr_ctrls_add_response(ctrl, "Overall Rates: %3.2Lf KB/s down, %3.2Lf KB/s up",
1467     shaper_tab.downrate, shaper_tab.uprate);
1468   pr_ctrls_add_response(ctrl, "Default Shares Per Session: %u down, %u up",
1469     shaper_tab.def_downshares, shaper_tab.def_upshares);
1470   pr_ctrls_add_response(ctrl, "Default Priority: %u", shaper_tab.def_prio);
1471   pr_ctrls_add_response(ctrl, "Number of Shaped Sessions: %u",
1472     shaper_tab.nsessions);
1473 
1474   sess_list = shaper_tab.sess_list->elts;
1475 
1476   for (i = 0; i < shaper_tab.nsessions; i++) {
1477     total_downshares += (shaper_tab.def_downshares +
1478       sess_list[i].sess_downincr);
1479     total_upshares += (shaper_tab.def_upshares +
1480       sess_list[i].sess_upincr);
1481   }
1482 
1483   if (shaper_tab.nsessions) {
1484     pr_ctrls_add_response(ctrl, "%-5s %8s %-14s %11s %-14s %11s",
1485       "PID", "Priority", "DShares", "DRate (KB/s)", "UShares", "URate (KB/s)");
1486     pr_ctrls_add_response(ctrl, "----- -------- -------------- ------------ -------------- ------------");
1487     downbuf = palloc(ctrl->ctrls_tmp_pool, downbufsz);
1488     upbuf = palloc(ctrl->ctrls_tmp_pool, upbufsz);
1489   }
1490 
1491   for (i = 0; i < shaper_tab.nsessions; i++) {
1492     memset(downbuf, '\0', downbufsz);
1493     memset(upbuf, '\0', upbufsz);
1494 
1495     pr_snprintf(downbuf, downbufsz, "%u/%u (%s%d)",
1496       shaper_tab.def_downshares + sess_list[i].sess_downincr, total_downshares,
1497       sess_list[i].sess_downincr > 0 ? "+" : "", sess_list[i].sess_downincr);
1498     downbuf[downbufsz-1] = '\0';
1499 
1500     pr_snprintf(upbuf, upbufsz, "%u/%u (%s%d)",
1501       shaper_tab.def_upshares + sess_list[i].sess_upincr, total_upshares,
1502       sess_list[i].sess_upincr > 0 ? "+" : "", sess_list[i].sess_upincr);
1503     upbuf[upbufsz-1] = '\0';
1504 
1505     pr_ctrls_add_response(ctrl, "%5u %8u %14s  %11.2Lf %14s  %11.2Lf",
1506       (unsigned int) sess_list[i].sess_pid, sess_list[i].sess_prio,
1507       downbuf, sess_list[i].sess_downrate, upbuf, sess_list[i].sess_uprate);
1508   }
1509 
1510   shaper_table_lock(LOCK_UN);
1511   return 0;
1512 }
1513 
1514 /* usage: shaper sess class|host|user name [priority prio] [shares incr]
1515  *    [downshares incr] [upshares incr]
1516  */
shaper_handle_sess(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1517 static int shaper_handle_sess(pr_ctrls_t *ctrl, int reqargc,
1518     char **reqargv) {
1519   register int i;
1520   int adjusted = FALSE, send_tab = TRUE;
1521   int prio = -1, downincr = 0, upincr = 0;
1522 
1523   if (reqargc < 4 ||
1524       reqargc > 6 ||
1525       reqargc % 2 != 0) {
1526     pr_ctrls_add_response(ctrl, "wrong number of parameters");
1527     return -1;
1528   }
1529 
1530   for (i = 2; i < reqargc;) {
1531     if (strcmp(reqargv[i], "downshares") == 0) {
1532 
1533       if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1534         pr_ctrls_add_response(ctrl,
1535           "downshares (%s) must start with '+' or '-'", reqargv[i+1]);
1536         return -1;
1537       }
1538 
1539       downincr = atoi(reqargv[i+1]);
1540 
1541       if (downincr == 0) {
1542         pr_ctrls_add_response(ctrl, "downshares cannot be 0");
1543         send_tab = FALSE;
1544         i += 2;
1545         continue;
1546       }
1547 
1548       pr_ctrls_add_response(ctrl, "adjusted session downshares by %s%d",
1549         downincr > 0 ? "+" : "", downincr);
1550 
1551       i += 2;
1552 
1553     } else if (strcmp(reqargv[i], "priority") == 0) {
1554       prio = atoi(reqargv[i+1]);
1555 
1556       if (prio < 0) {
1557         pr_ctrls_add_response(ctrl, "priority (%d) must be greater than 0",
1558           prio);
1559         send_tab = FALSE;
1560         i += 2;
1561         continue;
1562       }
1563 
1564       pr_ctrls_add_response(ctrl, "set session priority to %u", prio);
1565       i += 2;
1566 
1567     } else if (strcmp(reqargv[i], "shares") == 0) {
1568       int incr;
1569 
1570       if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1571         pr_ctrls_add_response(ctrl, "shares (%s) must start with '+' or '-'",
1572           reqargv[i+1]);
1573         return -1;
1574       }
1575 
1576       incr = atoi(reqargv[i+1]);
1577 
1578       if (incr == 0) {
1579         pr_ctrls_add_response(ctrl, "shares cannot be 0");
1580         send_tab = FALSE;
1581         i += 2;
1582         continue;
1583       }
1584 
1585       pr_ctrls_add_response(ctrl,
1586         "adjusted session downshares and upshares by %s%d",
1587         incr > 0 ? "+" : "", incr);
1588 
1589       downincr = upincr = incr;
1590       i += 2;
1591 
1592     } else if (strcmp(reqargv[i], "upshares") == 0) {
1593 
1594       if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1595         pr_ctrls_add_response(ctrl,
1596           "upshares (%s) must start with '+' or '-'", reqargv[i+1]);
1597         return -1;
1598       }
1599 
1600       upincr = atoi(reqargv[i+1]);
1601 
1602       if (upincr == 0) {
1603         pr_ctrls_add_response(ctrl, "upshares cannot be 0");
1604         send_tab = FALSE;
1605         i += 2;
1606         continue;
1607       }
1608 
1609       pr_ctrls_add_response(ctrl, "adjusted session upshares by %s%d",
1610         upincr > 0 ? "+" : "", upincr);
1611 
1612       i += 2;
1613 
1614     } else {
1615       pr_ctrls_add_response(ctrl, "unknown shaper session option '%s'",
1616         reqargv[i]);
1617       send_tab = FALSE;
1618       i += 2;
1619       continue;
1620     }
1621   }
1622 
1623   if (!send_tab)
1624     return -1;
1625 
1626   /* Sessions that are not shaped (i.e. excluded from mod_shaper) cannot be
1627    * adjusted.  If exempted at login time, they cannot later be shaped.
1628    */
1629   /* XXX add ability to add non-shaped session to ShaperTable at
1630    * post-login time?  And/or remove a session from ShaperTable before
1631    * session exit?
1632    */
1633 
1634   if (strcmp(reqargv[0], "user") == 0) {
1635     pr_scoreboard_entry_t *score;
1636     const char *user = reqargv[1];
1637 
1638     if (pr_rewind_scoreboard() < 0)
1639       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1640         "error rewinding scoreboard: %s", strerror(errno));
1641 
1642     while ((score = pr_scoreboard_entry_read()) != NULL) {
1643       pr_signals_handle();
1644 
1645       if (strcmp(score->sce_user, user) == 0) {
1646         if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1647             upincr) < 0) {
1648           (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1649             "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1650             strerror(errno));
1651           pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1652             (unsigned int) score->sce_pid, strerror(errno));
1653 
1654         } else
1655           adjusted = TRUE;
1656       }
1657     }
1658 
1659     pr_restore_scoreboard();
1660 
1661   } else if (strcmp(reqargv[0], "host") == 0) {
1662     pr_scoreboard_entry_t *score;
1663     const char *addr;
1664     const pr_netaddr_t *na;
1665 
1666     na = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, reqargv[1], NULL);
1667     if (na == NULL) {
1668       pr_ctrls_add_response(ctrl, "error resolving '%s': %s", reqargv[1],
1669         strerror(errno));
1670       return -1;
1671     }
1672 
1673     addr = pr_netaddr_get_ipstr(na);
1674 
1675     if (pr_rewind_scoreboard() < 0)
1676       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1677         "error rewinding scoreboard: %s", strerror(errno));
1678 
1679     while ((score = pr_scoreboard_entry_read()) != NULL) {
1680       pr_signals_handle();
1681 
1682       if (strcmp(score->sce_client_addr, addr) == 0) {
1683         if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1684             upincr) < 0) {
1685           (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1686             "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1687             strerror(errno));
1688           pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1689             (unsigned int) score->sce_pid, strerror(errno));
1690 
1691         } else {
1692           adjusted = TRUE;
1693         }
1694       }
1695     }
1696 
1697     pr_restore_scoreboard();
1698 
1699   } else if (strcmp(reqargv[0], "class") == 0) {
1700     pr_scoreboard_entry_t *score;
1701     const char *class = reqargv[1];
1702 
1703     if (pr_rewind_scoreboard() < 0)
1704       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1705         "error rewinding scoreboard: %s", strerror(errno));
1706 
1707     while ((score = pr_scoreboard_entry_read()) != NULL) {
1708       pr_signals_handle();
1709 
1710       if (strcmp(score->sce_class, class) == 0) {
1711         if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1712             upincr) < 0) {
1713           (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1714             "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1715             strerror(errno));
1716           pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1717             (unsigned int) score->sce_pid, strerror(errno));
1718 
1719         } else {
1720           adjusted = TRUE;
1721         }
1722       }
1723     }
1724 
1725     pr_restore_scoreboard();
1726 
1727   } else {
1728     pr_ctrls_add_response(ctrl, "unknown shaper session target type: '%s'",
1729       reqargv[0]);
1730     return -1;
1731   }
1732 
1733   if (adjusted) {
1734     pr_ctrls_add_response(ctrl, "sessions adjusted");
1735   }
1736 
1737   return 0;
1738 }
1739 
shaper_handle_shaper(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1740 static int shaper_handle_shaper(pr_ctrls_t *ctrl, int reqargc,
1741     char **reqargv) {
1742 
1743   /* Sanity check */
1744   if (reqargc == 0 || reqargv == NULL) {
1745     pr_ctrls_add_response(ctrl, "shaper: missing required parameters");
1746     return -1;
1747   }
1748 
1749   if (strcmp(reqargv[0], "all") == 0) {
1750 
1751     /* Check the all ACL */
1752     if (!ctrls_check_acl(ctrl, shaper_acttab, "all")) {
1753 
1754       /* Access denied */
1755       pr_ctrls_add_response(ctrl, "access denied");
1756       return -1;
1757     }
1758 
1759     return shaper_handle_all(ctrl, --reqargc, ++reqargv);
1760 
1761   } else if (strcmp(reqargv[0], "info") == 0) {
1762 
1763     /* Check the info ACL */
1764     if (!ctrls_check_acl(ctrl, shaper_acttab, "info")) {
1765 
1766       /* Access denied */
1767       pr_ctrls_add_response(ctrl, "access denied");
1768       return -1;
1769     }
1770 
1771     return shaper_handle_info(ctrl, --reqargc, ++reqargv);
1772 
1773   } else if (strcmp(reqargv[0], "sess") == 0) {
1774 
1775     /* Check the sess ACL */
1776     if (!ctrls_check_acl(ctrl, shaper_acttab, "sess")) {
1777 
1778       /* Access denied */
1779       pr_ctrls_add_response(ctrl, "access denied");
1780       return -1;
1781     }
1782 
1783     return shaper_handle_sess(ctrl, --reqargc, ++reqargv);
1784   }
1785 
1786   pr_ctrls_add_response(ctrl, "unknown shaper action: '%s'", reqargv[0]);
1787   return -1;
1788 }
1789 
1790 /* Configuration handlers
1791  */
1792 
1793 /* usage: ShaperAll [priority prio] [rate rate] [downrate rate] [uprate rate]
1794  *   [shares nshares] [downshares nshares] [upshares nshares]
1795  */
set_shaperall(cmd_rec * cmd)1796 MODRET set_shaperall(cmd_rec *cmd) {
1797   register unsigned int i;
1798 
1799   if (cmd->argc-1 < 2 || cmd->argc-1 > 14 || (cmd->argc-1) % 2 != 0)
1800     CONF_ERROR(cmd, "wrong number of parameters");
1801 
1802   CHECK_CONF(cmd, CONF_ROOT);
1803 
1804   for (i = 1; i < cmd->argc;) {
1805     if (strcmp(cmd->argv[i], "downrate") == 0) {
1806       char *tmp;
1807       long double rate;
1808 
1809       rate = strtod(cmd->argv[i+1], &tmp);
1810 
1811       if (tmp && *tmp)
1812         CONF_ERROR(cmd, "invalid downrate parameter");
1813 
1814       if (rate < 0.0)
1815         CONF_ERROR(cmd, "downrate must be greater than 0");
1816 
1817       shaper_tab.downrate = rate;
1818       i += 2;
1819 
1820     } else if (strcmp(cmd->argv[i], "downshares") == 0) {
1821       int shares = atoi(cmd->argv[i+1]);
1822 
1823       if (shares < 1)
1824         CONF_ERROR(cmd, "downshares must be greater than 1");
1825 
1826       shaper_tab.def_downshares = shares;
1827       i += 2;
1828 
1829     } else if (strcmp(cmd->argv[i], "priority") == 0) {
1830       int prio = atoi(cmd->argv[i+1]);
1831 
1832       if (prio < 0)
1833         CONF_ERROR(cmd, "priority must be greater than 0");
1834 
1835       shaper_tab.def_prio = prio;
1836       i += 2;
1837 
1838     } else if (strcmp(cmd->argv[i], "rate") == 0) {
1839       char *tmp;
1840       long double rate;
1841 
1842       rate = strtod(cmd->argv[i+1], &tmp);
1843 
1844       if (tmp && *tmp)
1845         CONF_ERROR(cmd, "invalid rate parameter");
1846 
1847       if (rate < 0.0)
1848         CONF_ERROR(cmd, "rate must be greater than 0");
1849 
1850       shaper_tab.downrate = rate;
1851       shaper_tab.uprate = rate;
1852       i += 2;
1853 
1854     } else if (strcmp(cmd->argv[i], "shares") == 0) {
1855       int shares = atoi(cmd->argv[i+1]);
1856 
1857       if (shares < 1)
1858         CONF_ERROR(cmd, "shares must be greater than 1");
1859 
1860       shaper_tab.def_downshares = shares;
1861       shaper_tab.def_upshares = shares;
1862       i += 2;
1863 
1864     } else if (strcmp(cmd->argv[i], "uprate") == 0) {
1865       char *tmp;
1866       long double rate;
1867 
1868       rate = strtod(cmd->argv[i+1], &tmp);
1869 
1870       if (tmp && *tmp)
1871         CONF_ERROR(cmd, "invalid uprate parameter");
1872 
1873       if (rate < 0.0)
1874         CONF_ERROR(cmd, "uprate must be greater than 0");
1875 
1876       shaper_tab.uprate = rate;
1877       i += 2;
1878 
1879     } else if (strcmp(cmd->argv[i], "upshares") == 0) {
1880       int shares = atoi(cmd->argv[i+1]);
1881 
1882       if (shares < 1)
1883         CONF_ERROR(cmd, "upshares must be greater than 1");
1884 
1885       shaper_tab.def_upshares = shares;
1886       i += 2;
1887 
1888     } else
1889       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown option: '",
1890         cmd->argv[i], "'", NULL));
1891   }
1892 
1893   return PR_HANDLED(cmd);
1894 }
1895 
1896 /* usage: ShaperControlsACLs actions|all allow|deny user|group list */
set_shaperctrlsacls(cmd_rec * cmd)1897 MODRET set_shaperctrlsacls(cmd_rec *cmd) {
1898   char *bad_action = NULL, **actions = NULL;
1899 
1900   CHECK_ARGS(cmd, 4);
1901   CHECK_CONF(cmd, CONF_ROOT);
1902 
1903   actions = ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]);
1904 
1905   /* Check the second parameter to make sure it is "allow" or "deny" */
1906   if (strcmp(cmd->argv[2], "allow") != 0 &&
1907       strcmp(cmd->argv[2], "deny") != 0)
1908     CONF_ERROR(cmd, "second parameter must be 'allow' or 'deny'");
1909 
1910   /* Check the third parameter to make sure it is "user" or "group" */
1911   if (strcmp(cmd->argv[3], "user") != 0 &&
1912       strcmp(cmd->argv[3], "group") != 0)
1913     CONF_ERROR(cmd, "third parameter must be 'user' or 'group'");
1914 
1915   bad_action = ctrls_set_module_acls(shaper_acttab, shaper_pool, actions,
1916     cmd->argv[2], cmd->argv[3], cmd->argv[4]);
1917   if (bad_action != NULL)
1918     CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown shaper action: '",
1919       bad_action, "'", NULL));
1920 
1921   return PR_HANDLED(cmd);
1922 }
1923 
1924 /* usage: ShaperEngine on|off */
set_shaperengine(cmd_rec * cmd)1925 MODRET set_shaperengine(cmd_rec *cmd) {
1926   int bool;
1927   config_rec *c;
1928 
1929   CHECK_ARGS(cmd, 1);
1930   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1931 
1932   bool = get_boolean(cmd, 1);
1933   if (bool == -1)
1934     CONF_ERROR(cmd, "expected Boolean parameter");
1935 
1936   c = add_config_param(cmd->argv[0], 1, NULL);
1937   c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
1938   *((unsigned int *) c->argv[0]) = bool;
1939   c->flags |= CF_MERGEDOWN;
1940 
1941   return PR_HANDLED(cmd);
1942 }
1943 
1944 /* usage: ShaperLog path|"none" */
set_shaperlog(cmd_rec * cmd)1945 MODRET set_shaperlog(cmd_rec *cmd) {
1946   CHECK_ARGS(cmd, 1);
1947   CHECK_CONF(cmd, CONF_ROOT);
1948 
1949   if (strcasecmp(cmd->argv[0], "none") != 0 &&
1950       pr_fs_valid_path(cmd->argv[1]) < 0) {
1951     CONF_ERROR(cmd, "must be an absolute path");
1952   }
1953 
1954   shaper_log_path = pstrdup(shaper_pool, cmd->argv[1]);
1955   return PR_HANDLED(cmd);
1956 }
1957 
1958 /* usage: ShaperSession [priority prio] [shares num] [downshares num]
1959  *   [upshares num]
1960  */
set_shapersession(cmd_rec * cmd)1961 MODRET set_shapersession(cmd_rec *cmd) {
1962   int prio = -1;
1963   int downshares = 0, upshares = 0;
1964   config_rec *c;
1965 
1966   register unsigned int i;
1967 
1968   if (cmd->argc-1 < 2 ||
1969       cmd->argc-1 > 8 ||
1970       (cmd->argc-1) % 2 != 0) {
1971     CONF_ERROR(cmd, "wrong number of parameters");
1972   }
1973 
1974   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1975 
1976   for (i = 1; i < cmd->argc;) {
1977     if (strcmp(cmd->argv[i], "downshares") == 0) {
1978       char *shareno;
1979 
1980       shareno = cmd->argv[i+1];
1981       if (*shareno != '+' &&
1982           *shareno != '-') {
1983         CONF_ERROR(cmd, "downshares parameter must start with '+' or '-'");
1984       }
1985 
1986       downshares = atoi(shareno);
1987       i += 2;
1988 
1989     } else if (strcmp(cmd->argv[i], "priority") == 0) {
1990       prio = atoi(cmd->argv[i+1]);
1991       if (prio < 0) {
1992         CONF_ERROR(cmd, "priority must be greater than 0");
1993       }
1994 
1995       i += 2;
1996 
1997     } else if (strcmp(cmd->argv[i], "shares") == 0) {
1998       char *shareno;
1999 
2000       shareno = cmd->argv[i+1];
2001       if (*shareno != '+' &&
2002           *shareno != '-') {
2003         CONF_ERROR(cmd, "shares parameter must start with '+' or '-'");
2004       }
2005 
2006       downshares = upshares = atoi(shareno);
2007       i += 2;
2008 
2009     } else if (strcmp(cmd->argv[i], "upshares") == 0) {
2010       char *shareno;
2011 
2012       shareno = cmd->argv[i+1];
2013       if (*shareno != '+' &&
2014           *shareno != '-') {
2015         CONF_ERROR(cmd, "upshares parameter must start with '+' or '-'");
2016       }
2017 
2018       upshares = atoi(shareno);
2019       i += 2;
2020 
2021     } else {
2022       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown option: '",
2023         (char *) cmd->argv[i], "'", NULL));
2024     }
2025   }
2026 
2027   c = add_config_param(cmd->argv[0], 3, NULL, NULL);
2028   c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
2029   *((unsigned int *) c->argv[0]) = (unsigned int) prio;
2030   c->argv[1] = pcalloc(c->pool, sizeof(int));
2031   *((int *) c->argv[1]) = downshares;
2032   c->argv[2] = pcalloc(c->pool, sizeof(int));
2033   *((int *) c->argv[2]) = upshares;
2034   c->flags |= CF_MERGEDOWN;
2035 
2036   return PR_HANDLED(cmd);
2037 }
2038 
2039 /* usage: ShaperTable path */
set_shapertable(cmd_rec * cmd)2040 MODRET set_shapertable(cmd_rec *cmd) {
2041   CHECK_ARGS(cmd, 1);
2042   CHECK_CONF(cmd, CONF_ROOT);
2043 
2044   if (pr_fs_valid_path(cmd->argv[1]) < 0) {
2045     CONF_ERROR(cmd, "must be an absolute path");
2046   }
2047 
2048   shaper_tab_path = pstrdup(shaper_pool, cmd->argv[1]);
2049   return PR_HANDLED(cmd);
2050 }
2051 
2052 /* Command handlers
2053  */
2054 
shaper_pre_pass(cmd_rec * cmd)2055 MODRET shaper_pre_pass(cmd_rec *cmd) {
2056 
2057   /* Make sure this session process has the ShaperTable open.
2058    *
2059    * NOTE: I'm not sure this is needed anymore, since an fd to the
2060    * ShaperTable is opened during the 'core.postparse' event handler,
2061    * in the daemon process.
2062    */
2063 
2064   PRIVS_ROOT
2065   shaper_tabfd = open(shaper_tab_path, O_RDWR);
2066   PRIVS_RELINQUISH
2067 
2068   if (shaper_tabfd < 0)
2069     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2070       "unable to open ShaperTable: %s", strerror(errno));
2071 
2072   return PR_DECLINED(cmd);
2073 }
2074 
shaper_post_pass(cmd_rec * cmd)2075 MODRET shaper_post_pass(cmd_rec *cmd) {
2076   config_rec *c;
2077   int downincr = 0, upincr = 0;
2078   unsigned int prio = -1;
2079 
2080   c = find_config(TOPLEVEL_CONF, CONF_PARAM, "ShaperEngine", FALSE);
2081   if (c != NULL &&
2082       *((unsigned char *) c->argv[0]) == TRUE) {
2083     shaper_engine = TRUE;
2084 
2085   } else {
2086     /* Don't need the ShaperTable open anymore. */
2087     close(shaper_tabfd);
2088     shaper_tabfd = -1;
2089 
2090     return PR_DECLINED(cmd);
2091   }
2092 
2093   if (!shaper_tab_path) {
2094     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2095       "ShaperTable not configured, disabling ShaperEngine");
2096     shaper_engine = FALSE;
2097     return PR_DECLINED(cmd);
2098   }
2099 
2100   if (shaper_tabfd < 0) {
2101     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2102       "ShaperTable not open, disabling ShaperEngine");
2103     shaper_engine = FALSE;
2104     return PR_DECLINED(cmd);
2105   }
2106 
2107   if (shaper_tab.downrate < 0.0 || shaper_tab.uprate < 0.0) {
2108     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2109       "overall rates negative or not configured, disabling ShaperEngine");
2110     shaper_engine = FALSE;
2111     return PR_DECLINED(cmd);
2112   }
2113 
2114   pr_event_register(&shaper_module, "core.exit", shaper_sess_exit_ev, NULL);
2115   pr_event_register(&shaper_module, "core.signal.USR2", shaper_sigusr2_ev,
2116     NULL);
2117 
2118   c = find_config(TOPLEVEL_CONF, CONF_PARAM, "ShaperSession", FALSE);
2119   if (c) {
2120     prio = *((unsigned int *) c->argv[0]);
2121     downincr = *((int *) c->argv[1]);
2122     upincr = *((int *) c->argv[2]);
2123   }
2124 
2125   /* Update the ShaperTable, adding a new entry for the current session. */
2126   if (shaper_table_sess_add(getpid(), prio, downincr, upincr) < 0) {
2127     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2128       "error adding session to ShaperTable: %s", strerror(errno));
2129   }
2130 
2131   return PR_DECLINED(cmd);
2132 }
2133 
shaper_post_err_pass(cmd_rec * cmd)2134 MODRET shaper_post_err_pass(cmd_rec *cmd) {
2135 
2136   /* Close the ShaperTable if we failed to authenticate. */
2137   close(shaper_tabfd);
2138   shaper_tabfd = -1;
2139 
2140   return PR_DECLINED(cmd);
2141 }
2142 
2143 /* Event handlers
2144  */
2145 
shaper_shutdown_ev(const void * event_data,void * user_data)2146 static void shaper_shutdown_ev(const void *event_data, void *user_data) {
2147 
2148   /* Remove the queue from the system, and delete the ShaperTable.  We can
2149    * only do this reliably when the standalone daemon process exits; if it's
2150    * an inetd process, there may be other proftpd processes still running.
2151    */
2152   if (getpid() == mpid &&
2153       ServerType == SERVER_STANDALONE) {
2154 
2155     if (shaper_qid >= 0) {
2156       shaper_remove_queue();
2157     }
2158 
2159     if (shaper_tab_path) {
2160       if (pr_fsio_unlink(shaper_tab_path) < 0) {
2161         pr_log_debug(DEBUG9, MOD_SHAPER_VERSION
2162           ": error unlinking '%s': %s", shaper_tab_path, strerror(errno));
2163       }
2164     }
2165   }
2166 
2167   return;
2168 }
2169 
shaper_sess_exit_ev(const void * event_data,void * user_data)2170 static void shaper_sess_exit_ev(const void *event_data, void *user_data) {
2171 
2172   /* Remove this session from the ShaperTable. */
2173   if (shaper_table_sess_remove(getpid()) < 0) {
2174     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2175       "error removing session from ShaperTable: %s", strerror(errno));
2176   }
2177 
2178   /* Clear any messages for this session from the queue as well. */
2179   shaper_msg_clear(getpid());
2180 
2181   return;
2182 }
2183 
2184 #if defined(PR_SHARED_MODULE)
shaper_mod_unload_ev(const void * event_data,void * user_data)2185 static void shaper_mod_unload_ev(const void *event_data, void *user_data) {
2186   if (strcmp("mod_shaper.c", (const char *) event_data) == 0) {
2187     /* Unregister ourselves from all events. */
2188     pr_event_unregister(&shaper_module, NULL, NULL);
2189 
2190     /* Unregister all control actions. */
2191     (void) pr_ctrls_unregister(&shaper_module, "shaper");
2192 
2193     if (shaper_scrub_timer_id != -1) {
2194       (void) pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2195       shaper_scrub_timer_id = -1;
2196     }
2197 
2198     if (shaper_pool) {
2199       destroy_pool(shaper_pool);
2200       shaper_pool = NULL;
2201       shaper_tab_pool = NULL;
2202       shaper_tab.sess_list = NULL;
2203     }
2204   }
2205 }
2206 #endif /* PR_SHARED_MODULE */
2207 
shaper_postparse_ev(const void * event_data,void * user_data)2208 static void shaper_postparse_ev(const void *event_data, void *user_data) {
2209   if (shaper_log_path &&
2210       strcasecmp(shaper_log_path, "none") != 0 &&
2211       pr_log_openfile(shaper_log_path, &shaper_logfd, 0660) < 0) {
2212     pr_log_debug(DEBUG2, MOD_SHAPER_VERSION
2213       ": error opening ShaperLog '%s': %s", shaper_log_path, strerror(errno));
2214     shaper_logfd = -1;
2215   }
2216 
2217   if (shaper_tab_path) {
2218     pr_fh_t *fh;
2219     int xerrno;
2220     struct stat st;
2221 
2222     PRIVS_ROOT
2223     fh = pr_fsio_open(shaper_tab_path, O_RDWR|O_CREAT);
2224     xerrno = errno;
2225     PRIVS_RELINQUISH
2226 
2227     if (fh == NULL) {
2228       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2229         "error opening ShaperTable '%s': %s", shaper_tab_path,
2230         strerror(xerrno));
2231       pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2232         ": error opening ShaperTable '%s': %s", shaper_tab_path,
2233         strerror(xerrno));
2234       pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2235         NULL);
2236     }
2237 
2238     if (pr_fsio_fstat(fh, &st) < 0) {
2239       xerrno = errno;
2240 
2241       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2242         "error checking ShaperTable '%s': %s", shaper_tab_path,
2243         strerror(xerrno));
2244       pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2245         ": error checking ShaperTable '%s': %s", shaper_tab_path,
2246         strerror(xerrno));
2247 
2248       pr_fsio_close(fh);
2249       pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2250         NULL);
2251     }
2252 
2253     if (S_ISDIR(st.st_mode)) {
2254       xerrno = EISDIR;
2255 
2256       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2257         "error using ShaperTable '%s': %s", shaper_tab_path,
2258         strerror(xerrno));
2259       pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2260         ": error using ShaperTable '%s': %s", shaper_tab_path,
2261         strerror(xerrno));
2262 
2263       pr_fsio_close(fh);
2264       pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2265         NULL);
2266     }
2267 
2268     /* Initialize ShaperTable */
2269     if (shaper_table_init(fh) < 0)
2270       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2271         "error initializing ShaperTable: %s", strerror(errno));
2272 
2273     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2274       "determining queue ID for path '%s'", shaper_tab_path);
2275 
2276     shaper_qid = shaper_get_queue(shaper_tab_path);
2277     if (shaper_qid < 0) {
2278       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2279         "error obtaining queue ID: %s", strerror(errno));
2280 
2281     } else {
2282       struct msqid_ds ds;
2283 
2284       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2285         "obtained queue ID %d", shaper_qid);
2286 
2287       if (msgctl(shaper_qid, IPC_STAT, &ds) < 0) {
2288         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2289           "error checking queue ID %d: %s", shaper_qid, strerror(errno));
2290 
2291       } else {
2292         shaper_qmaxbytes = ds.msg_qbytes;
2293       }
2294 
2295       /* We could be being restarted, in which case we want to send our
2296        * reinitialized table to existing sessions.
2297        */
2298 
2299       if (shaper_table_lock(LOCK_EX) < 0) {
2300         (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2301           "error locking ShaperTable: %s", strerror(errno));
2302         return;
2303       }
2304 
2305       if (shaper_table_refresh() < 0) {
2306         shaper_table_lock(LOCK_UN);
2307         return;
2308       }
2309 
2310       if (shaper_table_send() < 0) {
2311         shaper_table_lock(LOCK_UN);
2312         return;
2313       }
2314 
2315       if (shaper_table_flush() < 0) {
2316         shaper_table_lock(LOCK_UN);
2317         return;
2318       }
2319 
2320       shaper_table_lock(LOCK_UN);
2321     }
2322 
2323     if (shaper_scrub_timer_id == -1) {
2324       shaper_scrub_timer_id = pr_timer_add(SHAPER_SCRUB_INTERVAL, -1,
2325         &shaper_module, shaper_table_scrub_cb, "shaper table scrubber");
2326     }
2327 
2328   } else {
2329     (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2330       "no ShaperTable configured");
2331   }
2332 
2333 }
2334 
shaper_restart_ev(const void * event_data,void * user_data)2335 static void shaper_restart_ev(const void *event_data, void *user_data) {
2336   register unsigned int i;
2337 
2338   (void) close(shaper_logfd);
2339   shaper_logfd = -1;
2340   shaper_log_path = NULL;
2341 
2342   if (shaper_pool) {
2343     destroy_pool(shaper_pool);
2344 
2345     /* Make sure to mark subpools as invalid now as well. */
2346     shaper_tab_pool = NULL;
2347     shaper_tab.sess_list = NULL;
2348   }
2349 
2350   shaper_pool = make_sub_pool(permanent_pool);
2351   pr_pool_tag(shaper_pool, MOD_SHAPER_VERSION);
2352 
2353   for (i = 0; shaper_acttab[i].act_action; i++) {
2354     shaper_acttab[i].act_acl = pcalloc(shaper_pool, sizeof(ctrls_acl_t));
2355     ctrls_init_acl(shaper_acttab[i].act_acl);
2356   }
2357 
2358   if (shaper_scrub_timer_id != -1) {
2359     (void) pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2360     shaper_scrub_timer_id = -1;
2361   }
2362 
2363   return;
2364 }
2365 
shaper_sigusr2_ev(const void * event_data,void * user_data)2366 static void shaper_sigusr2_ev(const void *event_data, void *user_data) {
2367   int res;
2368 
2369   /* Check the queue for any messages for us. */
2370   res = shaper_msg_recv();
2371 
2372   switch (res) {
2373     case -1:
2374       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2375         "error receiving updates for pid %lu: %s", (unsigned long) getpid(),
2376         strerror(errno));
2377       break;
2378 
2379     case 0:
2380       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2381         "received signal, no updates for pid %lu", (unsigned long) getpid());
2382       break;
2383 
2384     default:
2385       (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2386         "received signal, read in %d %s for pid %lu", res,
2387         res == 1 ? "update" : "updates", (unsigned long) getpid());
2388   }
2389 
2390   return;
2391 }
2392 
2393 /* Initialization functions
2394  */
2395 
shaper_init(void)2396 static int shaper_init(void) {
2397 
2398   shaper_pool = make_sub_pool(permanent_pool);
2399   pr_pool_tag(shaper_pool, MOD_SHAPER_VERSION);
2400 
2401   shaper_tab.def_prio = SHAPER_DEFAULT_PRIO;
2402   shaper_tab.downrate = SHAPER_DEFAULT_RATE;
2403   shaper_tab.def_downshares = SHAPER_DEFAULT_DOWNSHARES;
2404   shaper_tab.uprate = SHAPER_DEFAULT_RATE;
2405   shaper_tab.def_upshares = SHAPER_DEFAULT_UPSHARES;
2406   shaper_tab.nsessions = 0;
2407 
2408   if (pr_ctrls_register(&shaper_module, "shaper", "tune mod_shaper settings",
2409       shaper_handle_shaper) < 0) {
2410     pr_log_pri(PR_LOG_NOTICE, MOD_SHAPER_VERSION
2411       ": error registering 'shaper' control: %s", strerror(errno));
2412 
2413   } else {
2414     register unsigned int i;
2415 
2416     for (i = 0; shaper_acttab[i].act_action; i++) {
2417       shaper_acttab[i].act_acl = pcalloc(shaper_pool, sizeof(ctrls_acl_t));
2418       ctrls_init_acl(shaper_acttab[i].act_acl);
2419     }
2420   }
2421 
2422 #if defined(PR_SHARED_MODULE)
2423   pr_event_register(&shaper_module, "core.module-unload", shaper_mod_unload_ev,
2424     NULL);
2425 #endif /* PR_SHARED_MODULE */
2426   pr_event_register(&shaper_module, "core.postparse", shaper_postparse_ev,
2427     NULL);
2428   pr_event_register(&shaper_module, "core.restart", shaper_restart_ev, NULL);
2429   pr_event_register(&shaper_module, "core.shutdown", shaper_shutdown_ev, NULL);
2430 
2431   return 0;
2432 }
2433 
shaper_sess_init(void)2434 static int shaper_sess_init(void) {
2435 
2436   /* The ShaperTable scrubbing timer should only run in the daemon. */
2437   pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2438 
2439   return 0;
2440 }
2441 
2442 /* Module API tables
2443  */
2444 
2445 static ctrls_acttab_t shaper_acttab[] = {
2446   { "all",	NULL, NULL, NULL },
2447   { "info",	NULL, NULL, NULL },
2448   { "sess",	NULL, NULL, NULL },
2449   { NULL, NULL, NULL, NULL }
2450 };
2451 
2452 static conftable shaper_conftab[] = {
2453   { "ShaperAll",		set_shaperall,		NULL },
2454   { "ShaperControlsACLs",	set_shaperctrlsacls,	NULL },
2455   { "ShaperEngine",		set_shaperengine,	NULL },
2456   { "ShaperLog",		set_shaperlog,		NULL },
2457   { "ShaperSession",		set_shapersession,	NULL },
2458   { "ShaperTable",		set_shapertable,	NULL },
2459   { NULL }
2460 };
2461 
2462 static cmdtable shaper_cmdtab[] = {
2463   { PRE_CMD,		C_PASS, G_NONE, shaper_pre_pass,	FALSE, FALSE },
2464   { POST_CMD,		C_PASS, G_NONE, shaper_post_pass,	FALSE, FALSE },
2465   { POST_CMD_ERR,	C_PASS, G_NONE, shaper_post_err_pass,	FALSE, FALSE },
2466   { 0, NULL }
2467 };
2468 
2469 module shaper_module = {
2470   NULL, NULL,
2471 
2472   /* Module API version 2.0 */
2473   0x20,
2474 
2475   /* Module name */
2476   "shaper",
2477 
2478   /* Module configuration handler table */
2479   shaper_conftab,
2480 
2481   /* Module command handler table */
2482   shaper_cmdtab,
2483 
2484   /* Module authentication handler table */
2485   NULL,
2486 
2487   /* Module initialization function */
2488   shaper_init,
2489 
2490   /* Session initialization function */
2491   shaper_sess_init,
2492 
2493   /* Module version */
2494   MOD_SHAPER_VERSION
2495 };
2496