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