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