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