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