SQL could be used just for xlat, or ip pool.
[freeradius.git] / src / modules / rlm_sql / rlm_sql.c
1
2 /*
3  * rlm_sql.c            SQL Module
4  *              Main SQL module file. Most ICRADIUS code is located in sql.c
5  *
6  * Version:     $Id$
7  *
8  *   This program is free software; you can redistribute it and/or modify
9  *   it under the terms of the GNU General Public License as published by
10  *   the Free Software Foundation; either version 2 of the License, or
11  *   (at your option) any later version.
12  *
13  *   This program is distributed in the hope that it will be useful,
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *   GNU General Public License for more details.
17  *
18  *   You should have received a copy of the GNU General Public License
19  *   along with this program; if not, write to the Free Software
20  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  *
22  * Copyright 2012  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
28 #include <freeradius-devel/ident.h>
29 RCSID("$Id$")
30
31 #include <ctype.h>
32
33 #include <freeradius-devel/radiusd.h>
34 #include <freeradius-devel/modules.h>
35 #include <freeradius-devel/token.h>
36 #include <freeradius-devel/rad_assert.h>
37
38 #include <sys/stat.h>
39
40 #include "rlm_sql.h"
41
42 static char *allowed_chars = NULL;
43
44 static const CONF_PARSER section_config[] = {
45         { "reference",  PW_TYPE_STRING_PTR,
46           offsetof(rlm_sql_config_section_t, reference), NULL, ".query"},
47           
48         {"logfile", PW_TYPE_STRING_PTR,
49          offsetof(rlm_sql_config_section_t, logfile), NULL, NULL},
50         {NULL, -1, 0, NULL, NULL}
51 };
52
53 static const CONF_PARSER module_config[] = {
54         {"driver",PW_TYPE_STRING_PTR,
55          offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
56         {"server",PW_TYPE_STRING_PTR,
57          offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
58         {"port",PW_TYPE_STRING_PTR,
59          offsetof(SQL_CONFIG,sql_port), NULL, ""},
60         {"login", PW_TYPE_STRING_PTR,
61          offsetof(SQL_CONFIG,sql_login), NULL, ""},
62         {"password", PW_TYPE_STRING_PTR,
63          offsetof(SQL_CONFIG,sql_password), NULL, ""},
64         {"radius_db", PW_TYPE_STRING_PTR,
65          offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
66         {"filename", PW_TYPE_FILENAME, /* for sqlite */
67          offsetof(SQL_CONFIG,sql_file), NULL, NULL},
68         {"read_groups", PW_TYPE_BOOLEAN,
69          offsetof(SQL_CONFIG,read_groups), NULL, "yes"},
70         {"readclients", PW_TYPE_BOOLEAN,
71          offsetof(SQL_CONFIG,do_clients), NULL, "no"},
72         {"deletestalesessions", PW_TYPE_BOOLEAN,
73          offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"},
74         {"sql_user_name", PW_TYPE_STRING_PTR,
75          offsetof(SQL_CONFIG,query_user), NULL, ""},
76         {"logfile", PW_TYPE_STRING_PTR,
77          offsetof(SQL_CONFIG,logfile), NULL, NULL},
78         {"default_user_profile", PW_TYPE_STRING_PTR,
79          offsetof(SQL_CONFIG,default_profile), NULL, ""},
80         {"nas_query", PW_TYPE_STRING_PTR,
81          offsetof(SQL_CONFIG,nas_query), NULL, "SELECT id,nasname,shortname,type,secret FROM nas"},
82         {"authorize_check_query", PW_TYPE_STRING_PTR,
83          offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
84         {"authorize_reply_query", PW_TYPE_STRING_PTR,
85          offsetof(SQL_CONFIG,authorize_reply_query), NULL, NULL},
86         {"authorize_group_check_query", PW_TYPE_STRING_PTR,
87          offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
88         {"authorize_group_reply_query", PW_TYPE_STRING_PTR,
89          offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
90         {"group_membership_query", PW_TYPE_STRING_PTR,
91          offsetof(SQL_CONFIG,groupmemb_query), NULL, NULL},
92 #ifdef WITH_SESSION_MGMT
93         {"simul_count_query", PW_TYPE_STRING_PTR,
94          offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
95         {"simul_verify_query", PW_TYPE_STRING_PTR,
96          offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
97 #endif
98         {"safe-characters", PW_TYPE_STRING_PTR,
99          offsetof(SQL_CONFIG,allowed_chars), NULL,
100         "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
101
102         /*
103          *      This only works for a few drivers.
104          */
105         {"query_timeout", PW_TYPE_INTEGER,
106          offsetof(SQL_CONFIG,query_timeout), NULL, NULL},
107          
108         {NULL, -1, 0, NULL, NULL}
109 };
110
111 /*
112  *      Fall-Through checking function from rlm_files.c
113  */
114 static int fallthrough(VALUE_PAIR *vp)
115 {
116         VALUE_PAIR *tmp;
117         tmp = pairfind(vp, PW_FALL_THROUGH, 0);
118
119         return tmp ? tmp->vp_integer : 0;
120 }
121
122
123
124 /*
125  *      Yucky prototype.
126  */
127 static int generate_sql_clients(SQL_INST *inst);
128 static size_t sql_escape_func(char *out, size_t outlen, const char *in);
129
130 /*
131  *                      SQL xlat function
132  *
133  *  For selects the first value of the first column will be returned,
134  *  for inserts, updates and deletes the number of rows afftected will be
135  *  returned instead.
136  */
137 static int sql_xlat(void *instance, REQUEST *request,
138                     char *fmt, char *out, size_t freespace,
139                     UNUSED RADIUS_ESCAPE_STRING func)
140 {
141         SQLSOCK *sqlsocket;
142         SQL_ROW row;
143         SQL_INST *inst = instance;
144         char querystr[MAX_QUERY_LEN];
145         char sqlusername[MAX_STRING_LEN];
146         size_t ret = 0;
147
148         RDEBUG("sql_xlat");
149
150         /*
151          * Add SQL-User-Name attribute just in case it is needed
152          *  We could search the string fmt for SQL-User-Name to see if this is
153          *  needed or not
154          */
155         sql_set_user(inst, request, sqlusername, NULL);
156         /*
157          * Do an xlat on the provided string (nice recursive operation).
158          */
159         if (!radius_xlat(querystr, sizeof(querystr), fmt, request, sql_escape_func)) {
160                 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
161                        inst->config->xlat_name);
162                 return 0;
163         }
164
165         sqlsocket = sql_get_socket(inst);
166         if (sqlsocket == NULL)
167                 return 0;
168
169         rlm_sql_query_log(inst, request, NULL, querystr);
170
171         /*
172          *      If the query starts with any of the following prefixes,
173          *      then return the number of rows affected
174          */
175         if ((strncasecmp(querystr, "insert", 6) == 0) ||
176             (strncasecmp(querystr, "update", 6) == 0) ||
177             (strncasecmp(querystr, "delete", 6) == 0)) {
178                 int numaffected;
179                 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
180
181                 if (rlm_sql_query(&sqlsocket,inst,querystr)) {
182                         sql_release_socket(inst,sqlsocket);
183                         
184                         return 0;
185                 }
186                
187                 numaffected = (inst->module->sql_affected_rows)(sqlsocket,
188                                                                 inst->config);
189                 if (numaffected < 1) {
190                         RDEBUG("rlm_sql (%s): SQL query affected no rows",
191                                 inst->config->xlat_name);
192                 }
193
194                 /*
195                  *      Don't chop the returned number if freespace is
196                  *      too small.  This hack is necessary because
197                  *      some implementations of snprintf return the
198                  *      size of the written data, and others return
199                  *      the size of the data they *would* have written
200                  *      if the output buffer was large enough.
201                  */
202                 snprintf(buffer, sizeof(buffer), "%d", numaffected);
203                 ret = strlen(buffer);
204                 if (ret >= freespace){
205                         RDEBUG("rlm_sql (%s): Can't write result, insufficient string space",
206                                inst->config->xlat_name);
207                         (inst->module->sql_finish_query)(sqlsocket,
208                                                          inst->config);
209                         sql_release_socket(inst,sqlsocket);
210                         return 0;
211                 }
212                 
213                 memcpy(out, buffer, ret + 1); /* we did bounds checking above */
214
215                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
216                 sql_release_socket(inst,sqlsocket);
217                 return ret;
218         } /* else it's a SELECT statement */
219
220         if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
221                 sql_release_socket(inst,sqlsocket);
222                 return 0;
223         }
224
225         ret = rlm_sql_fetch_row(&sqlsocket, inst);
226         if (ret) {
227                 RDEBUG("SQL query did not succeed");
228                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
229                 sql_release_socket(inst,sqlsocket);
230                 return 0;
231         }
232
233         row = sqlsocket->row;
234         if (row == NULL) {
235                 RDEBUG("SQL query did not return any results");
236                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
237                 sql_release_socket(inst,sqlsocket);
238                 return 0;
239         }
240
241         if (row[0] == NULL){
242                 RDEBUG("Null value in first column");
243                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
244                 sql_release_socket(inst,sqlsocket);
245                 return 0;
246         }
247         ret = strlen(row[0]);
248         if (ret >= freespace){
249                 RDEBUG("Insufficient string space");
250                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
251                 sql_release_socket(inst,sqlsocket);
252                 return 0;
253         }
254
255         strlcpy(out,row[0],freespace);
256
257         RDEBUG("sql_xlat finished");
258
259         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
260         sql_release_socket(inst,sqlsocket);
261         return ret;
262 }
263
264 static int generate_sql_clients(SQL_INST *inst)
265 {
266         SQLSOCK *sqlsocket;
267         SQL_ROW row;
268         char querystr[MAX_QUERY_LEN];
269         RADCLIENT *c;
270         char *prefix_ptr = NULL;
271         unsigned int i = 0;
272         int numf = 0;
273
274         DEBUG("rlm_sql (%s): Processing generate_sql_clients",
275               inst->config->xlat_name);
276
277         /* NAS query isn't xlat'ed */
278         strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
279         DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
280               inst->config->xlat_name, querystr);
281
282         sqlsocket = sql_get_socket(inst);
283         if (sqlsocket == NULL)
284                 return -1;
285         if (rlm_sql_select_query(&sqlsocket,inst,querystr)){
286                 return -1;
287         }
288
289         while(rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
290                 i++;
291                 row = sqlsocket->row;
292                 if (row == NULL)
293                         break;
294                 /*
295                  *  The return data for each row MUST be in the following order:
296                  *
297                  *  0. Row ID (currently unused)
298                  *  1. Name (or IP address)
299                  *  2. Shortname
300                  *  3. Type
301                  *  4. Secret
302                  *  5. Virtual Server (optional)
303                  */
304                 if (!row[0]){
305                         radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
306                         continue;
307                 }
308                 if (!row[1]){
309                         radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
310                         continue;
311                 }
312                 if (!row[2]){
313                         radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
314                         continue;
315                 }
316                 if (!row[4]){
317                         radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
318                         continue;
319                 }
320
321                 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
322                         row[1],row[2],row[4]);
323
324                 c = rad_malloc(sizeof(*c));
325                 memset(c, 0, sizeof(*c));
326
327 #ifdef WITH_DYNAMIC_CLIENTS
328                 c->dynamic = 1;
329 #endif
330
331                 /*
332                  *      Look for prefixes
333                  */
334                 c->prefix = -1;
335                 prefix_ptr = strchr(row[1], '/');
336                 if (prefix_ptr) {
337                         c->prefix = atoi(prefix_ptr + 1);
338                         if ((c->prefix < 0) || (c->prefix > 128)) {
339                                 radlog(L_ERR, "rlm_sql (%s): Invalid Prefix value '%s' for IP.",
340                                        inst->config->xlat_name, prefix_ptr + 1);
341                                 free(c);
342                                 continue;
343                         }
344                         /* Replace '/' with '\0' */
345                         *prefix_ptr = '\0';
346                 }
347
348                 /*
349                  *      Always get the numeric representation of IP
350                  */
351                 if (ip_hton(row[1], AF_UNSPEC, &c->ipaddr) < 0) {
352                         radlog(L_CONS|L_ERR, "rlm_sql (%s): Failed to look up hostname %s: %s",
353                                inst->config->xlat_name,
354                                row[1], fr_strerror());
355                         free(c);
356                         continue;
357                 } else {
358                         char buffer[256];
359                         ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
360                         c->longname = strdup(buffer);
361                 }
362
363                 if (c->prefix < 0) switch (c->ipaddr.af) {
364                 case AF_INET:
365                         c->prefix = 32;
366                         break;
367                 case AF_INET6:
368                         c->prefix = 128;
369                         break;
370                 default:
371                         break;
372                 }
373
374                 /*
375                  *      Other values (secret, shortname, nastype, virtual_server)
376                  */
377                 c->secret = strdup(row[4]);
378                 c->shortname = strdup(row[2]);
379                 if(row[3] != NULL)
380                         c->nastype = strdup(row[3]);
381
382                 numf = (inst->module->sql_num_fields)(sqlsocket, inst->config);
383                 if ((numf > 5) && (row[5] != NULL) && *row[5]) c->server = strdup(row[5]);
384
385                 DEBUG("rlm_sql (%s): Adding client %s (%s, server=%s) to clients list",
386                       inst->config->xlat_name,
387                       c->longname,c->shortname, c->server ? c->server : "<none>");
388                 if (!client_add(NULL, c)) {
389                         sql_release_socket(inst, sqlsocket);
390                         DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list.  Maybe there's a duplicate?",
391                               inst->config->xlat_name,
392                               c->longname,c->shortname);
393                         client_free(c);
394                         return -1;
395                 }
396         }
397         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
398         sql_release_socket(inst, sqlsocket);
399
400         return 0;
401 }
402
403
404 /*
405  *      Translate the SQL queries.
406  */
407 static size_t sql_escape_func(char *out, size_t outlen, const char *in)
408 {
409         size_t len = 0;
410
411         while (in[0]) {
412                 /*
413                  *      Non-printable characters get replaced with their
414                  *      mime-encoded equivalents.
415                  */
416                 if ((in[0] < 32) ||
417                     strchr(allowed_chars, *in) == NULL) {
418                         /*
419                          *      Only 3 or less bytes available.
420                          */
421                         if (outlen <= 3) {
422                                 break;
423                         }
424
425                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
426                         in++;
427                         out += 3;
428                         outlen -= 3;
429                         len += 3;
430                         continue;
431                 }
432
433                 /*
434                  *      Only one byte left.
435                  */
436                 if (outlen <= 1) {
437                         break;
438                 }
439
440                 /*
441                  *      Allowed character.
442                  */
443                 *out = *in;
444                 out++;
445                 in++;
446                 outlen--;
447                 len++;
448         }
449         *out = '\0';
450         return len;
451 }
452
453 /*
454  *      Set the SQL user name.
455  *
456  *      We don't call the escape function here. The resulting string
457  *      will be escaped later in the queries xlat so we don't need to
458  *      escape it twice. (it will make things wrong if we have an
459  *      escape candidate character in the username)
460  */
461 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
462 {
463         VALUE_PAIR *vp=NULL;
464         char tmpuser[MAX_STRING_LEN];
465
466         tmpuser[0] = '\0';
467         sqlusername[0]= '\0';
468
469         /* Remove any user attr we added previously */
470         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
471
472         if (username != NULL) {
473                 strlcpy(tmpuser, username, sizeof(tmpuser));
474         } else if (strlen(inst->config->query_user)) {
475                 radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL);
476         } else {
477                 return 0;
478         }
479
480         strlcpy(sqlusername, tmpuser, MAX_STRING_LEN);
481         RDEBUG2("sql_set_user escaped user --> '%s'", sqlusername);
482         vp = radius_pairmake(request, &request->packet->vps,
483                              "SQL-User-Name", NULL, 0);
484         if (!vp) {
485                 radlog(L_ERR, "%s", fr_strerror());
486                 return -1;
487         }
488
489         strlcpy(vp->vp_strvalue, tmpuser, sizeof(vp->vp_strvalue));
490         vp->length = strlen(vp->vp_strvalue);
491
492         return 0;
493
494 }
495
496
497 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
498 {
499         SQL_GROUPLIST *last;
500
501         while(*group_list) {
502                 last = *group_list;
503                 *group_list = (*group_list)->next;
504                 free(last);
505         }
506 }
507
508
509 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
510 {
511         char    querystr[MAX_QUERY_LEN];
512         int     num_groups = 0;
513         SQL_ROW row;
514         SQL_GROUPLIST   *group_list_tmp;
515
516         /* NOTE: sql_set_user should have been run before calling this function */
517
518         group_list_tmp = *group_list = NULL;
519
520         if (!inst->config->groupmemb_query ||
521             (inst->config->groupmemb_query[0] == 0))
522                 return 0;
523
524         if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func)) {
525                 radlog_request(L_ERR, 0, request, "xlat \"%s\" failed.",
526                                inst->config->groupmemb_query);
527                 return -1;
528         }
529
530         if (rlm_sql_select_query(&sqlsocket, inst, querystr) < 0) {
531                 return -1;
532         }
533         while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
534                 row = sqlsocket->row;
535                 if (row == NULL)
536                         break;
537                 if (row[0] == NULL){
538                         RDEBUG("row[0] returned NULL");
539                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
540                         sql_grouplist_free(group_list);
541                         return -1;
542                 }
543                 if (*group_list == NULL) {
544                         *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
545                         group_list_tmp = *group_list;
546                 } else {
547                         rad_assert(group_list_tmp != NULL);
548                         group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
549                         group_list_tmp = group_list_tmp->next;
550                 }
551                 group_list_tmp->next = NULL;
552                 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
553         }
554
555         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
556
557         return num_groups;
558 }
559
560
561 /*
562  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
563  * with the group memberships reciding in sql
564  * The group membership query should only return one element which is the username. The returned
565  * username will then be checked with the passed check string.
566  */
567
568 static int sql_groupcmp(void *instance, REQUEST *request, VALUE_PAIR *request_vp, VALUE_PAIR *check,
569                         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
570 {
571         SQLSOCK *sqlsocket;
572         SQL_INST *inst = instance;
573         char sqlusername[MAX_STRING_LEN];
574         SQL_GROUPLIST *group_list, *group_list_tmp;
575
576         check_pairs = check_pairs;
577         reply_pairs = reply_pairs;
578         request_vp = request_vp;
579
580         RDEBUG("sql_groupcmp");
581         if (!check || !check->vp_strvalue || !check->length){
582                 RDEBUG("sql_groupcmp: Illegal group name");
583                 return 1;
584         }
585         if (!request){
586                 RDEBUG("sql_groupcmp: NULL request");
587                 return 1;
588         }
589         /*
590          * Set, escape, and check the user attr here
591          */
592         if (sql_set_user(inst, request, sqlusername, NULL) < 0)
593                 return 1;
594
595         /*
596          *      Get a socket for this lookup
597          */
598         sqlsocket = sql_get_socket(inst);
599         if (sqlsocket == NULL) {
600                 /* Remove the username we (maybe) added above */
601                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
602                 return 1;
603         }
604
605         /*
606          *      Get the list of groups this user is a member of
607          */
608         if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
609                 radlog_request(L_ERR, 0, request,
610                                "Error getting group membership");
611                 /* Remove the username we (maybe) added above */
612                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
613                 sql_release_socket(inst, sqlsocket);
614                 return 1;
615         }
616
617         for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
618                 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
619                         RDEBUG("sql_groupcmp finished: User is a member of group %s",
620                                check->vp_strvalue);
621                         /* Free the grouplist */
622                         sql_grouplist_free(&group_list);
623                         /* Remove the username we (maybe) added above */
624                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
625                         sql_release_socket(inst, sqlsocket);
626                         return 0;
627                 }
628         }
629
630         /* Free the grouplist */
631         sql_grouplist_free(&group_list);
632         /* Remove the username we (maybe) added above */
633         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
634         sql_release_socket(inst,sqlsocket);
635
636         RDEBUG("sql_groupcmp finished: User is NOT a member of group %s",
637                check->vp_strvalue);
638
639         return 1;
640 }
641
642
643
644 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
645 {
646         VALUE_PAIR *check_tmp = NULL;
647         VALUE_PAIR *reply_tmp = NULL;
648         SQL_GROUPLIST *group_list, *group_list_tmp;
649         VALUE_PAIR *sql_group = NULL;
650         char    querystr[MAX_QUERY_LEN];
651         int found = 0;
652         int rows;
653
654         /*
655          *      Get the list of groups this user is a member of
656          */
657         if (sql_get_grouplist(inst, sqlsocket, request, &group_list) < 0) {
658                 radlog_request(L_ERR, 0, request, "Error retrieving group list");
659                 return -1;
660         }
661
662         for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
663                 /*
664                  *      Add the Sql-Group attribute to the request list so we know
665                  *      which group we're retrieving attributes for
666                  */
667                 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
668                 if (!sql_group) {
669                         radlog_request(L_ERR, 0, request,
670                                        "Error creating Sql-Group attribute");
671                         sql_grouplist_free(&group_list);
672                         return -1;
673                 }
674                 pairadd(&request->packet->vps, sql_group);
675                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func)) {
676                         radlog_request(L_ERR, 0, request,
677                                        "Error generating query; rejecting user");
678                         /* Remove the grouup we added above */
679                         pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
680                         sql_grouplist_free(&group_list);
681                         return -1;
682                 }
683                 rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
684                 if (rows < 0) {
685                         radlog_request(L_ERR, 0, request, "Error retrieving check pairs for group %s",
686                                group_list_tmp->groupname);
687                         /* Remove the grouup we added above */
688                         pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
689                         pairfree(&check_tmp);
690                         sql_grouplist_free(&group_list);
691                         return -1;
692                 } else if (rows > 0) {
693                         /*
694                          *      Only do this if *some* check pairs were returned
695                          */
696                         if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
697                                 found = 1;
698                                 RDEBUG2("User found in group %s",
699                                         group_list_tmp->groupname);
700                                 /*
701                                  *      Now get the reply pairs since the paircompare matched
702                                  */
703                                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
704                                         radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
705                                         /* Remove the grouup we added above */
706                                         pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
707                                         pairfree(&check_tmp);
708                                         sql_grouplist_free(&group_list);
709                                         return -1;
710                                 }
711                                 if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
712                                         radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
713                                                group_list_tmp->groupname);
714                                         /* Remove the grouup we added above */
715                                         pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
716                                         pairfree(&check_tmp);
717                                         pairfree(&reply_tmp);
718                                         sql_grouplist_free(&group_list);
719                                         return -1;
720                                 }
721                                 *dofallthrough = fallthrough(reply_tmp);
722                                 pairxlatmove(request, &request->reply->vps, &reply_tmp);
723                                 pairxlatmove(request, &request->config_items, &check_tmp);
724                         }
725                 } else {
726                         /*
727                          *      rows == 0.  This is like having the username on a line
728                          *      in the user's file with no check vp's.  As such, we treat
729                          *      it as found and add the reply attributes, so that we
730                          *      match expected behavior
731                          */
732                         found = 1;
733                         RDEBUG2("User found in group %s",
734                                 group_list_tmp->groupname);
735                         /*
736                          *      Now get the reply pairs since the paircompare matched
737                          */
738                         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
739                                 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
740                                 /* Remove the grouup we added above */
741                                 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
742                                 pairfree(&check_tmp);
743                                 sql_grouplist_free(&group_list);
744                                 return -1;
745                         }
746                         if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
747                                 radlog_request(L_ERR, 0, request, "Error retrieving reply pairs for group %s",
748                                        group_list_tmp->groupname);
749                                 /* Remove the grouup we added above */
750                                 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
751                                 pairfree(&check_tmp);
752                                 pairfree(&reply_tmp);
753                                 sql_grouplist_free(&group_list);
754                                 return -1;
755                         }
756                         *dofallthrough = fallthrough(reply_tmp);
757                         pairxlatmove(request, &request->reply->vps, &reply_tmp);
758                         pairxlatmove(request, &request->config_items, &check_tmp);
759                 }
760
761                 /*
762                  * Delete the Sql-Group we added above
763                  * And clear out the pairlists
764                  */
765                 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0);
766                 pairfree(&check_tmp);
767                 pairfree(&reply_tmp);
768         }
769
770         sql_grouplist_free(&group_list);
771         return found;
772 }
773
774
775 static int rlm_sql_detach(void *instance)
776 {
777         SQL_INST *inst = instance;
778
779         paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
780
781         if (inst->config) {
782                 int i;
783
784                 if (inst->pool) sql_poolfree(inst);
785
786                 if (inst->config->xlat_name) {
787                         xlat_unregister(inst->config->xlat_name,(RAD_XLAT_FUNC)sql_xlat, instance);
788                         free(inst->config->xlat_name);
789                 }
790
791                 /*
792                  *      Free up dynamically allocated string pointers.
793                  */
794                 for (i = 0; module_config[i].name != NULL; i++) {
795                         char **p;
796                         if (module_config[i].type != PW_TYPE_STRING_PTR) {
797                                 continue;
798                         }
799
800                         /*
801                          *      Treat 'config' as an opaque array of bytes,
802                          *      and take the offset into it.  There's a
803                          *      (char*) pointer at that offset, and we want
804                          *      to point to it.
805                          */
806                         p = (char **) (((char *)inst->config) + module_config[i].offset);
807                         if (!*p) { /* nothing allocated */
808                                 continue;
809                         }
810                         free(*p);
811                         *p = NULL;
812                 }
813                 /*
814                  *      Catch multiple instances of the module.
815                  */
816                 if (allowed_chars == inst->config->allowed_chars) {
817                         allowed_chars = NULL;
818                 }
819                 free(inst->config);
820                 inst->config = NULL;
821         }
822
823         if (inst->handle) {
824 #if 0
825                 /*
826                  *      FIXME: Call the modules 'destroy' function?
827                  */
828                 lt_dlclose(inst->handle);       /* ignore any errors */
829 #endif
830         }
831         free(inst);
832
833         return 0;
834 }
835
836 static int parse_sub_section(CONF_SECTION *parent, 
837                              UNUSED SQL_INST *instance,
838                              rlm_sql_config_section_t *config,
839                              rlm_components_t comp)
840 {
841         CONF_SECTION *cs;
842
843         const char *name = section_type_value[comp].section;
844         
845         cs = cf_section_sub_find(parent, name);
846         if (!cs) return 1;      /* no section == OK */
847         
848         if (cf_section_parse(cs, config, section_config) < 0) {
849                 radlog(L_ERR, "Failed parsing configuration for section %s",
850                        name);
851                 
852                 return -1;
853         }
854                 
855         config->cs = cs;
856
857         return 1;
858 }
859
860 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
861 {
862         SQL_INST *inst;
863         const char *xlat_name;
864
865         inst = rad_malloc(sizeof(SQL_INST));
866         memset(inst, 0, sizeof(SQL_INST));
867
868         /*
869          *      Export these methods, too.  This avoids RTDL_GLOBAL.
870          */
871         inst->sql_set_user              = sql_set_user;
872         inst->sql_get_socket            = sql_get_socket;
873         inst->sql_release_socket        = sql_release_socket;
874         inst->sql_escape_func           = sql_escape_func;
875         inst->sql_query                 = rlm_sql_query;
876         inst->sql_select_query          = rlm_sql_select_query;
877         inst->sql_fetch_row             = rlm_sql_fetch_row;
878         
879         inst->config = rad_malloc(sizeof(SQL_CONFIG));
880         memset(inst->config, 0, sizeof(SQL_CONFIG));
881         inst->cs = conf;
882                 
883         /*
884          *      If the configuration parameters can't be parsed, then fail.
885          */
886         if ((cf_section_parse(conf, inst->config, module_config) < 0) ||
887             (parse_sub_section(conf, inst,
888                                &inst->config->accounting,
889                                RLM_COMPONENT_ACCT) < 0) ||
890             (parse_sub_section(conf, inst,
891                                &inst->config->postauth,
892                                RLM_COMPONENT_POST_AUTH) < 0)) {
893                 radlog(L_ERR, "Failed parsing configuration");
894                 goto error;
895         }
896
897         xlat_name = cf_section_name2(conf);
898         if (xlat_name == NULL) {
899                 xlat_name = cf_section_name1(conf);
900         } else {
901                 char *group_name;
902                 DICT_ATTR *dattr;
903                 ATTR_FLAGS flags;
904
905                 /*
906                  *      Allocate room for <instance>-SQL-Group
907                  */
908                 group_name = rad_malloc((strlen(xlat_name) + 1 + 11) * sizeof(char));
909                 sprintf(group_name,"%s-SQL-Group", xlat_name);
910                 DEBUG("rlm_sql Creating new attribute %s",group_name);
911
912                 memset(&flags, 0, sizeof(flags));
913                 dict_addattr(group_name, 0, PW_TYPE_STRING, -1, flags);
914                 dattr = dict_attrbyname(group_name);
915                 if (dattr == NULL){
916                         radlog(L_ERR, "rlm_sql: Failed to create attribute %s",
917                                group_name);
918                                
919                         free(group_name);
920
921                         goto error;
922                 }
923
924                 if (inst->config->groupmemb_query && 
925                     inst->config->groupmemb_query[0]) {
926                         DEBUG("rlm_sql: Registering sql_groupcmp for %s",
927                               group_name);
928                         paircompare_register(dattr->attr, PW_USER_NAME,
929                                              sql_groupcmp, inst);
930                 }
931
932                 free(group_name);
933         }
934         
935         rad_assert(xlat_name);
936
937         /*
938          *      Register the SQL xlat function
939          */
940         inst->config->xlat_name = strdup(xlat_name);
941         xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst);
942                 
943         /*
944          *      Sanity check for crazy people.
945          */
946         if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
947                 radlog(L_ERR, "\"%s\" is NOT an SQL driver!",
948                        inst->config->sql_driver);
949                 goto error;
950         }
951
952         /*
953          *      Load the appropriate driver for our database
954          */
955         inst->handle = lt_dlopenext(inst->config->sql_driver);
956         if (inst->handle == NULL) {
957                 radlog(L_ERR, "Could not link driver %s: %s",
958                        inst->config->sql_driver,
959                        lt_dlerror());
960                 radlog(L_ERR, "Make sure it (and all its dependent libraries!)"
961                        "are in the search path of your system's ld.");
962
963                 goto error;
964         }
965
966         inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle,
967                                                      inst->config->sql_driver);
968         if (!inst->module) {
969                 radlog(L_ERR, "Could not link symbol %s: %s",
970                        inst->config->sql_driver,
971                        lt_dlerror());
972
973                 goto error;
974         }
975
976         radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
977                inst->config->xlat_name, inst->config->sql_driver,
978                inst->module->name);
979
980         /*
981          *      Initialise the connection pool for this instance
982          */
983         radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
984                inst->config->xlat_name, inst->config->sql_login,
985                inst->config->sql_server, inst->config->sql_port,
986                inst->config->sql_db);
987                
988         if (sql_init_socketpool(inst) < 0)
989                 goto error;
990
991         if (inst->config->groupmemb_query && 
992             inst->config->groupmemb_query[0]) {
993                 paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
994         }
995
996         if (inst->config->do_clients) {
997                 if (generate_sql_clients(inst) == -1){
998                         radlog(L_ERR, "Failed to load clients from SQL.");
999                         
1000                         goto error;
1001                 }
1002         }
1003         allowed_chars = inst->config->allowed_chars;
1004
1005         *instance = inst;
1006
1007         return RLM_MODULE_OK;
1008         
1009         error:
1010         rlm_sql_detach(inst);
1011         
1012         return -1;
1013 }
1014
1015
1016 static int rlm_sql_authorize(void *instance, REQUEST * request)
1017 {
1018         VALUE_PAIR *check_tmp = NULL;
1019         VALUE_PAIR *reply_tmp = NULL;
1020         VALUE_PAIR *user_profile = NULL;
1021         int     found = 0;
1022         int     dofallthrough = 1;
1023         int     rows;
1024         SQLSOCK *sqlsocket;
1025         SQL_INST *inst = instance;
1026         char    querystr[MAX_QUERY_LEN];
1027         char    sqlusername[MAX_STRING_LEN];
1028         /*
1029          * the profile username is used as the sqlusername during
1030          * profile checking so that we don't overwrite the orignal
1031          * sqlusername string
1032          */
1033         char   profileusername[MAX_STRING_LEN];
1034
1035         /*
1036          * Set, escape, and check the user attr here
1037          */
1038         if (sql_set_user(inst, request, sqlusername, NULL) < 0)
1039                 return RLM_MODULE_FAIL;
1040
1041
1042         /*
1043          *  Reserve a socket
1044          */
1045         sqlsocket = sql_get_socket(inst);
1046         if (sqlsocket == NULL) {
1047                 /* Remove the username we (maybe) added above */
1048                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1049                 return RLM_MODULE_FAIL;
1050         }
1051
1052
1053         /*
1054          *  After this point, ALL 'return's MUST release the SQL socket!
1055          */
1056
1057         /*
1058          * Alright, start by getting the specific entry for the user
1059          */
1060         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) {
1061                 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1062                 sql_release_socket(inst, sqlsocket);
1063                 /* Remove the username we (maybe) added above */
1064                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1065                 return RLM_MODULE_FAIL;
1066         }
1067         rows = sql_getvpdata(inst, &sqlsocket, &check_tmp, querystr);
1068         if (rows < 0) {
1069                 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1070                 sql_release_socket(inst, sqlsocket);
1071                 /* Remove the username we (maybe) added above */
1072                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1073                 pairfree(&check_tmp);
1074                 return RLM_MODULE_FAIL;
1075         } else if (rows > 0) {
1076                 /*
1077                  *      Only do this if *some* check pairs were returned
1078                  */
1079                 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
1080                         found = 1;
1081                         RDEBUG2("User found in radcheck table");
1082
1083                         if (inst->config->authorize_reply_query &&
1084                             *inst->config->authorize_reply_query) {
1085
1086                         /*
1087                          *      Now get the reply pairs since the paircompare matched
1088                          */
1089                         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func)) {
1090                                 radlog_request(L_ERR, 0, request, "Error generating query; rejecting user");
1091                                 sql_release_socket(inst, sqlsocket);
1092                                 /* Remove the username we (maybe) added above */
1093                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1094                                 pairfree(&check_tmp);
1095                                 return RLM_MODULE_FAIL;
1096                         }
1097                         if (sql_getvpdata(inst, &sqlsocket, &reply_tmp, querystr) < 0) {
1098                                 radlog_request(L_ERR, 0, request, "SQL query error; rejecting user");
1099                                 sql_release_socket(inst, sqlsocket);
1100                                 /* Remove the username we (maybe) added above */
1101                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1102                                 pairfree(&check_tmp);
1103                                 pairfree(&reply_tmp);
1104                                 
1105                                 return RLM_MODULE_FAIL;
1106                         }
1107
1108                         if (!inst->config->read_groups)
1109                                 dofallthrough = fallthrough(reply_tmp);
1110                         pairxlatmove(request, &request->reply->vps, &reply_tmp);
1111                         }
1112                         pairxlatmove(request, &request->config_items, &check_tmp);
1113                 }
1114         }
1115
1116         /*
1117          *      Clear out the pairlists
1118          */
1119         pairfree(&check_tmp);
1120         pairfree(&reply_tmp);
1121
1122         /*
1123          *      dofallthrough is set to 1 by default so that if the user information
1124          *      is not found, we will still process groups.  If the user information,
1125          *      however, *is* found, Fall-Through must be set in order to process
1126          *      the groups as well
1127          */
1128         if (dofallthrough) {
1129                 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1130                 if (rows < 0) {
1131                         radlog_request(L_ERR, 0, request, "Error processing groups; rejecting user");
1132                         sql_release_socket(inst, sqlsocket);
1133                         /* Remove the username we (maybe) added above */
1134                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1135                         return RLM_MODULE_FAIL;
1136                 } else if (rows > 0) {
1137                         found = 1;
1138                 }
1139         }
1140
1141         /*
1142          *      repeat the above process with the default profile or User-Profile
1143          */
1144         if (dofallthrough) {
1145                 int profile_found = 0;
1146                 /*
1147                 * Check for a default_profile or for a User-Profile.
1148                 */
1149                 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0);
1150                 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1151                         char *profile = inst->config->default_profile;
1152
1153                         if (user_profile != NULL)
1154                                 profile = user_profile->vp_strvalue;
1155                         if (profile && strlen(profile)){
1156                                 RDEBUG("Checking profile %s", profile);
1157                                 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1158                                         radlog_request(L_ERR, 0, request, "Error setting profile; rejecting user");
1159                                         sql_release_socket(inst, sqlsocket);
1160                                         /* Remove the username we (maybe) added above */
1161                                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1162                                         return RLM_MODULE_FAIL;
1163                                 } else {
1164                                         profile_found = 1;
1165                                 }
1166                         }
1167                 }
1168
1169                 if (profile_found) {
1170                         rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1171                         if (rows < 0) {
1172                                 radlog_request(L_ERR, 0, request, "Error processing profile groups; rejecting user");
1173                                 sql_release_socket(inst, sqlsocket);
1174                                 /* Remove the username we (maybe) added above */
1175                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1176                                 return RLM_MODULE_FAIL;
1177                         } else if (rows > 0) {
1178                                 found = 1;
1179                         }
1180                 }
1181         }
1182
1183         /* Remove the username we (maybe) added above */
1184         pairdelete(&request->packet->vps, PW_SQL_USER_NAME, 0);
1185         sql_release_socket(inst, sqlsocket);
1186
1187         if (!found) {
1188                 RDEBUG("User %s not found", sqlusername);
1189                 return RLM_MODULE_NOTFOUND;
1190         } else {
1191                 return RLM_MODULE_OK;
1192         }
1193 }
1194
1195 /*
1196  *      Generic function for failing between a bunch of queries.
1197  *
1198  *      Uses the same principle as rlm_linelog, expanding the 'reference' config
1199  *      item using xlat to figure out what query it should execute.
1200  *
1201  *      If the reference matches multiple config items, and a query fails or
1202  *      doesn't update any rows, the next matching config item is used.
1203  *  
1204  */
1205 static int rlm_sql_redundant(SQL_INST *inst, REQUEST *request, 
1206                              rlm_sql_config_section_t *section)
1207 {
1208         int             ret = RLM_MODULE_OK;
1209
1210         SQLSOCK         *sqlsocket = NULL;
1211         int             sql_ret;
1212         int             numaffected = 0;
1213
1214         CONF_ITEM       *item;
1215         CONF_PAIR       *pair;
1216         const char      *attr = NULL;
1217         const char      *value;
1218
1219         char    path[MAX_STRING_LEN];
1220         char    querystr[MAX_QUERY_LEN];
1221         char    sqlusername[MAX_STRING_LEN];
1222         
1223         char    *p = path;
1224
1225         sql_set_user(inst, request, sqlusername, NULL);
1226         
1227         if (section->reference[0] != '.')
1228                 *p++ = '.';
1229         
1230         if (radius_xlat(p, (sizeof(path) - (p - path)) - 1,
1231                         section->reference, request, NULL) < 0)
1232                 return RLM_MODULE_FAIL;
1233
1234         item = cf_reference_item(NULL, section->cs, path);
1235         if (!item)
1236                 return RLM_MODULE_FAIL;
1237
1238         if (cf_item_is_section(item)){
1239                 radlog(L_ERR, "Sections are not supported as references");
1240                 
1241                 return RLM_MODULE_FAIL;
1242         }
1243         
1244         pair = cf_itemtopair(item);
1245         attr = cf_pair_attr(pair);
1246         
1247         RDEBUG2("Using query template '%s'", attr);
1248         
1249         sqlsocket = sql_get_socket(inst);
1250         if (sqlsocket == NULL)
1251                 return RLM_MODULE_FAIL;
1252
1253         while (TRUE) {
1254                 value = cf_pair_value(pair);
1255                 if (!value)
1256                         goto null_query;
1257                 
1258                 radius_xlat(querystr, sizeof(querystr), value, request,
1259                             sql_escape_func);
1260                 if (!*querystr)
1261                         goto null_query;
1262                 
1263                 rlm_sql_query_log(inst, request, section, querystr);
1264                 
1265                 sql_ret = rlm_sql_query(&sqlsocket, inst, querystr);    
1266                 if (sql_ret == SQL_DOWN)
1267                         return RLM_MODULE_FAIL;
1268                         
1269                 rad_assert(sqlsocket);
1270         
1271                 /* 
1272                  *  Assume all other errors are incidental, and just meant our
1273                  *  operation failed and its not a client or SQL syntax error.
1274                  */
1275                 if (sql_ret == 0) {
1276                         numaffected = (inst->module->sql_affected_rows)
1277                                         (sqlsocket, inst->config);
1278                         if (numaffected > 0)
1279                                 break;
1280                                 
1281                         RDEBUG("No records updated");
1282                 }
1283
1284                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1285                 
1286                 /*
1287                  *  We assume all entries with the same name form a redundant
1288                  *  set of queries.
1289                  */
1290                 pair = cf_pair_find_next(section->cs, pair, attr);
1291                 
1292                 if (!pair) {
1293                         RDEBUG("No additional queries configured");
1294                         
1295                         ret = RLM_MODULE_NOOP;
1296                         
1297                         goto release;
1298                 }
1299
1300                 RDEBUG("Trying next query...");
1301         }
1302         
1303         (inst->module->sql_finish_query)(sqlsocket, inst->config);
1304
1305         release:
1306         
1307         sql_release_socket(inst, sqlsocket);
1308
1309         return ret;
1310         
1311         null_query:
1312         
1313         radlog_request(L_DBG, 0, request, "Ignoring null query");
1314         
1315         sql_release_socket(inst, sqlsocket);
1316
1317         return RLM_MODULE_NOOP;
1318 }
1319
1320 #ifdef WITH_ACCOUNTING
1321
1322 /*
1323  *      Accounting: Insert or update session data in our sql table
1324  */
1325 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1326         SQL_INST *inst = instance;              
1327
1328         return rlm_sql_redundant(inst, request, &inst->config->accounting); 
1329 }
1330
1331 #endif
1332
1333 #ifdef WITH_SESSION_MGMT
1334 /*
1335  *        See if a user is already logged in. Sets request->simul_count to the
1336  *        current session count for this user.
1337  *
1338  *        Check twice. If on the first pass the user exceeds his
1339  *        max. number of logins, do a second pass and validate all
1340  *        logins by querying the terminal server (using eg. SNMP).
1341  */
1342
1343 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1344         SQLSOCK         *sqlsocket;
1345         SQL_INST        *inst = instance;
1346         SQL_ROW         row;
1347         char            querystr[MAX_QUERY_LEN];
1348         char            sqlusername[MAX_STRING_LEN];
1349         int             check = 0;
1350         uint32_t        ipno = 0;
1351         char            *call_num = NULL;
1352         VALUE_PAIR      *vp;
1353         int             ret;
1354         uint32_t        nas_addr = 0;
1355         int             nas_port = 0;
1356
1357         /* If simul_count_query is not defined, we don't do any checking */
1358         if (!inst->config->simul_count_query ||
1359             (inst->config->simul_count_query[0] == 0)) {
1360                 return RLM_MODULE_NOOP;
1361         }
1362
1363         if((request->username == NULL) || (request->username->length == 0)) {
1364                 radlog_request(L_ERR, 0, request,
1365                                            "Zero Length username not permitted\n");
1366                 return RLM_MODULE_INVALID;
1367         }
1368
1369
1370         if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1371                 return RLM_MODULE_FAIL;
1372
1373         radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func);
1374
1375         /* initialize the sql socket */
1376         sqlsocket = sql_get_socket(inst);
1377         if(sqlsocket == NULL)
1378                 return RLM_MODULE_FAIL;
1379
1380         if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1381                 sql_release_socket(inst, sqlsocket);
1382                 return RLM_MODULE_FAIL;
1383         }
1384
1385         ret = rlm_sql_fetch_row(&sqlsocket, inst);
1386         if (ret != 0) {
1387                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1388                 sql_release_socket(inst, sqlsocket);
1389                 return RLM_MODULE_FAIL;
1390         }
1391
1392         row = sqlsocket->row;
1393         if (row == NULL) {
1394                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1395                 sql_release_socket(inst, sqlsocket);
1396                 return RLM_MODULE_FAIL;
1397         }
1398
1399         request->simul_count = atoi(row[0]);
1400         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1401
1402         if(request->simul_count < request->simul_max) {
1403                 sql_release_socket(inst, sqlsocket);
1404                 return RLM_MODULE_OK;
1405         }
1406
1407         /*
1408          *      Looks like too many sessions, so let's start verifying
1409          *      them, unless told to rely on count query only.
1410          */
1411         if (!inst->config->simul_verify_query ||
1412             (inst->config->simul_verify_query[0] == '\0')) {
1413                 sql_release_socket(inst, sqlsocket);
1414                 return RLM_MODULE_OK;
1415         }
1416
1417         radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
1418         if(rlm_sql_select_query(&sqlsocket, inst, querystr)) {
1419                 sql_release_socket(inst, sqlsocket);
1420                 return RLM_MODULE_FAIL;
1421         }
1422
1423         /*
1424          *      Setup some stuff, like for MPP detection.
1425          */
1426         request->simul_count = 0;
1427
1428         if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0)) != NULL)
1429                 ipno = vp->vp_ipaddr;
1430         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0)) != NULL)
1431                 call_num = vp->vp_strvalue;
1432
1433
1434         while (rlm_sql_fetch_row(&sqlsocket, inst) == 0) {
1435                 row = sqlsocket->row;
1436                 if (row == NULL)
1437                         break;
1438                 if (!row[2]){
1439                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1440                         sql_release_socket(inst, sqlsocket);
1441                         RDEBUG("Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1442                         return RLM_MODULE_FAIL;
1443                 }
1444                 if (!row[1]){
1445                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1446                         sql_release_socket(inst, sqlsocket);
1447                         RDEBUG("Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1448                         return RLM_MODULE_FAIL;
1449                 }
1450                 if (row[3])
1451                         nas_addr = inet_addr(row[3]);
1452                 if (row[4])
1453                         nas_port = atoi(row[4]);
1454
1455                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1456
1457                 if (check == 0) {
1458                         /*
1459                          *      Stale record - zap it.
1460                          */
1461                         if (inst->config->deletestalesessions == TRUE) {
1462                                 uint32_t framed_addr = 0;
1463                                 char proto = 0;
1464                                 int sess_time = 0;
1465
1466                                 if (row[5])
1467                                         framed_addr = inet_addr(row[5]);
1468                                 if (row[7]){
1469                                         if (strcmp(row[7], "PPP") == 0)
1470                                                 proto = 'P';
1471                                         else if (strcmp(row[7], "SLIP") == 0)
1472                                                 proto = 'S';
1473                                 }
1474                                 if (row[8])
1475                                         sess_time = atoi(row[8]);
1476                                 session_zap(request, nas_addr, nas_port,
1477                                             row[2], row[1], framed_addr,
1478                                             proto, sess_time);
1479                         }
1480                 }
1481                 else if (check == 1) {
1482                         /*
1483                          *      User is still logged in.
1484                          */
1485                         ++request->simul_count;
1486
1487                         /*
1488                          *      Does it look like a MPP attempt?
1489                          */
1490                         if (row[5] && ipno && inet_addr(row[5]) == ipno)
1491                                 request->simul_mpp = 2;
1492                         else if (row[6] && call_num &&
1493                                 !strncmp(row[6],call_num,16))
1494                                 request->simul_mpp = 2;
1495                 }
1496                 else {
1497                         /*
1498                          *      Failed to check the terminal server for
1499                          *      duplicate logins: return an error.
1500                          */
1501                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1502                         sql_release_socket(inst, sqlsocket);
1503                         radlog_request(L_ERR, 0, request, "Failed to check the terminal server for user '%s'.", row[2]);
1504                         return RLM_MODULE_FAIL;
1505                 }
1506         }
1507
1508         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1509         sql_release_socket(inst, sqlsocket);
1510
1511         /*
1512          *      The Auth module apparently looks at request->simul_count,
1513          *      not the return value of this module when deciding to deny
1514          *      a call for too many sessions.
1515          */
1516         return RLM_MODULE_OK;
1517 }
1518 #endif
1519
1520 /*
1521  *      Postauth: Write a record of the authentication attempt
1522  */
1523 static int rlm_sql_postauth(void *instance, REQUEST * request) {
1524         SQL_INST *inst = instance;
1525         
1526         return rlm_sql_redundant(inst, request, &inst->config->postauth); 
1527 }
1528
1529 /*
1530  *      Execute postauth_query after authentication
1531  */
1532
1533
1534 /* globally exported name */
1535 module_t rlm_sql = {
1536         RLM_MODULE_INIT,
1537         "SQL",
1538         RLM_TYPE_THREAD_SAFE,   /* type: reserved */
1539         rlm_sql_instantiate,    /* instantiation */
1540         rlm_sql_detach,         /* detach */
1541         {
1542                 NULL,                   /* authentication */
1543                 rlm_sql_authorize,      /* authorization */
1544                 NULL,                   /* preaccounting */
1545 #ifdef WITH_ACCOUNTING
1546                 rlm_sql_accounting,     /* accounting */
1547 #else
1548                 NULL,
1549 #endif
1550 #ifdef WITH_SESSION_MGMT
1551                 rlm_sql_checksimul,     /* checksimul */
1552 #else
1553                 NULL,
1554 #endif
1555                 NULL,                   /* pre-proxy */
1556                 NULL,                   /* post-proxy */
1557                 rlm_sql_postauth        /* post-auth */
1558         },
1559 };