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