1 /*
2  * ProFTPD: mod_quotatab_sql -- a mod_quotatab sub-module for managing quota
3  *                              data via SQL-based tables
4  * Copyright (c) 2002-2020 TJ Saunders
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19  *
20  * As a special exemption, TJ Saunders gives permission to link this program
21  * with OpenSSL, and distribute the resulting executable, without including
22  * the source code for OpenSSL in the source distribution.
23  */
24 
25 #include "mod_quotatab.h"
26 #include "mod_sql.h"
27 
28 #define QUOTATAB_SQL_VALUE_BUFSZ	20
29 
30 module quotatab_sql_module;
31 
sqltab_cmd_create(pool * parent_pool,unsigned int argc,...)32 static cmd_rec *sqltab_cmd_create(pool *parent_pool, unsigned int argc, ...) {
33   register unsigned int i = 0;
34   pool *cmd_pool = NULL;
35   cmd_rec *cmd = NULL;
36   va_list argp;
37 
38   cmd_pool = make_sub_pool(parent_pool);
39   cmd = (cmd_rec *) pcalloc(cmd_pool, sizeof(cmd_rec));
40   cmd->pool = cmd_pool;
41 
42   cmd->argc = argc;
43   cmd->argv = pcalloc(cmd->pool, argc * sizeof(void *));
44 
45   /* Hmmm... */
46   cmd->tmp_pool = cmd->pool;
47 
48   va_start(argp, argc);
49   for (i = 0; i < argc; i++) {
50     cmd->argv[i] = va_arg(argp, char *);
51   }
52   va_end(argp);
53 
54   return cmd;
55 }
56 
sqltab_get_name(pool * p,char * name)57 static char *sqltab_get_name(pool *p, char *name) {
58   cmdtable *cmdtab;
59   cmd_rec *cmd;
60   modret_t *res;
61 
62   /* Find the cmdtable for the sql_escapestr command. */
63   cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_escapestr", NULL, NULL, NULL);
64   if (cmdtab == NULL) {
65     quotatab_log("error: unable to find SQL hook symbol 'sql_escapestr'");
66     return name;
67   }
68 
69   if (strlen(name) == 0) {
70     return name;
71   }
72 
73   cmd = sqltab_cmd_create(p, 1, pr_str_strip(p, name));
74 
75   /* Call the handler. */
76   res = pr_module_call(cmdtab->m, cmdtab->handler, cmd);
77 
78   /* Check the results. */
79   if (MODRET_ISDECLINED(res) ||
80       MODRET_ISERROR(res)) {
81     quotatab_log("error executing 'sql_escapestring'");
82     return name;
83   }
84 
85   return res->data;
86 }
87 
sqltab_close(quota_table_t * sqltab)88 static int sqltab_close(quota_table_t *sqltab) {
89 
90   /* Is there really anything that needs to be done here? */
91   return 0;
92 }
93 
sqltab_create(quota_table_t * sqltab,void * ptr)94 static int sqltab_create(quota_table_t *sqltab, void *ptr) {
95   pool *tmp_pool = NULL;
96   cmdtable *sql_cmdtab = NULL;
97   cmd_rec *sql_cmd = NULL;
98   modret_t *sql_res = NULL;
99   char *insert_query = NULL, *tally_quota_name = NULL, *tally_quota_type = NULL,
100     *tally_bytes_in = NULL, *tally_bytes_out = NULL, *tally_bytes_xfer = NULL,
101     *tally_files_in = NULL, *tally_files_out = NULL, *tally_files_xfer = NULL;
102   quota_tally_t *tally = ptr;
103 
104   /* Allocate a sub pool for use by this function. */
105   tmp_pool = make_sub_pool(sqltab->tab_pool);
106 
107   /* Allocate small buffers for stringifying most of the tally struct
108    * members: quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used,
109    * files_in_used, files_out_used, files_xfer_used.
110    */
111   tally_quota_name = pcalloc(tmp_pool, 83 * sizeof(char));
112   tally_quota_type = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
113   tally_bytes_in = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
114   tally_bytes_out = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
115   tally_bytes_xfer = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
116   tally_files_in = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
117   tally_files_out = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
118   tally_files_xfer = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
119 
120   /* Execute the INSERT NamedQuery */
121   insert_query = ((char **) sqltab->tab_data)[2];
122 
123   /* Populate the INSERT query with the necessary data from the current
124    * limit record.  Need to stringify most of the tally struct members:
125    * quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used,
126    * files_in_used, files_out_used, files_xfer_used.
127    */
128 
129   /* NOTE: Per Issue #1149, we should NOT be adding the quotes to the
130    * text ourselves here.  It also makes the mod_quotatab_sql configuration
131    * inconsistent; the admin must quote these texts in the config for
132    * SELECTs, but not for INSERTs.
133    */
134   pr_snprintf(tally_quota_name, 83, "'%s'",
135     sqltab_get_name(tmp_pool, tally->name));
136   tally_quota_name[82] = '\0';
137 
138   if (tally->quota_type == USER_QUOTA) {
139     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "'%s'", "user");
140 
141   } else if (tally->quota_type == GROUP_QUOTA) {
142     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "'%s'", "group");
143 
144   } else if (tally->quota_type == CLASS_QUOTA) {
145     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "'%s'", "class");
146 
147   } else if (tally->quota_type == ALL_QUOTA) {
148     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "'%s'", "all");
149   }
150 
151   tally_quota_type[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
152 
153   pr_snprintf(tally_bytes_in, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
154     tally->bytes_in_used);
155   tally_bytes_in[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
156 
157   pr_snprintf(tally_bytes_out, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
158     tally->bytes_out_used);
159   tally_bytes_out[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
160 
161   pr_snprintf(tally_bytes_xfer, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
162     tally->bytes_xfer_used);
163   tally_bytes_xfer[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
164 
165   pr_snprintf(tally_files_in, QUOTATAB_SQL_VALUE_BUFSZ, "%u",
166     tally->files_in_used);
167   tally_files_in[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
168 
169   pr_snprintf(tally_files_out, QUOTATAB_SQL_VALUE_BUFSZ, "%u",
170     tally->files_out_used);
171   tally_files_out[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
172 
173   pr_snprintf(tally_files_xfer, QUOTATAB_SQL_VALUE_BUFSZ, "%u",
174     tally->files_xfer_used);
175   tally_files_xfer[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
176 
177   sql_cmd = sqltab_cmd_create(tmp_pool, 10, "sql_change", insert_query,
178     tally_quota_name, tally_quota_type,
179     tally_bytes_in, tally_bytes_out, tally_bytes_xfer,
180     tally_files_in, tally_files_out, tally_files_xfer);
181 
182   /* Find the cmdtable for the sql_change command. */
183   sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_change", NULL, NULL,
184     NULL);
185   if (sql_cmdtab == NULL) {
186     quotatab_log("error: unable to find SQL hook symbol 'sql_change'");
187     destroy_pool(tmp_pool);
188     return -1;
189   }
190 
191   /* Call the handler. */
192   sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
193 
194   /* Check the results. */
195   if (MODRET_ISERROR(sql_res)) {
196     quotatab_log("error executing NamedQuery '%s': %s", insert_query,
197       strerror(errno));
198     destroy_pool(tmp_pool);
199     return -1;
200   }
201 
202   destroy_pool(tmp_pool);
203   return 0;
204 }
205 
sqltab_lookup(quota_table_t * sqltab,void * ptr,const char * name,quota_type_t quota_type)206 static unsigned char sqltab_lookup(quota_table_t *sqltab, void *ptr,
207     const char *name, quota_type_t quota_type) {
208   pool *tmp_pool = NULL;
209   cmdtable *sql_cmdtab = NULL;
210   cmd_rec *sql_cmd = NULL;
211   modret_t *sql_res = NULL;
212   array_header *sql_data = NULL;
213   char *select_query = NULL;
214 
215   /* Allocate a temporary pool for the duration of this lookup. */
216   tmp_pool = make_sub_pool(sqltab->tab_pool);
217 
218   /* Handle tally and limit tables differently... */
219   if (sqltab->tab_type == TYPE_TALLY) {
220     select_query = ((char **) sqltab->tab_data)[0];
221 
222   } else if (sqltab->tab_type == TYPE_LIMIT) {
223     select_query = (char *) sqltab->tab_data;
224   }
225 
226   /* Find the cmdtable for the sql_lookup command. */
227   sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_lookup", NULL, NULL,
228     NULL);
229   if (sql_cmdtab == NULL) {
230     quotatab_log("error: unable to find SQL hook symbol 'sql_lookup'");
231     destroy_pool(tmp_pool);
232     return FALSE;
233   }
234 
235   /* Prepare the SELECT query. */
236   sql_cmd = sqltab_cmd_create(tmp_pool, 4, "sql_lookup", select_query,
237     name ? sqltab_get_name(tmp_pool, (char *) name) : "",
238     quota_type == USER_QUOTA ? "user" : quota_type == GROUP_QUOTA ? "group" :
239     quota_type == CLASS_QUOTA ? "class" : "all");
240 
241   /* Call the handler. */
242   sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
243 
244   /* Check the results. */
245   if (sql_res == NULL ||
246       MODRET_ISERROR(sql_res)) {
247     quotatab_log("error processing NamedQuery '%s'", select_query);
248     destroy_pool(tmp_pool);
249     return FALSE;
250   }
251 
252   sql_data = (array_header *) sql_res->data;
253 
254   if (sqltab->tab_type == TYPE_TALLY) {
255     quota_tally_t *tally = ptr;
256     char **values = (char **) sql_data->elts;
257 
258     /* Update the tally record with the 8 values:
259      *  name
260      *  quota_type
261      *  bytes_{in,out,xfer}_used
262      *  files_{in,out,xfer}_used
263      */
264 
265     if (sql_data->nelts < 8) {
266       if (sql_data->nelts > 0) {
267         quotatab_log("error: SQLNamedQuery '%s' returned incorrect number of "
268           "values (%d)", select_query, sql_data->nelts);
269       }
270 
271       destroy_pool(tmp_pool);
272       return FALSE;
273     }
274 
275     /* Process each element returned. */
276     memmove(tally->name, values[0], sizeof(tally->name));
277 
278     if (strcasecmp(values[1], "user") == 0) {
279       tally->quota_type = USER_QUOTA;
280 
281     } else if (strcasecmp(values[1], "group") == 0) {
282       tally->quota_type = GROUP_QUOTA;
283 
284     } else if (strcasecmp(values[1], "class") == 0) {
285       tally->quota_type = CLASS_QUOTA;
286 
287     } else if (strcasecmp(values[1], "all") == 0) {
288       tally->quota_type = ALL_QUOTA;
289     }
290 
291     /* Check if this is the requested record, now that enough information
292      * is in place.
293      */
294     if (quota_type != tally->quota_type) {
295       destroy_pool(tmp_pool);
296       return FALSE;
297     }
298 
299     /* Match names if need be */
300     if (quota_type != ALL_QUOTA &&
301         values[0] &&
302         strlen(values[0]) > 0 &&
303         strcmp(name, tally->name) != 0) {
304       destroy_pool(tmp_pool);
305       return FALSE;
306     }
307 
308     tally->bytes_in_used = -1.0;
309     if (values[2]) {
310       tally->bytes_in_used = atof(values[2]);
311     }
312 
313     tally->bytes_out_used = -1.0;
314     if (values[3]) {
315       tally->bytes_out_used = atof(values[3]);
316     }
317 
318     tally->bytes_xfer_used = -1.0;
319     if (values[4]) {
320       tally->bytes_xfer_used = atof(values[4]);
321     }
322 
323     tally->files_in_used = 0;
324     if (values[5]) {
325       tally->files_in_used = atol(values[5]);
326     }
327 
328     tally->files_out_used = 0;
329     if (values[6])
330       tally->files_out_used = atol(values[6]);
331 
332     tally->files_xfer_used = 0;
333     if (values[7]) {
334       tally->files_xfer_used = atol(values[7]);
335     }
336 
337     destroy_pool(tmp_pool);
338     return TRUE;
339   }
340 
341   if (sqltab->tab_type == TYPE_LIMIT) {
342     quota_limit_t *limit = ptr;
343     char **values = (char **) sql_data->elts;
344 
345     /* Update the limit record with the 10 values:
346      *  name
347      *  quota_type
348      *  per_session
349      *  limit_type
350      *  bytes_{in,out,xfer}_avail
351      *  files_{in,out,xfer}_avail
352      */
353 
354     if (sql_data->nelts < 10) {
355       if (sql_data->nelts > 0) {
356         quotatab_log("error: SQLNamedQuery '%s' returned incorrect number of "
357           "values (%d)", select_query, sql_data->nelts);
358       }
359 
360       destroy_pool(tmp_pool);
361       return FALSE;
362     }
363 
364     /* Process each element returned. */
365     memmove(limit->name, values[0], sizeof(limit->name));
366 
367     if (strcasecmp(values[1], "user") == 0) {
368       limit->quota_type = USER_QUOTA;
369 
370     } else if (strcasecmp(values[1], "group") == 0) {
371       limit->quota_type = GROUP_QUOTA;
372 
373     } else if (strcasecmp(values[1], "class") == 0) {
374       limit->quota_type = CLASS_QUOTA;
375 
376     } else if (strcasecmp(values[1], "all") == 0) {
377       limit->quota_type = ALL_QUOTA;
378     }
379 
380     /* Check if this is the requested record, now that enough information
381      * is in place.
382      */
383     if (quota_type != limit->quota_type) {
384       destroy_pool(tmp_pool);
385       return FALSE;
386     }
387 
388     /* Match names if need be */
389     if (quota_type != ALL_QUOTA &&
390         values[0] &&
391         strlen(values[0]) > 0 &&
392         strcmp(name, limit->name) != 0) {
393       destroy_pool(tmp_pool);
394       return FALSE;
395     }
396 
397     if (strcasecmp(values[2], "false") == 0) {
398       limit->quota_per_session = FALSE;
399 
400     } else if (strcasecmp(values[2], "true") == 0) {
401       limit->quota_per_session = TRUE;
402     }
403 
404     if (strcasecmp(values[3], "soft") == 0) {
405       limit->quota_limit_type = SOFT_LIMIT;
406 
407     } else if (strcasecmp(values[3], "hard") == 0) {
408       limit->quota_limit_type = HARD_LIMIT;
409     }
410 
411     limit->bytes_in_avail = -1.0;
412     if (values[4]) {
413       limit->bytes_in_avail = atof(values[4]);
414     }
415 
416     limit->bytes_out_avail = -1.0;
417     if (values[5]) {
418       limit->bytes_out_avail = atof(values[5]);
419     }
420 
421     limit->bytes_xfer_avail = -1.0;
422     if (values[6]) {
423       limit->bytes_xfer_avail = atof(values[6]);
424     }
425 
426     limit->files_in_avail = 0;
427     if (values[7]) {
428       limit->files_in_avail = atol(values[7]);
429     }
430 
431     limit->files_out_avail = 0;
432     if (values[8]) {
433       limit->files_out_avail = atol(values[8]);
434     }
435 
436     limit->files_xfer_avail = 0;
437     if (values[9]) {
438       limit->files_xfer_avail = atol(values[9]);
439     }
440 
441     destroy_pool(tmp_pool);
442     return TRUE;
443   }
444 
445   destroy_pool(tmp_pool);
446 
447   /* default */
448   return FALSE;
449 }
450 
sqltab_read(quota_table_t * sqltab,void * ptr)451 static int sqltab_read(quota_table_t *sqltab, void *ptr) {
452   quota_tally_t *tally = ptr;
453 
454   return sqltab_lookup(sqltab, ptr, tally->name, tally->quota_type);
455 }
456 
sqltab_verify(quota_table_t * sqltab)457 static unsigned char sqltab_verify(quota_table_t *sqltab) {
458 
459   /* Always TRUE. */
460   return TRUE;
461 }
462 
sqltab_write(quota_table_t * sqltab,void * ptr)463 static int sqltab_write(quota_table_t *sqltab, void *ptr) {
464   pool *tmp_pool = NULL;
465   cmdtable *sql_cmdtab = NULL;
466   cmd_rec *sql_cmd = NULL;
467   modret_t *sql_res = NULL;
468   char *update_query = NULL, *tally_quota_type = NULL,
469     *tally_bytes_in = NULL, *tally_bytes_out = NULL, *tally_bytes_xfer = NULL,
470     *tally_files_in = NULL, *tally_files_out = NULL, *tally_files_xfer = NULL;
471   quota_tally_t *tally = ptr;
472 
473   /* Allocate a sub pool for use by this function. */
474   tmp_pool = make_sub_pool(sqltab->tab_pool);
475 
476   /* Allocate small buffers for stringifying most of the tally struct
477    * members: quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used,
478    * files_in_used, files_out_used, files_xfer_used.
479    */
480   tally_quota_type = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
481   tally_bytes_in = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
482   tally_bytes_out = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
483   tally_bytes_xfer = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
484   tally_files_in = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
485   tally_files_out = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
486   tally_files_xfer = pcalloc(tmp_pool, QUOTATAB_SQL_VALUE_BUFSZ * sizeof(char));
487 
488   /* Retrieve the UPDATE NamedQuery */
489   update_query = ((char **) sqltab->tab_data)[1];
490 
491   /* Populate the UPDATE query with the necessary data from the current
492    * limit record.  Need to stringify most of the tally struct members:
493    * quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used,
494    * files_in_used, files_out_used, files_xfer_used.
495    */
496 
497   if (tally->quota_type == USER_QUOTA) {
498     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "%s", "user");
499 
500   } else if (tally->quota_type == GROUP_QUOTA) {
501     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "%s", "group");
502 
503   } else if (tally->quota_type == CLASS_QUOTA) {
504     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "%s", "class");
505 
506   } else if (tally->quota_type == ALL_QUOTA) {
507     pr_snprintf(tally_quota_type, QUOTATAB_SQL_VALUE_BUFSZ, "%s", "all");
508   }
509 
510   tally_quota_type[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
511 
512   /* Note: use the deltas data, not the tally members, so that the
513    * UPDATE can do an "atomic" read+update all in one shot.
514    */
515   pr_snprintf(tally_bytes_in, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
516     quotatab_deltas.bytes_in_delta);
517   tally_bytes_in[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
518 
519   pr_snprintf(tally_bytes_out, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
520     quotatab_deltas.bytes_out_delta);
521   tally_bytes_out[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
522 
523   pr_snprintf(tally_bytes_xfer, QUOTATAB_SQL_VALUE_BUFSZ, "%f",
524     quotatab_deltas.bytes_xfer_delta);
525   tally_bytes_xfer[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
526 
527   /* Don't try to prevent underflows here; mod_quotatab already makes
528    * these checks.
529    */
530   pr_snprintf(tally_files_in, QUOTATAB_SQL_VALUE_BUFSZ, "%d",
531     quotatab_deltas.files_in_delta);
532   tally_files_in[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
533 
534   pr_snprintf(tally_files_out, QUOTATAB_SQL_VALUE_BUFSZ, "%d",
535     quotatab_deltas.files_out_delta);
536   tally_files_out[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
537 
538   pr_snprintf(tally_files_xfer, QUOTATAB_SQL_VALUE_BUFSZ, "%d",
539     quotatab_deltas.files_xfer_delta);
540   tally_files_xfer[QUOTATAB_SQL_VALUE_BUFSZ-1] = '\0';
541 
542   sql_cmd = sqltab_cmd_create(tmp_pool, 10, "sql_change", update_query,
543     tally_bytes_in, tally_bytes_out, tally_bytes_xfer,
544     tally_files_in, tally_files_out, tally_files_xfer,
545     sqltab_get_name(tmp_pool, tally->name), tally_quota_type);
546 
547   /* Find the cmdtable for the sql_change command. */
548   sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_change", NULL, NULL,
549     NULL);
550   if (sql_cmdtab == NULL) {
551     quotatab_log("error: unable to find SQL hook symbol 'sql_change'");
552     destroy_pool(tmp_pool);
553     return -1;
554   }
555 
556   /* Call the handler. */
557   sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
558 
559   /* Check the results. */
560   if (MODRET_ISERROR(sql_res)) {
561     quotatab_log("error executing NamedQuery '%s': %s", update_query,
562       strerror(errno));
563     destroy_pool(tmp_pool);
564     return -1;
565   }
566 
567   destroy_pool(tmp_pool);
568   return 0;
569 }
570 
sqltab_rlock(quota_table_t * sqltab)571 static int sqltab_rlock(quota_table_t *sqltab) {
572 
573   /* Check for a configured lock file. */
574   if (sqltab->tab_lockfd > 0) {
575     sqltab->tab_lock.l_type = F_RDLCK;
576     return fcntl(sqltab->tab_lockfd, F_SETLK, &sqltab->tab_lock);
577   }
578 
579   return 0;
580 }
581 
sqltab_unlock(quota_table_t * sqltab)582 static int sqltab_unlock(quota_table_t *sqltab) {
583 
584   /* Check for a configured lock file. */
585   if (sqltab->tab_lockfd > 0) {
586     sqltab->tab_lock.l_type = F_UNLCK;
587     return fcntl(sqltab->tab_lockfd, F_SETLK, &sqltab->tab_lock);
588   }
589 
590   return 0;
591 }
592 
sqltab_wlock(quota_table_t * sqltab)593 static int sqltab_wlock(quota_table_t *sqltab) {
594 
595   /* Check for a configured lock file. */
596   if (sqltab->tab_lockfd > 0) {
597     sqltab->tab_lock.l_type = F_WRLCK;
598     return fcntl(sqltab->tab_lockfd, F_SETLK, &sqltab->tab_lock);
599   }
600 
601   return 0;
602 }
603 
sqltab_open(pool * parent_pool,quota_tabtype_t tab_type,const char * srcinfo)604 static quota_table_t *sqltab_open(pool *parent_pool, quota_tabtype_t tab_type,
605     const char *srcinfo) {
606 
607   quota_table_t *tab = NULL;
608   pool *tab_pool = make_sub_pool(parent_pool),
609     *tmp_pool = make_sub_pool(parent_pool);
610   config_rec *c = NULL;
611   char *named_query = NULL;
612 
613   tab = (quota_table_t *) pcalloc(tab_pool, sizeof(quota_table_t));
614   tab->tab_pool = tab_pool;
615   tab->tab_type = tab_type;
616 
617   if (tab->tab_type == TYPE_TALLY) {
618     char *start = NULL, *finish = NULL;
619     char *select_query = NULL, *update_query = NULL, *insert_query = NULL;
620 
621     /* Parse the SELECT, UPDATE, and INSERT query names out of the srcinfo
622      * string.  Lookup and store the queries in the tab_data area, so that
623      * they need not be looked up later.
624      *
625      * The srcinfo string for this case should look like:
626      *  "/<select-named-query>/<update-named-query>/<insert-named-query>/"
627      */
628 
629     start = strchr(srcinfo, '/');
630     if (start == NULL) {
631       quotatab_log("error: badly formatted source info '%s'", srcinfo);
632       destroy_pool(tmp_pool);
633       errno = EINVAL;
634       return NULL;
635     }
636 
637     /* Find the next slash. */
638     finish = strchr(++start, '/');
639     if (finish == NULL) {
640       quotatab_log("error: badly formatted source info '%s'", srcinfo);
641       destroy_pool(tmp_pool);
642       errno = EINVAL;
643       return NULL;
644     }
645 
646     *finish = '\0';
647     select_query = pstrdup(tab->tab_pool, start);
648 
649     /* Verify that the named query has indeed been defined. This is
650      * based on how mod_sql creates its config_rec names.
651      */
652     named_query = pstrcat(tmp_pool, "SQLNamedQuery_", select_query, NULL);
653 
654     c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
655     if (c == NULL) {
656       quotatab_log("error: unable to resolve SQLNamedQuery name '%s'",
657         select_query);
658       destroy_pool(tmp_pool);
659       errno = EINVAL;
660       return NULL;
661     }
662 
663     /* Now, find the next slash. */
664     start = finish;
665     finish = strchr(++start, '/');
666     if (finish == NULL) {
667       quotatab_log("error: badly formatted source info '%s'", srcinfo);
668       destroy_pool(tmp_pool);
669       errno = EINVAL;
670       return NULL;
671     }
672 
673     *finish = '\0';
674     update_query = pstrdup(tab->tab_pool, start);
675 
676     /* Verify that the named query has indeed been defined. This is
677      * based on how mod_sql creates its config_rec names.
678      */
679     named_query = pstrcat(tmp_pool, "SQLNamedQuery_", update_query, NULL);
680 
681     c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
682     if (c == NULL) {
683       quotatab_log("error: unable to resolve SQLNamedQuery name '%s'",
684         update_query);
685       destroy_pool(tmp_pool);
686       errno = EINVAL;
687       return NULL;
688     }
689 
690     /* Assume the rest of the srcinfo string is the INSERT query (assuming
691      * there *is* a "rest of the string").
692      */
693     if (*(++finish) != '\0') {
694       insert_query = pstrdup(tab->tab_pool, finish);
695 
696     } else {
697       quotatab_log("error: badly formatted source info '%s'", srcinfo);
698       destroy_pool(tmp_pool);
699       errno = EINVAL;
700       return NULL;
701     }
702 
703     /* Verify that the named query has indeed been defined. This is
704      * based on how mod_sql creates its config_rec names.
705      */
706     named_query = pstrcat(tmp_pool, "SQLNamedQuery_", insert_query, NULL);
707 
708     c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
709     if (c == NULL) {
710       quotatab_log("error: unable to resolve SQLNamedQuery name '%s'",
711         insert_query);
712       destroy_pool(tmp_pool);
713       errno = EINVAL;
714       return NULL;
715     }
716 
717     tab->tab_data = (char **) pcalloc(tab->tab_pool, 3 * sizeof(char *));
718     ((char **) tab->tab_data)[0] = pstrdup(tab->tab_pool, select_query);
719     ((char **) tab->tab_data)[1] = pstrdup(tab->tab_pool, update_query);
720     ((char **) tab->tab_data)[2] = pstrdup(tab->tab_pool, insert_query);
721 
722   } else if (tab->tab_type == TYPE_LIMIT) {
723     char *start = NULL, *select_query = NULL;
724 
725     /* Parse the SELECT query name out of the srcinfo string.  Lookup and
726      * store the queries in the tab_data area, so that it need not be looked
727      * up later.
728      *
729      * The srcinfo string for this case should look like:
730      *  "/<select-named-query>"
731      */
732 
733     start = strchr(srcinfo, '/');
734     if (start == NULL) {
735       quotatab_log("error: badly formatted source info '%s'", srcinfo);
736       destroy_pool(tmp_pool);
737       errno = EINVAL;
738       return NULL;
739     }
740     select_query = ++start;
741 
742     /* Verify that the named query has indeed been defined. This is
743      * based on how mod_sql creates its config_rec names.
744      */
745     named_query = pstrcat(tmp_pool, "SQLNamedQuery_", select_query, NULL);
746 
747     c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
748     if (c == NULL) {
749       quotatab_log("error: unable to resolve SQLNamedQuery name '%s'",
750         select_query);
751       destroy_pool(tmp_pool);
752       errno = EINVAL;
753       return NULL;
754     }
755 
756     tab->tab_data = (void *) pstrdup(tab->tab_pool, select_query);
757   }
758 
759   /* Set all the necessary function pointers. */
760   tab->tab_close = sqltab_close;
761   tab->tab_create = sqltab_create;
762   tab->tab_lookup = sqltab_lookup;
763   tab->tab_read = sqltab_read;
764   tab->tab_verify = sqltab_verify;
765   tab->tab_write = sqltab_write;
766 
767   tab->tab_rlock = sqltab_rlock;
768   tab->tab_unlock = sqltab_unlock;
769   tab->tab_wlock = sqltab_wlock;
770 
771   /* Prepare the lock structure. */
772   tab->tab_lock.l_whence = SEEK_CUR;
773   tab->tab_lock.l_start = 0;
774   tab->tab_lock.l_len = 0;
775 
776   destroy_pool(tmp_pool);
777   return tab;
778 }
779 
780 /* Event handlers
781  */
782 
783 #if defined(PR_SHARED_MODULE)
sqltab_mod_unload_ev(const void * event_data,void * user_data)784 static void sqltab_mod_unload_ev(const void *event_data, void *user_data) {
785   if (strcmp("mod_quotatab_sql.c", (const char *) event_data) == 0) {
786     pr_event_unregister(&quotatab_sql_module, NULL, NULL);
787     quotatab_unregister_backend("sql", QUOTATAB_LIMIT_SRC|QUOTATAB_TALLY_SRC);
788   }
789 }
790 #endif /* PR_SHARED_MODULE */
791 
792 /* Initialization routines
793  */
794 
sqltab_init(void)795 static int sqltab_init(void) {
796 
797   /* Initialize the quota source objects for type "sql". */
798   quotatab_register_backend("sql", sqltab_open,
799     QUOTATAB_LIMIT_SRC|QUOTATAB_TALLY_SRC);
800 
801 #if defined(PR_SHARED_MODULE)
802   pr_event_register(&quotatab_sql_module, "core.module-unload",
803     sqltab_mod_unload_ev, NULL);
804 #endif /* PR_SHARED_MODULE */
805 
806   return 0;
807 }
808 
809 module quotatab_sql_module = {
810   NULL, NULL,
811 
812   /* Module API version 2.0 */
813   0x20,
814 
815   /* Module name */
816   "quotatab_sql",
817 
818   /* Module configuration handler table */
819   NULL,
820 
821   /* Module command handler table */
822   NULL,
823 
824   /* Module authentication handler table */
825   NULL,
826 
827   /* Module initialization */
828   sqltab_init,
829 
830   /* Session initialization */
831   NULL
832 };
833