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