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