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