07a0e68de610a9778585eeca24f697146e9059a9
[freeradius.git] / src / modules / rlm_sql / rlm_sql.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_sql.c
20  * @brief Implements SQL 'users' file, and SQL accounting.
21  *
22  * @copyright 2012-2014  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23  * @copyright 2000,2006  The FreeRADIUS server project
24  * @copyright 2000  Mike Machado <mike@innercite.com>
25  * @copyright 2000  Alan DeKok <aland@ox.org>
26  */
27 RCSID("$Id$")
28
29 #include <ctype.h>
30
31 #include <freeradius-devel/radiusd.h>
32 #include <freeradius-devel/modules.h>
33 #include <freeradius-devel/token.h>
34 #include <freeradius-devel/rad_assert.h>
35 #include <freeradius-devel/exfile.h>
36
37 #include <sys/stat.h>
38
39 #include "rlm_sql.h"
40
41 /*
42  *      So we can do pass2 xlat checks on the queries.
43  */
44 static const CONF_PARSER query_config[] = {
45         { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, accounting.query), NULL },
46
47         CONF_PARSER_TERMINATOR
48 };
49
50 /*
51  *      For now hard-code the subsections.  This isn't perfect, but it
52  *      helps the average case.
53  */
54 static const CONF_PARSER type_config[] = {
55         { "accounting-on", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
56         { "accounting-off", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
57         { "start", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
58         { "interim-update", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
59         { "stop", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
60
61         CONF_PARSER_TERMINATOR
62 };
63
64 static const CONF_PARSER acct_config[] = {
65         { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.reference), ".query" },
66         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.logfile), NULL },
67
68         { "type", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) type_config },
69
70         CONF_PARSER_TERMINATOR
71 };
72
73 static const CONF_PARSER postauth_config[] = {
74         { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.reference), ".query" },
75         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.logfile), NULL },
76
77         { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, postauth.query), NULL },
78         CONF_PARSER_TERMINATOR
79 };
80
81 static const CONF_PARSER module_config[] = {
82         { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
83         { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "" }, /* Must be zero length so drivers can determine if it was set */
84         { "port", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, sql_port), "0" },
85         { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
86         { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
87         { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
88         { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
89         { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
90         { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
91         { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
92         { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, delete_stale_sessions), NULL },
93         { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, delete_stale_sessions), "yes" },
94         { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, query_user), "" },
95         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, logfile), NULL },
96         { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
97         { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
98         { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
99         { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, connect_query), NULL },
100
101         { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_check_query), NULL },
102         { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_reply_query), NULL },
103
104         { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_check_query), NULL },
105         { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_reply_query), NULL },
106         { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, groupmemb_query), NULL },
107 #ifdef WITH_SESSION_MGMT
108         { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_count_query), NULL },
109         { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_verify_query), NULL },
110 #endif
111         { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
112         { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
113
114         /*
115          *      This only works for a few drivers.
116          */
117         { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
118
119         { "accounting", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) acct_config },
120
121         { "post-auth", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) postauth_config },
122         CONF_PARSER_TERMINATOR
123 };
124
125 /*
126  *      Fall-Through checking function from rlm_files.c
127  */
128 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
129 {
130         VALUE_PAIR *tmp;
131         tmp = fr_pair_find_by_num(vp, PW_FALL_THROUGH, 0, TAG_ANY);
132
133         return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
134 }
135
136 /*
137  *      Yucky prototype.
138  */
139 static int generate_sql_clients(rlm_sql_t *inst);
140 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
141
142 /*
143  *                      SQL xlat function
144  *
145  *  For selects the first value of the first column will be returned,
146  *  for inserts, updates and deletes the number of rows affected will be
147  *  returned instead.
148  */
149 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
150 {
151         rlm_sql_handle_t        *handle = NULL;
152         rlm_sql_row_t           row;
153         rlm_sql_t               *inst = instance;
154         sql_rcode_t             rcode;
155         ssize_t                 ret = 0;
156         size_t                  len = 0;
157
158         /*
159          *      Add SQL-User-Name attribute just in case it is needed
160          *      We could search the string fmt for SQL-User-Name to see if this is
161          *      needed or not
162          */
163         sql_set_user(inst, request, NULL);
164
165         handle = fr_connection_get(inst->pool); /* connection pool should produce error */
166         if (!handle) return 0;
167
168         rlm_sql_query_log(inst, request, NULL, query);
169
170         /*
171          *      If the query starts with any of the following prefixes,
172          *      then return the number of rows affected
173          */
174         if ((strncasecmp(query, "insert", 6) == 0) ||
175             (strncasecmp(query, "update", 6) == 0) ||
176             (strncasecmp(query, "delete", 6) == 0)) {
177                 int numaffected;
178                 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
179
180                 rcode = rlm_sql_query(inst, request, &handle, query);
181                 if (rcode != RLM_SQL_OK) {
182                 query_error:
183                         if (rcode != RLM_SQL_NO_MORE_ROWS) {
184                                 RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, rcode, "<INVALID>"));
185                         } else {
186                                 RDEBUG("No rows were returned.");
187                         }
188
189                         ret = -1;
190                         goto finish;
191                 }
192
193                 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
194                 if (numaffected < 1) {
195                         RDEBUG("SQL query affected no rows");
196                         (inst->module->sql_finish_query)(handle, inst->config);
197
198                         goto finish;
199                 }
200
201                 /*
202                  *      Don't chop the returned number if freespace is
203                  *      too small.  This hack is necessary because
204                  *      some implementations of snprintf return the
205                  *      size of the written data, and others return
206                  *      the size of the data they *would* have written
207                  *      if the output buffer was large enough.
208                  */
209                 snprintf(buffer, sizeof(buffer), "%d", numaffected);
210
211                 len = strlen(buffer);
212                 if (len >= freespace){
213                         RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->name);
214
215                         (inst->module->sql_finish_query)(handle, inst->config);
216
217                         ret = -1;
218                         goto finish;
219                 }
220
221                 memcpy(out, buffer, len + 1); /* we did bounds checking above */
222                 ret = len;
223
224                 (inst->module->sql_finish_query)(handle, inst->config);
225
226                 goto finish;
227         } /* else it's a SELECT statement */
228
229         rcode = rlm_sql_select_query(inst, request, &handle, query);
230         if (rcode != RLM_SQL_OK) goto query_error;
231
232         rcode = rlm_sql_fetch_row(inst, request, &handle);
233         if (rcode != RLM_SQL_OK) {
234                 (inst->module->sql_finish_select_query)(handle, inst->config);
235                 goto query_error;
236         }
237
238         row = handle->row;
239         if (!row) {
240                 RDEBUG("SQL query returned no results");
241                 (inst->module->sql_finish_select_query)(handle, inst->config);
242                 ret = -1;
243
244                 goto finish;
245         }
246
247         if (!row[0]){
248                 RDEBUG("NULL value in first column of result");
249                 (inst->module->sql_finish_select_query)(handle, inst->config);
250                 ret = -1;
251
252                 goto finish;
253         }
254
255         len = strlen(row[0]);
256         if (len >= freespace){
257                 RDEBUG("Insufficient string space");
258                 (inst->module->sql_finish_select_query)(handle, inst->config);
259
260                 ret = -1;
261                 goto finish;
262         }
263
264         strlcpy(out, row[0], freespace);
265         ret = len;
266
267         (inst->module->sql_finish_select_query)(handle, inst->config);
268
269 finish:
270         fr_connection_release(inst->pool, handle);
271
272         return ret;
273 }
274
275 static int generate_sql_clients(rlm_sql_t *inst)
276 {
277         rlm_sql_handle_t *handle;
278         rlm_sql_row_t row;
279         unsigned int i = 0;
280         RADCLIENT *c;
281
282         DEBUG("rlm_sql (%s): Processing generate_sql_clients",
283               inst->name);
284
285         DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
286               inst->name, inst->config->client_query);
287
288         handle = fr_connection_get(inst->pool);
289         if (!handle) return -1;
290
291         if (rlm_sql_select_query(inst, NULL, &handle, inst->config->client_query) != RLM_SQL_OK) return -1;
292
293         while ((rlm_sql_fetch_row(inst, NULL, &handle) == RLM_SQL_OK) && (row = handle->row)) {
294                 int num_rows;
295                 char *server = NULL;
296
297                 i++;
298
299                 num_rows = (inst->module->sql_num_fields)(handle, inst->config);
300                 if (num_rows < 5) {
301                         WARN("SELECT returned too few rows.  Please do not edit 'client_query'");
302                         continue;
303                 }
304
305                 /*
306                  *  The return data for each row MUST be in the following order:
307                  *
308                  *  0. Row ID (currently unused)
309                  *  1. Name (or IP address)
310                  *  2. Shortname
311                  *  3. Type
312                  *  4. Secret
313                  *  5. Virtual Server (optional)
314                  */
315                 if (!row[0]){
316                         ERROR("rlm_sql (%s): No row id found on pass %d",inst->name,i);
317                         continue;
318                 }
319                 if (!row[1]){
320                         ERROR("rlm_sql (%s): No nasname found for row %s",inst->name,row[0]);
321                         continue;
322                 }
323                 if (!row[2]){
324                         ERROR("rlm_sql (%s): No short name found for row %s",inst->name,row[0]);
325                         continue;
326                 }
327                 if (!row[4]){
328                         ERROR("rlm_sql (%s): No secret found for row %s",inst->name,row[0]);
329                         continue;
330                 }
331
332                 if ((num_rows > 5) && (row[5] != NULL) && *row[5]) {
333                         server = row[5];
334                 }
335
336                 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
337                       inst->name,
338                       row[1], row[2], server ? server : "global");
339
340                 /* FIXME: We should really pass a proper ctx */
341                 c = client_afrom_query(NULL,
342                                       row[1],   /* identifier */
343                                       row[4],   /* secret */
344                                       row[2],   /* shortname */
345                                       row[3],   /* type */
346                                       server,   /* server */
347                                       false);   /* require message authenticator */
348                 if (!c) {
349                         continue;
350                 }
351
352                 if (!client_add(NULL, c)) {
353                         WARN("Failed to add client, possible duplicate?");
354
355                         client_free(c);
356                         continue;
357                 }
358
359                 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
360                       inst->name);
361         }
362
363         (inst->module->sql_finish_select_query)(handle, inst->config);
364         fr_connection_release(inst->pool, handle);
365
366         return 0;
367 }
368
369
370 /*
371  *      Translate the SQL queries.
372  */
373 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
374                               char const *in, void *arg)
375 {
376         rlm_sql_t *inst = arg;
377         size_t len = 0;
378
379         while (in[0]) {
380                 size_t utf8_len;
381
382                 /*
383                  *      Allow all multi-byte UTF8 characters.
384                  */
385                 utf8_len = fr_utf8_char((uint8_t const *) in, -1);
386                 if (utf8_len > 1) {
387                         if (outlen <= utf8_len) break;
388
389                         memcpy(out, in, utf8_len);
390                         in += utf8_len;
391                         out += utf8_len;
392
393                         outlen -= utf8_len;
394                         len += utf8_len;
395                         continue;
396                 }
397
398                 /*
399                  *      Because we register our own escape function
400                  *      we're now responsible for escaping all special
401                  *      chars in an xlat expansion or attribute value.
402                  */
403                 switch (in[0]) {
404                 case '\n':
405                         if (outlen <= 2) break;
406                         out[0] = '\\';
407                         out[1] = 'n';
408
409                         in++;
410                         out += 2;
411                         outlen -= 2;
412                         len += 2;
413                         break;
414
415                 case '\r':
416                         if (outlen <= 2) break;
417                         out[0] = '\\';
418                         out[1] = 'r';
419
420                         in++;
421                         out += 2;
422                         outlen -= 2;
423                         len += 2;
424                         break;
425
426                 case '\t':
427                         if (outlen <= 2) break;
428                         out[0] = '\\';
429                         out[1] = 't';
430
431                         in++;
432                         out += 2;
433                         outlen -= 2;
434                         len += 2;
435                         break;
436                 }
437
438                 /*
439                  *      Non-printable characters get replaced with their
440                  *      mime-encoded equivalents.
441                  */
442                 if ((in[0] < 32) ||
443                     strchr(inst->config->allowed_chars, *in) == NULL) {
444                         /*
445                          *      Only 3 or less bytes available.
446                          */
447                         if (outlen <= 3) {
448                                 break;
449                         }
450
451                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
452                         in++;
453                         out += 3;
454                         outlen -= 3;
455                         len += 3;
456                         continue;
457                 }
458
459                 /*
460                  *      Only one byte left.
461                  */
462                 if (outlen <= 1) {
463                         break;
464                 }
465
466                 /*
467                  *      Allowed character.
468                  */
469                 *out = *in;
470                 out++;
471                 in++;
472                 outlen--;
473                 len++;
474         }
475         *out = '\0';
476         return len;
477 }
478
479 /*
480  *      Set the SQL user name.
481  *
482  *      We don't call the escape function here. The resulting string
483  *      will be escaped later in the queries xlat so we don't need to
484  *      escape it twice. (it will make things wrong if we have an
485  *      escape candidate character in the username)
486  */
487 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
488 {
489         char *expanded = NULL;
490         VALUE_PAIR *vp = NULL;
491         char const *sqluser;
492         ssize_t len;
493
494         rad_assert(request->packet != NULL);
495
496         if (username != NULL) {
497                 sqluser = username;
498         } else if (inst->config->query_user[0] != '\0') {
499                 sqluser = inst->config->query_user;
500         } else {
501                 return 0;
502         }
503
504         len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
505         if (len < 0) {
506                 return -1;
507         }
508
509         vp = fr_pair_afrom_da(request->packet, inst->sql_user);
510         if (!vp) {
511                 talloc_free(expanded);
512                 return -1;
513         }
514
515         fr_pair_value_strsteal(vp, expanded);
516         RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
517         vp->op = T_OP_SET;
518
519         /*
520          *      Delete any existing SQL-User-Name, and replace it with ours.
521          */
522         fr_pair_delete_by_num(&request->packet->vps, vp->da->attr, vp->da->vendor, TAG_ANY);
523         fr_pair_add(&request->packet->vps, vp);
524
525         return 0;
526 }
527
528 /*
529  *      Do a set/unset user, so it's a bit clearer what's going on.
530  */
531 #define sql_unset_user(_i, _r) fr_pair_delete_by_num(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
532
533 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
534                              rlm_sql_grouplist_t **phead)
535 {
536         char    *expanded = NULL;
537         int     num_groups = 0;
538         rlm_sql_row_t row;
539         rlm_sql_grouplist_t *entry;
540         int ret;
541
542         /* NOTE: sql_set_user should have been run before calling this function */
543
544         entry = *phead = NULL;
545
546         if (!inst->config->groupmemb_query) return 0;
547
548         if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) return -1;
549
550         ret = rlm_sql_select_query(inst, request, handle, expanded);
551         talloc_free(expanded);
552         if (ret != RLM_SQL_OK) return -1;
553
554         while (rlm_sql_fetch_row(inst, request, handle) == RLM_SQL_OK) {
555                 row = (*handle)->row;
556                 if (!row)
557                         break;
558
559                 if (!row[0]){
560                         RDEBUG("row[0] returned NULL");
561                         (inst->module->sql_finish_select_query)(*handle, inst->config);
562                         talloc_free(entry);
563                         return -1;
564                 }
565
566                 if (!*phead) {
567                         *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
568                         entry = *phead;
569                 } else {
570                         entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
571                         entry = entry->next;
572                 }
573                 entry->next = NULL;
574                 entry->name = talloc_typed_strdup(entry, row[0]);
575
576                 num_groups++;
577         }
578
579         (inst->module->sql_finish_select_query)(*handle, inst->config);
580
581         return num_groups;
582 }
583
584
585 /*
586  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
587  * with the group memberships reciding in sql
588  * The group membership query should only return one element which is the username. The returned
589  * username will then be checked with the passed check string.
590  */
591 static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
592                         VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
593                         UNUSED VALUE_PAIR **reply_pairs) CC_HINT(nonnull (1, 2, 4));
594
595 static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
596                         VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
597                         UNUSED VALUE_PAIR **reply_pairs)
598 {
599         rlm_sql_handle_t *handle;
600         rlm_sql_t *inst = instance;
601         rlm_sql_grouplist_t *head, *entry;
602
603         /*
604          *      No group queries, don't do group comparisons.
605          */
606         if (!inst->config->groupmemb_query) {
607                 RWARN("Cannot do group comparison when group_membership_query is not set");
608                 return 1;
609         }
610
611         RDEBUG("sql_groupcmp");
612
613         if (check->vp_length == 0){
614                 RDEBUG("sql_groupcmp: Illegal group name");
615                 return 1;
616         }
617
618         /*
619          *      Set, escape, and check the user attr here
620          */
621         if (sql_set_user(inst, request, NULL) < 0)
622                 return 1;
623
624         /*
625          *      Get a socket for this lookup
626          */
627         handle = fr_connection_get(inst->pool);
628         if (!handle) {
629                 return 1;
630         }
631
632         /*
633          *      Get the list of groups this user is a member of
634          */
635         if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
636                 REDEBUG("Error getting group membership");
637                 fr_connection_release(inst->pool, handle);
638                 return 1;
639         }
640
641         for (entry = head; entry != NULL; entry = entry->next) {
642                 if (strcmp(entry->name, check->vp_strvalue) == 0){
643                         RDEBUG("sql_groupcmp finished: User is a member of group %s",
644                                check->vp_strvalue);
645                         talloc_free(head);
646                         fr_connection_release(inst->pool, handle);
647                         return 0;
648                 }
649         }
650
651         /* Free the grouplist */
652         talloc_free(head);
653         fr_connection_release(inst->pool, handle);
654
655         RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
656
657         return 1;
658 }
659
660 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
661                                           sql_fall_through_t *do_fall_through)
662 {
663         rlm_rcode_t             rcode = RLM_MODULE_NOOP;
664         VALUE_PAIR              *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
665         rlm_sql_grouplist_t     *head = NULL, *entry = NULL;
666
667         char                    *expanded = NULL;
668         int                     rows;
669
670         rad_assert(request->packet != NULL);
671
672         if (!inst->config->groupmemb_query) {
673                 RWARN("Cannot do check groups when group_membership_query is not set");
674
675         do_nothing:
676                 *do_fall_through = FALL_THROUGH_DEFAULT;
677
678                 /*
679                  *      Didn't add group attributes or allocate
680                  *      memory, so don't do anything else.
681                  */
682                 return RLM_MODULE_NOTFOUND;
683         }
684
685         /*
686          *      Get the list of groups this user is a member of
687          */
688         rows = sql_get_grouplist(inst, handle, request, &head);
689         if (rows < 0) {
690                 REDEBUG("Error retrieving group list");
691
692                 return RLM_MODULE_FAIL;
693         }
694         if (rows == 0) {
695                 RDEBUG2("User not found in any groups");
696                 goto do_nothing;
697         }
698         rad_assert(head);
699
700         RDEBUG2("User found in the group table");
701
702         /*
703          *      Add the Sql-Group attribute to the request list so we know
704          *      which group we're retrieving attributes for
705          */
706         sql_group = pair_make_request(inst->group_da->name, NULL, T_OP_EQ);
707         if (!sql_group) {
708                 REDEBUG("Error creating %s attribute", inst->group_da->name);
709                 rcode = RLM_MODULE_FAIL;
710                 goto finish;
711         }
712
713         entry = head;
714         do {
715         next:
716                 rad_assert(entry != NULL);
717                 fr_pair_value_strcpy(sql_group, entry->name);
718
719                 if (inst->config->authorize_group_check_query) {
720                         vp_cursor_t cursor;
721                         VALUE_PAIR *vp;
722
723                         /*
724                          *      Expand the group query
725                          */
726                         if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
727                                          sql_escape_func, inst) < 0) {
728                                 REDEBUG("Error generating query");
729                                 rcode = RLM_MODULE_FAIL;
730                                 goto finish;
731                         }
732
733                         rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded);
734                         TALLOC_FREE(expanded);
735                         if (rows < 0) {
736                                 REDEBUG("Error retrieving check pairs for group %s", entry->name);
737                                 rcode = RLM_MODULE_FAIL;
738                                 goto finish;
739                         }
740
741                         /*
742                          *      If we got check rows we need to process them before we decide to
743                          *      process the reply rows
744                          */
745                         if ((rows > 0) &&
746                             (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
747                                 fr_pair_list_free(&check_tmp);
748                                 entry = entry->next;
749
750                                 if (!entry) break;
751
752                                 goto next;      /* != continue */
753                         }
754
755                         RDEBUG2("Group \"%s\": Conditional check items matched", entry->name);
756                         rcode = RLM_MODULE_OK;
757
758                         RDEBUG2("Group \"%s\": Merging assignment check items", entry->name);
759                         RINDENT();
760                         for (vp = fr_cursor_init(&cursor, &check_tmp);
761                              vp;
762                              vp = fr_cursor_next(&cursor)) {
763                                 if (!fr_assignment_op[vp->op]) continue;
764
765                                 rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
766                         }
767                         REXDENT();
768                         radius_pairmove(request, &request->config, check_tmp, true);
769                         check_tmp = NULL;
770                 }
771
772                 if (inst->config->authorize_group_reply_query) {
773                         /*
774                          *      Now get the reply pairs since the paircompare matched
775                          */
776                         if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
777                                          sql_escape_func, inst) < 0) {
778                                 REDEBUG("Error generating query");
779                                 rcode = RLM_MODULE_FAIL;
780                                 goto finish;
781                         }
782
783                         rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded);
784                         TALLOC_FREE(expanded);
785                         if (rows < 0) {
786                                 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
787                                 rcode = RLM_MODULE_FAIL;
788                                 goto finish;
789                         }
790                         *do_fall_through = fall_through(reply_tmp);
791
792                         RDEBUG2("Group \"%s\": Merging reply items", entry->name);
793                         rcode = RLM_MODULE_OK;
794
795                         rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
796
797                         radius_pairmove(request, &request->reply->vps, reply_tmp, true);
798                         reply_tmp = NULL;
799                 /*
800                  *      If there's no reply query configured, then we assume
801                  *      FALL_THROUGH_NO, which is the same as the users file if you
802                  *      had no reply attributes.
803                  */
804                 } else {
805                         *do_fall_through = FALL_THROUGH_DEFAULT;
806                 }
807
808                 entry = entry->next;
809         } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));
810
811 finish:
812         talloc_free(head);
813         fr_pair_delete_by_num(&request->packet->vps, inst->group_da->attr, 0, TAG_ANY);
814
815         return rcode;
816 }
817
818
819 static int mod_detach(void *instance)
820 {
821         rlm_sql_t *inst = instance;
822
823         if (inst->pool) fr_connection_pool_free(inst->pool);
824
825         /*
826          *  We need to explicitly free all children, so if the driver
827          *  parented any memory off the instance, their destructors
828          *  run before we unload the bytecode for them.
829          *
830          *  If we don't do this, we get a SEGV deep inside the talloc code
831          *  when it tries to call a destructor that no longer exists.
832          */
833         talloc_free_children(inst);
834
835         /*
836          *  Decrements the reference count. The driver object won't be unloaded
837          *  until all instances of rlm_sql that use it have been destroyed.
838          */
839         if (inst->handle) dlclose(inst->handle);
840
841         return 0;
842 }
843
844 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
845 {
846         rlm_sql_t *inst = instance;
847
848         /*
849          *      Hack...
850          */
851         inst->config = &inst->myconfig;
852         inst->cs = conf;
853
854         inst->name = cf_section_name2(conf);
855         if (!inst->name) inst->name = cf_section_name1(conf);
856
857         /*
858          *      Load the appropriate driver for our database.
859          *
860          *      We need this to check if the sql_fields callback is provided.
861          */
862         inst->handle = fr_dlopenext(inst->config->sql_driver_name);
863         if (!inst->handle) {
864                 ERROR("Could not link driver %s: %s", inst->config->sql_driver_name, fr_strerror());
865                 ERROR("Make sure it (and all its dependent libraries!) are in the search path of your system's ld");
866                 return -1;
867         }
868
869         inst->module = (rlm_sql_module_t *) dlsym(inst->handle,  inst->config->sql_driver_name);
870         if (!inst->module) {
871                 ERROR("Could not link symbol %s: %s", inst->config->sql_driver_name, dlerror());
872                 return -1;
873         }
874
875         INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked", inst->name,
876              inst->config->sql_driver_name, inst->module->name);
877
878         if (inst->config->groupmemb_query) {
879                 if (cf_section_name2(conf)) {
880                         char buffer[256];
881
882                         snprintf(buffer, sizeof(buffer), "%s-SQL-Group", inst->name);
883
884                         if (paircompare_register_byname(buffer, dict_attrbyvalue(PW_USER_NAME, 0),
885                                                         false, sql_groupcmp, inst) < 0) {
886                                 ERROR("Error registering group comparison: %s", fr_strerror());
887                                 return -1;
888                         }
889
890                         inst->group_da = dict_attrbyname(buffer);
891
892                         /*
893                          *      We're the default instance
894                          */
895                 } else {
896                         if (paircompare_register_byname("SQL-Group", dict_attrbyvalue(PW_USER_NAME, 0),
897                                                         false, sql_groupcmp, inst) < 0) {
898                                 ERROR("Error registering group comparison: %s", fr_strerror());
899                                 return -1;
900                         }
901
902                         inst->group_da = dict_attrbyname("SQL-Group");
903                 }
904
905                 if (!inst->group_da) {
906                         ERROR("Failed resolving group attribute");
907                         return -1;
908                 }
909         }
910
911         /*
912          *      Register the SQL xlat function
913          */
914         xlat_register(inst->name, sql_xlat, sql_escape_func, inst);
915
916         return 0;
917 }
918
919
920 static int mod_instantiate(CONF_SECTION *conf, void *instance)
921 {
922         rlm_sql_t *inst = instance;
923
924         /*
925          *      Complain if the strings exist, but are empty.
926          */
927 #define CHECK_STRING(_x) if (inst->config->_x && !inst->config->_x[0]) \
928 do { \
929         WARN("rlm_sql (%s): " STRINGIFY(_x) " is empty.  Please delete it from the configuration", inst->name);\
930         inst->config->_x = NULL;\
931 } while (0)
932
933         CHECK_STRING(groupmemb_query);
934         CHECK_STRING(authorize_check_query);
935         CHECK_STRING(authorize_reply_query);
936         CHECK_STRING(authorize_group_check_query);
937         CHECK_STRING(authorize_group_reply_query);
938         CHECK_STRING(simul_count_query);
939         CHECK_STRING(simul_verify_query);
940         CHECK_STRING(connect_query);
941         CHECK_STRING(client_query);
942         if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
943                 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!", inst->name, inst->config->sql_driver_name);
944                 return -1;
945         }
946
947         /*
948          *      We need authorize_group_check_query or authorize_group_reply_query
949          *      if group_membership_query is set.
950          *
951          *      Or we need group_membership_query if authorize_group_check_query or
952          *      authorize_group_reply_query is set.
953          */
954         if (!inst->config->groupmemb_query) {
955                 if (inst->config->authorize_group_check_query) {
956                         WARN("rlm_sql (%s): Ignoring authorize_group_reply_query as group_membership_query "
957                              "is not configured", inst->name);
958                 }
959
960                 if (inst->config->authorize_group_reply_query) {
961                         WARN("rlm_sql (%s): Ignoring authorize_group_check_query as group_membership_query "
962                              "is not configured", inst->name);
963                 }
964
965                 if (!inst->config->read_groups) {
966                         WARN("rlm_sql (%s): Ignoring read_groups as group_membership_query "
967                              "is not configured", inst->name);
968                         inst->config->read_groups = false;
969                 }
970         } /* allow the group check / reply queries to be NULL */
971
972         /*
973          *      This will always exist, as cf_section_parse_init()
974          *      will create it if it doesn't exist.  However, the
975          *      "reference" config item won't exist in an auto-created
976          *      configuration.  So if that doesn't exist, we ignore
977          *      the whole subsection.
978          */
979         inst->config->accounting.cs = cf_section_sub_find(conf, "accounting");
980         inst->config->accounting.reference_cp = (cf_pair_find(inst->config->accounting.cs, "reference") != NULL);
981
982         inst->config->postauth.cs = cf_section_sub_find(conf, "post-auth");
983         inst->config->postauth.reference_cp = (cf_pair_find(inst->config->postauth.cs, "reference") != NULL);
984
985         /*
986          *      Cache the SQL-User-Name DICT_ATTR, so we can be slightly
987          *      more efficient about creating SQL-User-Name attributes.
988          */
989         inst->sql_user = dict_attrbyname("SQL-User-Name");
990         if (!inst->sql_user) {
991                 return -1;
992         }
993
994         /*
995          *      Export these methods, too.  This avoids RTDL_GLOBAL.
996          */
997         inst->sql_set_user              = sql_set_user;
998         inst->sql_escape_func           = sql_escape_func;
999         inst->sql_query                 = rlm_sql_query;
1000         inst->sql_select_query          = rlm_sql_select_query;
1001         inst->sql_fetch_row             = rlm_sql_fetch_row;
1002
1003         if (inst->module->mod_instantiate) {
1004                 CONF_SECTION *cs;
1005                 char const *name;
1006
1007                 name = strrchr(inst->config->sql_driver_name, '_');
1008                 if (!name) {
1009                         name = inst->config->sql_driver_name;
1010                 } else {
1011                         name++;
1012                 }
1013
1014                 cs = cf_section_sub_find(conf, name);
1015                 if (!cs) {
1016                         cs = cf_section_alloc(conf, name, NULL);
1017                         if (!cs) {
1018                                 return -1;
1019                         }
1020                 }
1021
1022                 /*
1023                  *      It's up to the driver to register a destructor
1024                  */
1025                 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
1026                         return -1;
1027                 }
1028         }
1029
1030         inst->ef = exfile_init(inst, 256, 30, true);
1031         if (!inst->ef) {
1032                 cf_log_err_cs(conf, "Failed creating log file context");
1033                 return -1;
1034         }
1035
1036         /*
1037          *      Initialise the connection pool for this instance
1038          */
1039         INFO("rlm_sql (%s): Attempting to connect to database \"%s\"", inst->name, inst->config->sql_db);
1040
1041         inst->pool = fr_connection_pool_module_init(inst->cs, inst, mod_conn_create, NULL, NULL);
1042         if (!inst->pool) return -1;
1043
1044         if (inst->config->do_clients) {
1045                 if (generate_sql_clients(inst) == -1){
1046                         ERROR("Failed to load clients from SQL");
1047                         return -1;
1048                 }
1049         }
1050
1051         return RLM_MODULE_OK;
1052 }
1053
1054 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) CC_HINT(nonnull);
1055 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
1056 {
1057         rlm_rcode_t rcode = RLM_MODULE_NOOP;
1058
1059         rlm_sql_t *inst = instance;
1060         rlm_sql_handle_t  *handle;
1061
1062         VALUE_PAIR *check_tmp = NULL;
1063         VALUE_PAIR *reply_tmp = NULL;
1064         VALUE_PAIR *user_profile = NULL;
1065
1066         bool    user_found = false;
1067
1068         sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
1069
1070         int     rows;
1071
1072         char    *expanded = NULL;
1073
1074         rad_assert(request->packet != NULL);
1075         rad_assert(request->reply != NULL);
1076
1077         if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query &&
1078             !inst->config->read_groups && !inst->config->read_profiles) {
1079                 RWDEBUG("No authorization checks configured, returning noop");
1080
1081                 return RLM_MODULE_NOOP;
1082         }
1083
1084         /*
1085          *      Set, escape, and check the user attr here
1086          */
1087         if (sql_set_user(inst, request, NULL) < 0) {
1088                 return RLM_MODULE_FAIL;
1089         }
1090
1091         /*
1092          *      Reserve a socket
1093          *
1094          *      After this point use goto error or goto release to cleanup socket temporary pairlists and
1095          *      temporary attributes.
1096          */
1097         handle = fr_connection_get(inst->pool);
1098         if (!handle) {
1099                 rcode = RLM_MODULE_FAIL;
1100                 goto error;
1101         }
1102
1103         /*
1104          *      Query the check table to find any conditions associated with this user/realm/whatever...
1105          */
1106         if (inst->config->authorize_check_query) {
1107                 vp_cursor_t cursor;
1108                 VALUE_PAIR *vp;
1109
1110                 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
1111                                  sql_escape_func, inst) < 0) {
1112                         REDEBUG("Error generating query");
1113                         rcode = RLM_MODULE_FAIL;
1114                         goto error;
1115                 }
1116
1117                 rows = sql_getvpdata(request, inst, request, &handle, &check_tmp, expanded);
1118                 TALLOC_FREE(expanded);
1119                 if (rows < 0) {
1120                         REDEBUG("Error getting check attributes");
1121                         rcode = RLM_MODULE_FAIL;
1122                         goto error;
1123                 }
1124
1125                 if (rows == 0) goto skipreply;  /* Don't need to free VPs we don't have */
1126
1127                 /*
1128                  *      Only do this if *some* check pairs were returned
1129                  */
1130                 RDEBUG2("User found in radcheck table");
1131                 user_found = true;
1132                 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
1133                         fr_pair_list_free(&check_tmp);
1134                         check_tmp = NULL;
1135                         goto skipreply;
1136                 }
1137
1138                 RDEBUG2("Conditional check items matched, merging assignment check items");
1139                 RINDENT();
1140                 for (vp = fr_cursor_init(&cursor, &check_tmp);
1141                      vp;
1142                      vp = fr_cursor_next(&cursor)) {
1143                         if (!fr_assignment_op[vp->op]) continue;
1144
1145                         rdebug_pair(2, request, vp, NULL);
1146                 }
1147                 REXDENT();
1148                 radius_pairmove(request, &request->config, check_tmp, true);
1149
1150                 rcode = RLM_MODULE_OK;
1151                 check_tmp = NULL;
1152         }
1153
1154         if (inst->config->authorize_reply_query) {
1155                 /*
1156                  *      Now get the reply pairs since the paircompare matched
1157                  */
1158                 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
1159                                  sql_escape_func, inst) < 0) {
1160                         REDEBUG("Error generating query");
1161                         rcode = RLM_MODULE_FAIL;
1162                         goto error;
1163                 }
1164
1165                 rows = sql_getvpdata(request->reply, inst, request, &handle, &reply_tmp, expanded);
1166                 TALLOC_FREE(expanded);
1167                 if (rows < 0) {
1168                         REDEBUG("SQL query error getting reply attributes");
1169                         rcode = RLM_MODULE_FAIL;
1170                         goto error;
1171                 }
1172
1173                 if (rows == 0) goto skipreply;
1174
1175                 do_fall_through = fall_through(reply_tmp);
1176
1177                 RDEBUG2("User found in radreply table, merging reply items");
1178                 user_found = true;
1179
1180                 rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);
1181
1182                 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1183
1184                 rcode = RLM_MODULE_OK;
1185                 reply_tmp = NULL;
1186         }
1187
1188         /*
1189          *      Neither group checks or profiles will work without
1190          *      a group membership query.
1191          */
1192         if (!inst->config->groupmemb_query) goto release;
1193
1194 skipreply:
1195         if ((do_fall_through == FALL_THROUGH_YES) ||
1196             (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1197                 rlm_rcode_t ret;
1198
1199                 RDEBUG3("... falling-through to group processing");
1200                 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1201                 switch (ret) {
1202                 /*
1203                  *      Nothing bad happened, continue...
1204                  */
1205                 case RLM_MODULE_UPDATED:
1206                         rcode = RLM_MODULE_UPDATED;
1207                         /* FALL-THROUGH */
1208                 case RLM_MODULE_OK:
1209                         if (rcode != RLM_MODULE_UPDATED) {
1210                                 rcode = RLM_MODULE_OK;
1211                         }
1212                         /* FALL-THROUGH */
1213                 case RLM_MODULE_NOOP:
1214                         user_found = true;
1215                         break;
1216
1217                 case RLM_MODULE_NOTFOUND:
1218                         break;
1219
1220                 default:
1221                         rcode = ret;
1222                         goto release;
1223                 }
1224         }
1225
1226         /*
1227          *      Repeat the above process with the default profile or User-Profile
1228          */
1229         if ((do_fall_through == FALL_THROUGH_YES) ||
1230             (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1231                 rlm_rcode_t ret;
1232
1233                 /*
1234                  *  Check for a default_profile or for a User-Profile.
1235                  */
1236                 RDEBUG3("... falling-through to profile processing");
1237                 user_profile = fr_pair_find_by_num(request->config, PW_USER_PROFILE, 0, TAG_ANY);
1238
1239                 char const *profile = user_profile ?
1240                                       user_profile->vp_strvalue :
1241                                       inst->config->default_profile;
1242
1243                 if (!profile || !*profile) {
1244                         goto release;
1245                 }
1246
1247                 RDEBUG2("Checking profile %s", profile);
1248
1249                 if (sql_set_user(inst, request, profile) < 0) {
1250                         REDEBUG("Error setting profile");
1251                         rcode = RLM_MODULE_FAIL;
1252                         goto error;
1253                 }
1254
1255                 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1256                 switch (ret) {
1257                 /*
1258                  *      Nothing bad happened, continue...
1259                  */
1260                 case RLM_MODULE_UPDATED:
1261                         rcode = RLM_MODULE_UPDATED;
1262                         /* FALL-THROUGH */
1263                 case RLM_MODULE_OK:
1264                         if (rcode != RLM_MODULE_UPDATED) {
1265                                 rcode = RLM_MODULE_OK;
1266                         }
1267                         /* FALL-THROUGH */
1268                 case RLM_MODULE_NOOP:
1269                         user_found = true;
1270                         break;
1271
1272                 case RLM_MODULE_NOTFOUND:
1273                         break;
1274
1275                 default:
1276                         rcode = ret;
1277                         goto release;
1278                 }
1279         }
1280
1281         /*
1282          *      At this point the key (user) hasn't be found in the check table, the reply table
1283          *      or the group mapping table, and there was no matching profile.
1284          */
1285 release:
1286         if (!user_found) {
1287                 rcode = RLM_MODULE_NOTFOUND;
1288         }
1289
1290         fr_connection_release(inst->pool, handle);
1291         sql_unset_user(inst, request);
1292
1293         return rcode;
1294
1295 error:
1296         fr_pair_list_free(&check_tmp);
1297         fr_pair_list_free(&reply_tmp);
1298         sql_unset_user(inst, request);
1299
1300         fr_connection_release(inst->pool, handle);
1301
1302         return rcode;
1303 }
1304
1305 /*
1306  *      Generic function for failing between a bunch of queries.
1307  *
1308  *      Uses the same principle as rlm_linelog, expanding the 'reference' config
1309  *      item using xlat to figure out what query it should execute.
1310  *
1311  *      If the reference matches multiple config items, and a query fails or
1312  *      doesn't update any rows, the next matching config item is used.
1313  *
1314  */
1315 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1316 {
1317         rlm_rcode_t             rcode = RLM_MODULE_OK;
1318
1319         rlm_sql_handle_t        *handle = NULL;
1320         int                     sql_ret;
1321         int                     numaffected = 0;
1322
1323         CONF_ITEM               *item;
1324         CONF_PAIR               *pair;
1325         char const              *attr = NULL;
1326         char const              *value;
1327
1328         char                    path[MAX_STRING_LEN];
1329         char                    *p = path;
1330         char                    *expanded = NULL;
1331
1332         rad_assert(section);
1333
1334         if (section->reference[0] != '.') {
1335                 *p++ = '.';
1336         }
1337
1338         if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1339                 rcode = RLM_MODULE_FAIL;
1340
1341                 goto finish;
1342         }
1343
1344         /*
1345          *      If we can't find a matching config item we do
1346          *      nothing so return RLM_MODULE_NOOP.
1347          */
1348         item = cf_reference_item(NULL, section->cs, path);
1349         if (!item) {
1350                 RWDEBUG("No such configuration item %s", path);
1351                 rcode = RLM_MODULE_NOOP;
1352
1353                 goto finish;
1354         }
1355         if (cf_item_is_section(item)){
1356                 RWDEBUG("Sections are not supported as references");
1357                 rcode = RLM_MODULE_NOOP;
1358
1359                 goto finish;
1360         }
1361
1362         pair = cf_item_to_pair(item);
1363         attr = cf_pair_attr(pair);
1364
1365         RDEBUG2("Using query template '%s'", attr);
1366
1367         handle = fr_connection_get(inst->pool);
1368         if (!handle) {
1369                 rcode = RLM_MODULE_FAIL;
1370
1371                 goto finish;
1372         }
1373
1374         sql_set_user(inst, request, NULL);
1375
1376         while (true) {
1377                 value = cf_pair_value(pair);
1378                 if (!value) {
1379                         RDEBUG("Ignoring null query");
1380                         rcode = RLM_MODULE_NOOP;
1381
1382                         goto finish;
1383                 }
1384
1385                 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1386                         rcode = RLM_MODULE_FAIL;
1387
1388                         goto finish;
1389                 }
1390
1391                 if (!*expanded) {
1392                         RDEBUG("Ignoring null query");
1393                         rcode = RLM_MODULE_NOOP;
1394                         talloc_free(expanded);
1395
1396                         goto finish;
1397                 }
1398
1399                 rlm_sql_query_log(inst, request, section, expanded);
1400
1401                 sql_ret = rlm_sql_query(inst, request, &handle, expanded);
1402                 TALLOC_FREE(expanded);
1403                 RDEBUG("SQL query returned: %s", fr_int2str(sql_rcode_table, sql_ret, "<INVALID>"));
1404
1405                 switch (sql_ret) {
1406                 /*
1407                  *  Query was a success! Now we just need to check if it did anything.
1408                  */
1409                 case RLM_SQL_OK:
1410                         break;
1411
1412                 /*
1413                  *  A general, unrecoverable server fault.
1414                  */
1415                 case RLM_SQL_ERROR:
1416                 /*
1417                  *  If we get RLM_SQL_RECONNECT it means all connections in the pool
1418                  *  were exhausted, and we couldn't create a new connection,
1419                  *  so we do not need to call fr_connection_release.
1420                  */
1421                 case RLM_SQL_RECONNECT:
1422                         rcode = RLM_MODULE_FAIL;
1423                         goto finish;
1424
1425                 /*
1426                  *  Query was invalid, this is a terminal error, but we still need
1427                  *  to do cleanup, as the connection handle is still valid.
1428                  */
1429                 case RLM_SQL_QUERY_INVALID:
1430                         rcode = RLM_MODULE_INVALID;
1431                         goto finish;
1432
1433                 /*
1434                  *  Driver found an error (like a unique key constraint violation)
1435                  *  that hinted it might be a good idea to try an alternative query.
1436                  */
1437                 case RLM_SQL_ALT_QUERY:
1438                         goto next;
1439                 }
1440                 rad_assert(handle);
1441
1442                 /*
1443                  *  We need to have updated something for the query to have been
1444                  *  counted as successful.
1445                  */
1446                 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1447                 (inst->module->sql_finish_query)(handle, inst->config);
1448                 RDEBUG("%i record(s) updated", numaffected);
1449
1450                 if (numaffected > 0) break;     /* A query succeeded, were done! */
1451         next:
1452                 /*
1453                  *  We assume all entries with the same name form a redundant
1454                  *  set of queries.
1455                  */
1456                 pair = cf_pair_find_next(section->cs, pair, attr);
1457
1458                 if (!pair) {
1459                         RDEBUG("No additional queries configured");
1460                         rcode = RLM_MODULE_NOOP;
1461
1462                         goto finish;
1463                 }
1464
1465                 RDEBUG("Trying next query...");
1466         }
1467
1468
1469 finish:
1470         talloc_free(expanded);
1471         fr_connection_release(inst->pool, handle);
1472         sql_unset_user(inst, request);
1473
1474         return rcode;
1475 }
1476
1477 #ifdef WITH_ACCOUNTING
1478
1479 /*
1480  *      Accounting: Insert or update session data in our sql table
1481  */
1482 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) CC_HINT(nonnull);
1483 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
1484 {
1485         rlm_sql_t *inst = instance;
1486
1487         if (inst->config->accounting.reference_cp) {
1488                 return acct_redundant(inst, request, &inst->config->accounting);
1489         }
1490
1491         return RLM_MODULE_NOOP;
1492 }
1493
1494 #endif
1495
1496 #ifdef WITH_SESSION_MGMT
1497 /*
1498  *      See if a user is already logged in. Sets request->simul_count to the
1499  *      current session count for this user.
1500  *
1501  *      Check twice. If on the first pass the user exceeds his
1502  *      max. number of logins, do a second pass and validate all
1503  *      logins by querying the terminal server (using eg. SNMP).
1504  */
1505 static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) CC_HINT(nonnull);
1506 static rlm_rcode_t mod_checksimul(void *instance, REQUEST * request)
1507 {
1508         rlm_rcode_t             rcode = RLM_MODULE_OK;
1509         rlm_sql_handle_t        *handle = NULL;
1510         rlm_sql_t               *inst = instance;
1511         rlm_sql_row_t           row;
1512         int                     check = 0;
1513         uint32_t                ipno = 0;
1514         char const              *call_num = NULL;
1515         VALUE_PAIR              *vp;
1516         int                     ret;
1517         uint32_t                nas_addr = 0;
1518         uint32_t                nas_port = 0;
1519
1520         char                    *expanded = NULL;
1521
1522         /* If simul_count_query is not defined, we don't do any checking */
1523         if (!inst->config->simul_count_query) {
1524                 RWDEBUG("Simultaneous-Use checking requires 'simul_count_query' to be configured");
1525                 return RLM_MODULE_NOOP;
1526         }
1527
1528         if ((!request->username) || (request->username->vp_length == 0)) {
1529                 REDEBUG("Zero Length username not permitted");
1530
1531                 return RLM_MODULE_INVALID;
1532         }
1533
1534         if (sql_set_user(inst, request, NULL) < 0) {
1535                 return RLM_MODULE_FAIL;
1536         }
1537
1538         if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1539                 sql_unset_user(inst, request);
1540                 return RLM_MODULE_FAIL;
1541         }
1542
1543         /* initialize the sql socket */
1544         handle = fr_connection_get(inst->pool);
1545         if (!handle) {
1546                 talloc_free(expanded);
1547                 sql_unset_user(inst, request);
1548                 return RLM_MODULE_FAIL;
1549         }
1550
1551         if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) {
1552                 rcode = RLM_MODULE_FAIL;
1553                 goto release;   /* handle may no longer be valid */
1554         }
1555
1556         ret = rlm_sql_fetch_row(inst, request, &handle);
1557         if (ret != 0) {
1558                 rcode = RLM_MODULE_FAIL;
1559                 goto finish;
1560         }
1561
1562         row = handle->row;
1563         if (!row) {
1564                 rcode = RLM_MODULE_FAIL;
1565                 goto finish;
1566         }
1567
1568         request->simul_count = atoi(row[0]);
1569
1570         (inst->module->sql_finish_select_query)(handle, inst->config);
1571         TALLOC_FREE(expanded);
1572
1573         if (request->simul_count < request->simul_max) {
1574                 rcode = RLM_MODULE_OK;
1575                 goto finish;
1576         }
1577
1578         /*
1579          *      Looks like too many sessions, so let's start verifying
1580          *      them, unless told to rely on count query only.
1581          */
1582         if (!inst->config->simul_verify_query) {
1583                 rcode = RLM_MODULE_OK;
1584
1585                 goto finish;
1586         }
1587
1588         if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1589                 rcode = RLM_MODULE_FAIL;
1590
1591                 goto finish;
1592         }
1593
1594         if (rlm_sql_select_query(inst, request, &handle, expanded) != RLM_SQL_OK) goto release;
1595
1596         /*
1597          *      Setup some stuff, like for MPP detection.
1598          */
1599         request->simul_count = 0;
1600
1601         if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1602                 ipno = vp->vp_ipaddr;
1603         }
1604
1605         if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1606                 call_num = vp->vp_strvalue;
1607         }
1608
1609         while (rlm_sql_fetch_row(inst, request, &handle) == RLM_SQL_OK) {
1610                 int num_rows;
1611
1612                 row = handle->row;
1613                 if (!row) {
1614                         break;
1615                 }
1616
1617                 num_rows = (inst->module->sql_num_fields)(handle, inst->config);
1618                 if (num_rows < 8) {
1619                         RDEBUG("Too few rows returned.  Please do not edit 'simul_verify_query'");
1620                         rcode = RLM_MODULE_FAIL;
1621
1622                         goto finish;
1623                 }
1624
1625                 if (!row[2]){
1626                         RDEBUG("Cannot zap stale entry. No username present in entry");
1627                         rcode = RLM_MODULE_FAIL;
1628
1629                         goto finish;
1630                 }
1631
1632                 if (!row[1]){
1633                         RDEBUG("Cannot zap stale entry. No session id in entry");
1634                         rcode = RLM_MODULE_FAIL;
1635
1636                         goto finish;
1637                 }
1638
1639                 if (row[3]) {
1640                         nas_addr = inet_addr(row[3]);
1641                 }
1642
1643                 if (row[4]) {
1644                         nas_port = atoi(row[4]);
1645                 }
1646
1647                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1648                 if (check == 0) {
1649                         /*
1650                          *      Stale record - zap it.
1651                          */
1652                         if (inst->config->delete_stale_sessions == true) {
1653                                 uint32_t framed_addr = 0;
1654                                 char proto = 0;
1655                                 int sess_time = 0;
1656
1657                                 if (row[5])
1658                                         framed_addr = inet_addr(row[5]);
1659                                 if (row[7]){
1660                                         if (strcmp(row[7], "PPP") == 0)
1661                                                 proto = 'P';
1662                                         else if (strcmp(row[7], "SLIP") == 0)
1663                                                 proto = 'S';
1664                                 }
1665                                 if ((num_rows > 8) && row[8])
1666                                         sess_time = atoi(row[8]);
1667                                 session_zap(request, nas_addr, nas_port,
1668                                             row[2], row[1], framed_addr,
1669                                             proto, sess_time);
1670                         }
1671                 }
1672                 else if (check == 1) {
1673                         /*
1674                          *      User is still logged in.
1675                          */
1676                         ++request->simul_count;
1677
1678                         /*
1679                          *      Does it look like a MPP attempt?
1680                          */
1681                         if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1682                                 request->simul_mpp = 2;
1683                         } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1684                                 request->simul_mpp = 2;
1685                         }
1686                 } else {
1687                         /*
1688                          *      Failed to check the terminal server for
1689                          *      duplicate logins: return an error.
1690                          */
1691                         REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1692
1693                         rcode = RLM_MODULE_FAIL;
1694                         goto finish;
1695                 }
1696         }
1697
1698 finish:
1699         (inst->module->sql_finish_select_query)(handle, inst->config);
1700 release:
1701         fr_connection_release(inst->pool, handle);
1702         talloc_free(expanded);
1703         sql_unset_user(inst, request);
1704
1705         /*
1706          *      The Auth module apparently looks at request->simul_count,
1707          *      not the return value of this module when deciding to deny
1708          *      a call for too many sessions.
1709          */
1710         return rcode;
1711 }
1712 #endif
1713
1714 /*
1715  *      Postauth: Write a record of the authentication attempt
1716  */
1717 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request) CC_HINT(nonnull);
1718 static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request)
1719 {
1720         rlm_sql_t *inst = instance;
1721
1722         if (inst->config->postauth.reference_cp) {
1723                 return acct_redundant(inst, request, &inst->config->postauth);
1724         }
1725
1726         return RLM_MODULE_NOOP;
1727 }
1728
1729 /*
1730  *      Execute postauth_query after authentication
1731  */
1732
1733
1734 /* globally exported name */
1735 extern module_t rlm_sql;
1736 module_t rlm_sql = {
1737         .magic          = RLM_MODULE_INIT,
1738         .name           = "sql",
1739         .type           = RLM_TYPE_THREAD_SAFE,
1740         .inst_size      = sizeof(rlm_sql_t),
1741         .config         = module_config,
1742         .bootstrap      = mod_bootstrap,
1743         .instantiate    = mod_instantiate,
1744         .detach         = mod_detach,
1745         .methods = {
1746                 [MOD_AUTHORIZE]         = mod_authorize,
1747 #ifdef WITH_ACCOUNTING
1748                 [MOD_ACCOUNTING]        = mod_accounting,
1749 #endif
1750 #ifdef WITH_SESSION_MGMT
1751                 [MOD_SESSION]           = mod_checksimul,
1752 #endif
1753                 [MOD_POST_AUTH]         = mod_post_auth
1754         },
1755 };