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