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