Added debug message about duplicate clients
[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 <sys/wait.h>
36
37 #include "rlm_sql.h"
38
39 static char *allowed_chars = NULL;
40
41 static const CONF_PARSER module_config[] = {
42         {"driver",PW_TYPE_STRING_PTR,
43          offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
44         {"server",PW_TYPE_STRING_PTR,
45          offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
46         {"port",PW_TYPE_STRING_PTR,
47          offsetof(SQL_CONFIG,sql_port), NULL, ""},
48         {"login", PW_TYPE_STRING_PTR,
49          offsetof(SQL_CONFIG,sql_login), NULL, ""},
50         {"password", PW_TYPE_STRING_PTR,
51          offsetof(SQL_CONFIG,sql_password), NULL, ""},
52         {"radius_db", PW_TYPE_STRING_PTR,
53          offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
54         {"read_groups", PW_TYPE_BOOLEAN,
55          offsetof(SQL_CONFIG,read_groups), NULL, "yes"},
56         {"sqltrace", PW_TYPE_BOOLEAN,
57          offsetof(SQL_CONFIG,sqltrace), NULL, "no"},
58         {"sqltracefile", PW_TYPE_STRING_PTR,
59          offsetof(SQL_CONFIG,tracefile), NULL, SQLTRACEFILE},
60         {"readclients", PW_TYPE_BOOLEAN,
61          offsetof(SQL_CONFIG,do_clients), NULL, "no"},
62         {"deletestalesessions", PW_TYPE_BOOLEAN,
63          offsetof(SQL_CONFIG,deletestalesessions), NULL, "yes"},
64         {"num_sql_socks", PW_TYPE_INTEGER,
65          offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"},
66         {"sql_user_name", PW_TYPE_STRING_PTR,
67          offsetof(SQL_CONFIG,query_user), NULL, ""},
68         {"default_user_profile", PW_TYPE_STRING_PTR,
69          offsetof(SQL_CONFIG,default_profile), NULL, ""},
70         {"nas_query", PW_TYPE_STRING_PTR,
71          offsetof(SQL_CONFIG,nas_query), NULL, "SELECT id,nasname,shortname,type,secret FROM nas"},
72         {"authorize_check_query", PW_TYPE_STRING_PTR,
73          offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
74         {"authorize_reply_query", PW_TYPE_STRING_PTR,
75          offsetof(SQL_CONFIG,authorize_reply_query), NULL, ""},
76         {"authorize_group_check_query", PW_TYPE_STRING_PTR,
77          offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
78         {"authorize_group_reply_query", PW_TYPE_STRING_PTR,
79          offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
80         {"accounting_onoff_query", PW_TYPE_STRING_PTR,
81          offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""},
82         {"accounting_update_query", PW_TYPE_STRING_PTR,
83          offsetof(SQL_CONFIG,accounting_update_query), NULL, ""},
84         {"accounting_update_query_alt", PW_TYPE_STRING_PTR,
85          offsetof(SQL_CONFIG,accounting_update_query_alt), NULL, ""},
86         {"accounting_start_query", PW_TYPE_STRING_PTR,
87          offsetof(SQL_CONFIG,accounting_start_query), NULL, ""},
88         {"accounting_start_query_alt", PW_TYPE_STRING_PTR,
89          offsetof(SQL_CONFIG,accounting_start_query_alt), NULL, ""},
90         {"accounting_stop_query", PW_TYPE_STRING_PTR,
91          offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""},
92         {"accounting_stop_query_alt", PW_TYPE_STRING_PTR,
93          offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""},
94         {"group_membership_query", PW_TYPE_STRING_PTR,
95          offsetof(SQL_CONFIG,groupmemb_query), NULL, ""},
96         {"connect_failure_retry_delay", PW_TYPE_INTEGER,
97          offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"},
98         {"simul_count_query", PW_TYPE_STRING_PTR,
99          offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
100         {"simul_verify_query", PW_TYPE_STRING_PTR,
101          offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
102         {"postauth_query", PW_TYPE_STRING_PTR,
103          offsetof(SQL_CONFIG,postauth_query), NULL, ""},
104         {"safe-characters", PW_TYPE_STRING_PTR,
105          offsetof(SQL_CONFIG,allowed_chars), NULL,
106         "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"},
107
108         {NULL, -1, 0, NULL, NULL}
109 };
110
111 /*
112  *      Fall-Through checking function from rlm_files.c
113  */
114 static int fallthrough(VALUE_PAIR *vp)
115 {
116         VALUE_PAIR *tmp;
117         tmp = pairfind(vp, PW_FALL_THROUGH);
118
119         return tmp ? tmp->vp_integer : 0;
120 }
121
122
123
124 /*
125  *      Yucky prototype.
126  */
127 static int generate_sql_clients(SQL_INST *inst);
128 static int sql_escape_func(char *out, int outlen, const char *in);
129
130 /*
131  *      sql xlat function. Right now only SELECTs are supported. Only
132  *      the first element of the SELECT result will be used.
133  */
134 static int sql_xlat(void *instance, REQUEST *request,
135                     char *fmt, char *out, size_t freespace,
136                     UNUSED RADIUS_ESCAPE_STRING func)
137 {
138         SQLSOCK *sqlsocket;
139         SQL_ROW row;
140         SQL_INST *inst = instance;
141         char querystr[MAX_QUERY_LEN];
142         char sqlusername[MAX_STRING_LEN];
143         int ret = 0;
144
145         DEBUG("rlm_sql (%s): - sql_xlat", inst->config->xlat_name);
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                        (char *)(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                 DEBUG("rlm_sql (%s): SQL query did not succeed",
177                       inst->config->xlat_name);
178                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
179                 sql_release_socket(inst,sqlsocket);
180                 return 0;
181         }
182
183         row = sqlsocket->row;
184         if (row == NULL) {
185                 DEBUG("rlm_sql (%s): SQL query did not return any results",
186                       inst->config->xlat_name);
187                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
188                 sql_release_socket(inst,sqlsocket);
189                 return 0;
190         }
191
192         if (row[0] == NULL){
193                 DEBUG("rlm_sql (%s): row[0] returned NULL",
194                       inst->config->xlat_name);
195                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
196                 sql_release_socket(inst,sqlsocket);
197                 return 0;
198         }
199         ret = strlen(row[0]);
200         if (ret >= freespace){
201                 DEBUG("rlm_sql (%s): sql_xlat:: Insufficient string space",
202                       inst->config->xlat_name);
203                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
204                 sql_release_socket(inst,sqlsocket);
205                 return 0;
206         }
207
208         strlcpy(out,row[0],freespace);
209
210         DEBUG("rlm_sql (%s): - sql_xlat finished",
211               inst->config->xlat_name);
212
213         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
214         sql_release_socket(inst,sqlsocket);
215         return ret;
216 }
217
218 static int generate_sql_clients(SQL_INST *inst)
219 {
220         SQLSOCK *sqlsocket;
221         SQL_ROW row;
222         char querystr[MAX_QUERY_LEN];
223         RADCLIENT *c;
224         char *prefix_ptr = NULL;
225         unsigned int i = 0;
226
227         DEBUG("rlm_sql (%s): Processing generate_sql_clients",
228               inst->config->xlat_name);
229
230         /* NAS query isn't xlat'ed */
231         strlcpy(querystr, inst->config->nas_query, sizeof(querystr));
232         DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
233               inst->config->xlat_name, querystr);
234
235         sqlsocket = sql_get_socket(inst);
236         if (sqlsocket == NULL)
237                 return -1;
238         if (rlm_sql_select_query(sqlsocket,inst,querystr)){
239                 radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
240                         inst->config->xlat_name,querystr,
241                         (char *)(inst->module->sql_error)(sqlsocket, inst->config));
242                 sql_release_socket(inst,sqlsocket);
243                 return -1;
244         }
245
246         while(rlm_sql_fetch_row(sqlsocket, inst) == 0) {
247                 i++;
248                 row = sqlsocket->row;
249                 if (row == NULL)
250                         break;
251         /*
252          *  The return data for each row MUST be in the following order:
253          *
254          *  0. Row ID (currently unused)
255          *  1. Name (or IP address)
256          *  2. Shortname
257          *  3. Type
258          *  4. Secret
259          */
260                 if (!row[0]){
261                         radlog(L_ERR, "rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
262                         continue;
263                 }
264                 if (!row[1]){
265                         radlog(L_ERR, "rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
266                         continue;
267                 }
268                 if (!row[2]){
269                         radlog(L_ERR, "rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
270                         continue;
271                 }
272                 if (!row[4]){
273                         radlog(L_ERR, "rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
274                         continue;
275                 }
276
277                 DEBUG("rlm_sql (%s): Read entry nasname=%s,shortname=%s,secret=%s",inst->config->xlat_name,
278                         row[1],row[2],row[4]);
279
280                 c = rad_malloc(sizeof(*c));
281                 memset(c, 0, sizeof(*c));
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], librad_errstr);
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)
328                  */
329                 c->secret = (u_char *)strdup(row[4]);
330                 c->shortname = strdup(row[2]);
331                 if(row[3] != NULL)
332                         c->nastype = strdup(row[3]);
333
334                 DEBUG("rlm_sql (%s): Adding client %s (%s) to clients list",
335                       inst->config->xlat_name,
336                       c->longname,c->shortname);
337                 if (!client_add(mainconfig.clients, c)) {
338                         DEBUG("rlm_sql (%s): Failed to add client %s (%s) to clients list.  Maybe there's a duplicate?",
339                               inst->config->xlat_name,
340                               c->longname,c->shortname);
341                         client_free(c);
342                         return -1;
343                 }
344         }
345         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
346         sql_release_socket(inst, sqlsocket);
347
348         return 0;
349 }
350
351
352 /*
353  *      Translate the SQL queries.
354  */
355 static int sql_escape_func(char *out, int outlen, const char *in)
356 {
357         int len = 0;
358
359         while (in[0]) {
360                 /*
361                  *      Non-printable characters get replaced with their
362                  *      mime-encoded equivalents.
363                  */
364                 if ((in[0] < 32) ||
365                     strchr(allowed_chars, *in) == NULL) {
366                         /*
367                          *      Only 3 or less bytes available.
368                          */
369                         if (outlen <= 3) {
370                                 break;
371                         }
372
373                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
374                         in++;
375                         out += 3;
376                         outlen -= 3;
377                         len += 3;
378                         continue;
379                 }
380
381                 /*
382                  *      Only one byte left.
383                  */
384                 if (outlen <= 1) {
385                         break;
386                 }
387
388                 /*
389                  *      Allowed character.
390                  */
391                 *out = *in;
392                 out++;
393                 in++;
394                 outlen--;
395                 len++;
396         }
397         *out = '\0';
398         return len;
399 }
400
401 /*
402  *      Set the SQL user name.
403  *
404  *      We don't call the escape function here. The resulting string
405  *      will be escaped later in the queries xlat so we don't need to
406  *      escape it twice. (it will make things wrong if we have an
407  *      escape candidate character in the username)
408  */
409 int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username)
410 {
411         VALUE_PAIR *vp=NULL;
412         char tmpuser[MAX_STRING_LEN];
413
414         tmpuser[0] = '\0';
415         sqlusername[0]= '\0';
416
417         /* Remove any user attr we added previously */
418         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
419
420         if (username != NULL) {
421                 strlcpy(tmpuser, username, sizeof(tmpuser));
422         } else if (strlen(inst->config->query_user)) {
423                 radius_xlat(tmpuser, sizeof(tmpuser), inst->config->query_user, request, NULL);
424         } else {
425                 return 0;
426         }
427
428         strlcpy(sqlusername, tmpuser, MAX_STRING_LEN);
429         DEBUG2("rlm_sql (%s): sql_set_user escaped user --> '%s'",
430                        inst->config->xlat_name, sqlusername);
431         vp = pairmake("SQL-User-Name", sqlusername, 0);
432         if (vp == NULL) {
433                 radlog(L_ERR, "%s", librad_errstr);
434                 return -1;
435         }
436
437         pairadd(&request->packet->vps, vp);
438         return 0;
439
440 }
441
442
443 static void sql_grouplist_free (SQL_GROUPLIST **group_list)
444 {
445         SQL_GROUPLIST *last;
446
447         while(*group_list) {
448                 last = *group_list;
449                 *group_list = (*group_list)->next;
450                 free(last);
451         }
452 }
453
454
455 static int sql_get_grouplist (SQL_INST *inst, SQLSOCK *sqlsocket, REQUEST *request, SQL_GROUPLIST **group_list)
456 {
457         char    querystr[MAX_QUERY_LEN];
458         int     num_groups = 0;
459         SQL_ROW row;
460         SQL_GROUPLIST   *group_list_tmp;
461
462         /* NOTE: sql_set_user should have been run before calling this function */
463
464         group_list_tmp = *group_list = NULL;
465
466         if (inst->config->groupmemb_query[0] == 0)
467                 return 1;
468
469         if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func)) {
470                 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
471                         inst->config->xlat_name);
472                 return -1;
473         }
474
475         if (rlm_sql_select_query(sqlsocket, inst, querystr) < 0) {
476                 radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
477                         inst->config->xlat_name,querystr,
478                         (char *)(inst->module->sql_error)(sqlsocket,inst->config));
479                 return -1;
480         }
481         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
482                 row = sqlsocket->row;
483                 if (row == NULL)
484                         break;
485                 if (row[0] == NULL){
486                         DEBUG("rlm_sql (%s): row[0] returned NULL",
487                                 inst->config->xlat_name);
488                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
489                         sql_grouplist_free(group_list);
490                         return -1;
491                 }
492                 if (*group_list == NULL) {
493                         *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
494                         group_list_tmp = *group_list;
495                 } else {
496                         group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
497                         group_list_tmp = group_list_tmp->next;
498                 }
499                 group_list_tmp->next = NULL;
500                 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
501         }
502
503         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
504
505         return num_groups;
506 }
507
508
509 /*
510  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
511  * with the group memberships reciding in sql
512  * The group membership query should only return one element which is the username. The returned
513  * username will then be checked with the passed check string.
514  */
515
516 static int sql_groupcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
517                         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
518 {
519         SQLSOCK *sqlsocket;
520         SQL_INST *inst = instance;
521         char sqlusername[MAX_STRING_LEN];
522         SQL_GROUPLIST *group_list, *group_list_tmp;
523
524         check_pairs = check_pairs;
525         reply_pairs = reply_pairs;
526         request = request;
527
528         DEBUG("rlm_sql (%s): - sql_groupcmp", inst->config->xlat_name);
529         if (!check || !check->vp_strvalue || !check->length){
530                 DEBUG("rlm_sql (%s): sql_groupcmp: Illegal group name",
531                       inst->config->xlat_name);
532                 return 1;
533         }
534         if (req == NULL){
535                 DEBUG("rlm_sql (%s): sql_groupcmp: NULL request",
536                       inst->config->xlat_name);
537                 return 1;
538         }
539         /*
540          * Set, escape, and check the user attr here
541          */
542         if (sql_set_user(inst, req, sqlusername, NULL) < 0)
543                 return 1;
544
545         /*
546          *      Get a socket for this lookup
547          */
548         sqlsocket = sql_get_socket(inst);
549         if (sqlsocket == NULL) {
550                 /* Remove the username we (maybe) added above */
551                 pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
552                 return 1;
553         }
554
555         /*
556          *      Get the list of groups this user is a member of
557          */
558         if (sql_get_grouplist(inst, sqlsocket, req, &group_list)) {
559                 radlog(L_ERR, "rlm_sql (%s): Error getting group membership",
560                        inst->config->xlat_name);
561                 /* Remove the username we (maybe) added above */
562                 pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
563                 sql_release_socket(inst, sqlsocket);
564                 return 1;
565         }
566
567         for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
568                 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
569                         DEBUG("rlm_sql (%s): - sql_groupcmp finished: User is a member of group %s",
570                               inst->config->xlat_name,
571                               (char *)check->vp_strvalue);
572                         /* Free the grouplist */
573                         sql_grouplist_free(&group_list);
574                         /* Remove the username we (maybe) added above */
575                         pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
576                         sql_release_socket(inst, sqlsocket);
577                         return 0;
578                 }
579         }
580
581         /* Free the grouplist */
582         sql_grouplist_free(&group_list);
583         /* Remove the username we (maybe) added above */
584         pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
585         sql_release_socket(inst,sqlsocket);
586
587         DEBUG("rlm_sql (%s): - sql_groupcmp finished: User is NOT a member of group %s",
588               inst->config->xlat_name, (char *)check->vp_strvalue);
589
590         return 1;
591 }
592
593
594
595 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
596 {
597         VALUE_PAIR *check_tmp = NULL;
598         VALUE_PAIR *reply_tmp = NULL;
599         SQL_GROUPLIST *group_list, *group_list_tmp;
600         VALUE_PAIR *sql_group = NULL;
601         char    querystr[MAX_QUERY_LEN];
602         int found = 0;
603         int rows;
604
605         /*
606          *      Get the list of groups this user is a member of
607          */
608         if (sql_get_grouplist(inst, sqlsocket, request, &group_list)) {
609                 radlog(L_ERR, "rlm_sql (%s): Error retrieving group list",
610                        inst->config->xlat_name);
611                 return -1;
612         }
613
614         for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
615                 /*
616                  *      Add the Sql-Group attribute to the request list so we know
617                  *      which group we're retrieving attributes for
618                  */
619                 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
620                 if (!sql_group) {
621                         radlog(L_ERR, "rlm_sql (%s): Error creating Sql-Group attribute",
622                                inst->config->xlat_name);
623                         return -1;
624                 }
625                 pairadd(&request->packet->vps, sql_group);
626                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func)) {
627                         radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
628                                inst->config->xlat_name);
629                         /* Remove the grouup we added above */
630                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
631                         return -1;
632                 }
633                 rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr);
634                 if (rows < 0) {
635                         radlog(L_ERR, "rlm_sql (%s): Error retrieving check pairs for group %s",
636                                inst->config->xlat_name, group_list_tmp->groupname);
637                         /* Remove the grouup we added above */
638                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
639                         pairfree(&check_tmp);
640                         return -1;
641                 } else if (rows > 0) {
642                         /*
643                          *      Only do this if *some* check pairs were returned
644                          */
645                         if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
646                                 found = 1;
647                                 DEBUG2("rlm_sql (%s): User found in group %s",
648                                         inst->config->xlat_name, group_list_tmp->groupname);
649                                 /*
650                                  *      Now get the reply pairs since the paircompare matched
651                                  */
652                                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
653                                         radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
654                                                inst->config->xlat_name);
655                                         /* Remove the grouup we added above */
656                                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
657                                         pairfree(&check_tmp);
658                                         return -1;
659                                 }
660                                 if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) {
661                                         radlog(L_ERR, "rlm_sql (%s): Error retrieving reply pairs for group %s",
662                                                inst->config->xlat_name, group_list_tmp->groupname);
663                                         /* Remove the grouup we added above */
664                                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
665                                         pairfree(&check_tmp);
666                                         pairfree(&reply_tmp);
667                                         return -1;
668                                 }
669                                 *dofallthrough = fallthrough(reply_tmp);
670                                 pairxlatmove(request, &request->reply->vps, &reply_tmp);
671                                 pairxlatmove(request, &request->config_items, &check_tmp);
672                         }
673                 } else {
674                         /*
675                          *      rows == 0.  This is like having the username on a line
676                          *      in the user's file with no check vp's.  As such, we treat
677                          *      it as found and add the reply attributes, so that we
678                          *      match expected behavior
679                          */
680                         found = 1;
681                         DEBUG2("rlm_sql (%s): User found in group %s",
682                                 inst->config->xlat_name, group_list_tmp->groupname);
683                         /*
684                          *      Now get the reply pairs since the paircompare matched
685                          */
686                         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
687                                 radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
688                                        inst->config->xlat_name);
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(L_ERR, "rlm_sql (%s): Error retrieving reply pairs for group %s",
696                                        inst->config->xlat_name, 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          * If the configuration parameters can't be parsed, then
792          * fail.
793          */
794         if (cf_section_parse(conf, inst->config, module_config) < 0) {
795                 rlm_sql_detach(inst);
796                 return -1;
797         }
798
799         xlat_name = cf_section_name2(conf);
800         if (xlat_name == NULL)
801                 xlat_name = cf_section_name1(conf);
802         if (xlat_name){
803                 inst->config->xlat_name = strdup(xlat_name);
804                 xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst);
805         }
806
807         if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
808                 radlog(L_ERR | L_CONS, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d",
809                        inst->config->xlat_name, MAX_SQL_SOCKS);
810                 rlm_sql_detach(inst);
811                 return -1;
812         }
813
814         /*
815          *      Sanity check for crazy people.
816          */
817         if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
818                 radlog(L_ERR, "rlm_sql (%s): \"%s\" is NOT an SQL driver!",
819                        inst->config->xlat_name, inst->config->sql_driver);
820                 rlm_sql_detach(inst);
821                 return -1;
822         }
823
824         inst->handle = lt_dlopenext(inst->config->sql_driver);
825         if (inst->handle == NULL) {
826                 radlog(L_ERR, "rlm_sql (%s): Could not link driver %s: %s",
827                        inst->config->xlat_name, inst->config->sql_driver,
828                        lt_dlerror());
829                 radlog(L_ERR, "rlm_sql (%s): Make sure it (and all its dependent libraries!) are in the search path of your system's ld.",
830                        inst->config->xlat_name);
831                 rlm_sql_detach(inst);
832                 return -1;
833         }
834
835         inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle, inst->config->sql_driver);
836         if (!inst->module) {
837                 radlog(L_ERR, "rlm_sql (%s): Could not link symbol %s: %s",
838                        inst->config->xlat_name, inst->config->sql_driver,
839                        lt_dlerror());
840                 rlm_sql_detach(inst);
841                 return -1;
842         }
843
844         radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
845                inst->config->xlat_name, inst->config->sql_driver,
846                inst->module->name);
847         radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
848                inst->config->xlat_name, inst->config->sql_login,
849                inst->config->sql_server, inst->config->sql_port,
850                inst->config->sql_db);
851
852         if (sql_init_socketpool(inst) < 0) {
853                 rlm_sql_detach(inst);
854                 return -1;
855         }
856         paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
857
858         if (inst->config->do_clients){
859                 if (generate_sql_clients(inst) == -1){
860                         radlog(L_ERR, "rlm_sql (%s): generate_sql_clients() returned error",inst->config->xlat_name);
861                         rlm_sql_detach(inst);
862                         return -1;
863                 }
864         }
865         allowed_chars = inst->config->allowed_chars;
866
867         *instance = inst;
868
869         return RLM_MODULE_OK;
870 }
871
872
873 static int rlm_sql_authorize(void *instance, REQUEST * request)
874 {
875         VALUE_PAIR *check_tmp = NULL;
876         VALUE_PAIR *reply_tmp = NULL;
877         VALUE_PAIR *user_profile = NULL;
878         int     found = 0;
879         int     dofallthrough = 1;
880         int     rows;
881         SQLSOCK *sqlsocket;
882         SQL_INST *inst = instance;
883         char    querystr[MAX_QUERY_LEN];
884         char    sqlusername[MAX_STRING_LEN];
885         /*
886          * the profile username is used as the sqlusername during
887          * profile checking so that we don't overwrite the orignal
888          * sqlusername string
889          */
890         char   profileusername[MAX_STRING_LEN];
891
892         /*
893          * Set, escape, and check the user attr here
894          */
895         if (sql_set_user(inst, request, sqlusername, NULL) < 0)
896                 return RLM_MODULE_FAIL;
897
898
899         /*
900          * reserve a socket
901          */
902         sqlsocket = sql_get_socket(inst);
903         if (sqlsocket == NULL) {
904                 /* Remove the username we (maybe) added above */
905                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
906                 return RLM_MODULE_FAIL;
907         }
908
909
910         /*
911          *  After this point, ALL 'return's MUST release the SQL socket!
912          */
913
914         /*
915          * Alright, start by getting the specific entry for the user
916          */
917         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) {
918                 radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
919                        inst->config->xlat_name);
920                 sql_release_socket(inst, sqlsocket);
921                 /* Remove the username we (maybe) added above */
922                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
923                 return RLM_MODULE_FAIL;
924         }
925         rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr);
926         if (rows < 0) {
927                 radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user",
928                        inst->config->xlat_name);
929                 sql_release_socket(inst, sqlsocket);
930                 /* Remove the username we (maybe) added above */
931                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
932                 pairfree(&check_tmp);
933                 return RLM_MODULE_FAIL;
934         } else if (rows > 0) {
935                 /*
936                  *      Only do this if *some* check pairs were returned
937                  */
938                 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
939                         found = 1;
940                         DEBUG2("rlm_sql (%s): User found in radcheck table", inst->config->xlat_name);
941                         /*
942                          *      Now get the reply pairs since the paircompare matched
943                          */
944                         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func)) {
945                                 radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
946                                        inst->config->xlat_name);
947                                 sql_release_socket(inst, sqlsocket);
948                                 /* Remove the username we (maybe) added above */
949                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
950                                 pairfree(&check_tmp);
951                                 return RLM_MODULE_FAIL;
952                         }
953                         if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) {
954                                 radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user",
955                                        inst->config->xlat_name);
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                                 pairfree(&reply_tmp);
961                                 return RLM_MODULE_FAIL;
962                         }
963                         if (!inst->config->read_groups)
964                                 dofallthrough = fallthrough(reply_tmp);
965                         pairxlatmove(request, &request->reply->vps, &reply_tmp);
966                         pairxlatmove(request, &request->config_items, &check_tmp);
967                 }
968         }
969
970         /*
971          *      Clear out the pairlists
972          */
973         pairfree(&check_tmp);
974         pairfree(&reply_tmp);
975
976         /*
977          *      dofallthrough is set to 1 by default so that if the user information
978          *      is not found, we will still process groups.  If the user information,
979          *      however, *is* found, Fall-Through must be set in order to process
980          *      the groups as well
981          */
982         if (dofallthrough) {
983                 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
984                 if (rows < 0) {
985                         radlog(L_ERR, "rlm_sql (%s): Error processing groups; rejecting user",
986                                inst->config->xlat_name);
987                         sql_release_socket(inst, sqlsocket);
988                         /* Remove the username we (maybe) added above */
989                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
990                         return RLM_MODULE_FAIL;
991                 } else if (rows > 0) {
992                         found = 1;
993                 }
994         }
995
996         /*
997          *      repeat the above process with the default profile or User-Profile
998          */
999         if (dofallthrough) {
1000                 int profile_found = 0;
1001                 /*
1002                 * Check for a default_profile or for a User-Profile.
1003                 */
1004                 user_profile = pairfind(request->config_items, PW_USER_PROFILE);
1005                 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1006                         char *profile = inst->config->default_profile;
1007
1008                         if (user_profile != NULL)
1009                                 profile = user_profile->vp_strvalue;
1010                         if (profile && strlen(profile)){
1011                                 radlog(L_DBG, "rlm_sql (%s): Checking profile %s",
1012                                        inst->config->xlat_name, profile);
1013                                 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1014                                         radlog(L_ERR, "rlm_sql (%s): Error setting profile; rejecting user",
1015                                                inst->config->xlat_name);
1016                                         sql_release_socket(inst, sqlsocket);
1017                                         /* Remove the username we (maybe) added above */
1018                                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1019                                         return RLM_MODULE_FAIL;
1020                                 } else {
1021                                         profile_found = 1;
1022                                 }
1023                         }
1024                 }
1025
1026                 if (profile_found) {
1027                         rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1028                         if (rows < 0) {
1029                                 radlog(L_ERR, "rlm_sql (%s): Error processing profile groups; rejecting user",
1030                                        inst->config->xlat_name);
1031                                 sql_release_socket(inst, sqlsocket);
1032                                 /* Remove the username we (maybe) added above */
1033                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1034                                 return RLM_MODULE_FAIL;
1035                         } else if (rows > 0) {
1036                                 found = 1;
1037                         }
1038                 }
1039         }
1040
1041         /* Remove the username we (maybe) added above */
1042         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1043         sql_release_socket(inst, sqlsocket);
1044
1045         if (!found) {
1046                 radlog(L_DBG, "rlm_sql (%s): User %s not found",
1047                        inst->config->xlat_name, sqlusername);
1048                 return RLM_MODULE_NOTFOUND;
1049         } else {
1050                 return RLM_MODULE_OK;
1051         }
1052 }
1053
1054 /*
1055  *      Accounting: save the account data to our sql table
1056  */
1057 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1058
1059         SQLSOCK *sqlsocket = NULL;
1060         VALUE_PAIR *pair;
1061         SQL_INST *inst = instance;
1062         int     ret = RLM_MODULE_OK;
1063         int     numaffected = 0;
1064         int     acctstatustype = 0;
1065         char    querystr[MAX_QUERY_LEN];
1066         char    logstr[MAX_QUERY_LEN];
1067         char    sqlusername[MAX_STRING_LEN];
1068
1069 #ifdef CISCO_ACCOUNTING_HACK
1070         int     acctsessiontime = 0;
1071 #endif
1072
1073         memset(querystr, 0, MAX_QUERY_LEN);
1074
1075         /*
1076          * Find the Acct Status Type
1077          */
1078         if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
1079                 acctstatustype = pair->vp_integer;
1080         } else {
1081                 radius_xlat(logstr, sizeof(logstr), "packet has no accounting status type. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
1082                 radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s",
1083                        inst->config->xlat_name, logstr);
1084                 return RLM_MODULE_INVALID;
1085         }
1086
1087         switch (acctstatustype) {
1088                         /*
1089                          * The Terminal server informed us that it was rebooted
1090                          * STOP all records from this NAS
1091                          */
1092                 case PW_STATUS_ACCOUNTING_ON:
1093                 case PW_STATUS_ACCOUNTING_OFF:
1094                         radlog(L_INFO, "rlm_sql (%s): received Acct On/Off packet", inst->config->xlat_name);
1095                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_onoff_query, request, sql_escape_func);
1096                         query_log(request, inst, querystr);
1097
1098                         sqlsocket = sql_get_socket(inst);
1099                         if (sqlsocket == NULL)
1100                                 return(RLM_MODULE_FAIL);
1101                         if (*querystr) { /* non-empty query */
1102                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1103                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting for Acct On/Off packet - %s",
1104                                                inst->config->xlat_name,
1105                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1106                                         ret = RLM_MODULE_FAIL;
1107                                 }
1108                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1109                         }
1110
1111                         break;
1112
1113                         /*
1114                          * Got an update accounting packet
1115                          */
1116                 case PW_STATUS_ALIVE:
1117
1118                         /*
1119                          * Set, escape, and check the user attr here
1120                          */
1121                         sql_set_user(inst, request, sqlusername, NULL);
1122
1123                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query, request, sql_escape_func);
1124                         query_log(request, inst, querystr);
1125
1126                         sqlsocket = sql_get_socket(inst);
1127                         if (sqlsocket == NULL)
1128                                 return(RLM_MODULE_FAIL);
1129                         if (*querystr) { /* non-empty query */
1130                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1131                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting ALIVE record - %s",
1132                                                inst->config->xlat_name,
1133                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1134                                         ret = RLM_MODULE_FAIL;
1135                                 }
1136                                 else {
1137                                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
1138                                         if (numaffected < 1) {
1139
1140                                                 /*
1141                                                  * If our update above didn't match anything
1142                                                  * we assume it's because we haven't seen a
1143                                                  * matching Start record.  So we have to
1144                                                  * insert this update rather than do an update
1145                                                  */
1146                                                 radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query_alt, request, sql_escape_func);
1147                                                 query_log(request, inst, querystr);
1148                                                 if (*querystr) { /* non-empty query */
1149                                                         if (rlm_sql_query(sqlsocket, inst, querystr)) {
1150                                                                 radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting ALIVE record - %s",
1151                                                                            inst->config->xlat_name,
1152                                                                            (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1153                                                                 ret = RLM_MODULE_FAIL;
1154                                                         }
1155                                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
1156                                                 }
1157                                         }
1158                                 }
1159                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1160                         }
1161                         break;
1162
1163                         /*
1164                          * Got accounting start packet
1165                          */
1166                 case PW_STATUS_START:
1167
1168                         /*
1169                          * Set, escape, and check the user attr here
1170                          */
1171                         sql_set_user(inst, request, sqlusername, NULL);
1172
1173                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_start_query, request, sql_escape_func);
1174                         query_log(request, inst, querystr);
1175
1176                         sqlsocket = sql_get_socket(inst);
1177                         if (sqlsocket == NULL)
1178                                 return(RLM_MODULE_FAIL);
1179                         if (*querystr) { /* non-empty query */
1180                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1181                                         radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting START record - %s",
1182                                                inst->config->xlat_name,
1183                                                (char *)(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(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting START record - %s",
1196                                                                inst->config->xlat_name,
1197                                                                (char *)(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(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting STOP record - %s",
1226                                                inst->config->xlat_name,
1227                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1228                                         ret = RLM_MODULE_FAIL;
1229                                 }
1230                                 else {
1231                                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
1232                                         if (numaffected < 1) {
1233                                                 /*
1234                                                  * If our update above didn't match anything
1235                                                  * we assume it's because we haven't seen a
1236                                                  * matching Start record.  So we have to
1237                                                  * insert this stop rather than do an update
1238                                                  */
1239 #ifdef CISCO_ACCOUNTING_HACK
1240                                                 /*
1241                                                  * If stop but zero session length AND no previous
1242                                                  * session found, drop it as in invalid packet
1243                                                  * This is to fix CISCO's aaa from filling our
1244                                                  * table with bogus crap
1245                                                  */
1246                                                 if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
1247                                                         acctsessiontime = pair->vp_integer;
1248
1249                                                 if (acctsessiontime <= 0) {
1250                                                         radius_xlat(logstr, sizeof(logstr), "stop packet with zero session length. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
1251                                                         radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s", inst->config->xlat_name, logstr);
1252                                                         sql_release_socket(inst, sqlsocket);
1253                                                         ret = RLM_MODULE_NOOP;
1254                                                 }
1255 #endif
1256
1257                                                 radius_xlat(querystr, sizeof(querystr), inst->config->accounting_stop_query_alt, request, sql_escape_func);
1258                                                 query_log(request, inst, querystr);
1259
1260                                                 if (*querystr) { /* non-empty query */
1261                                                         if (rlm_sql_query(sqlsocket, inst, querystr)) {
1262                                                                 radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting STOP record - %s",
1263                                                                                 inst->config->xlat_name,
1264                                                                                 (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1265                                                                 ret = RLM_MODULE_FAIL;
1266                                                         }
1267                                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
1268                                                 }
1269                                         }
1270                                 }
1271                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1272                         }
1273                         break;
1274
1275                         /*
1276                          *      Anything else is ignored.
1277                          */
1278                 default:
1279                         radlog(L_INFO, "rlm_sql (%s): Unsupported Acct-Status-Type = %d", inst->config->xlat_name, 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[0] == 0) {
1316                 return RLM_MODULE_NOOP;
1317         }
1318
1319         if((request->username == NULL) || (request->username->length == 0)) {
1320                 radlog(L_ERR, "rlm_sql (%s): Zero Length username not permitted\n", inst->config->xlat_name);
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[0] == '\0') {
1369                 sql_release_socket(inst, sqlsocket);
1370                 return RLM_MODULE_OK;
1371         }
1372
1373         radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
1374         if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
1375                 radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Database query error", inst->config->xlat_name);
1376                 sql_release_socket(inst, sqlsocket);
1377                 return RLM_MODULE_FAIL;
1378         }
1379
1380         /*
1381          *      Setup some stuff, like for MPP detection.
1382          */
1383         request->simul_count = 0;
1384
1385         if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
1386                 ipno = vp->vp_ipaddr;
1387         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
1388                 call_num = vp->vp_strvalue;
1389
1390
1391         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
1392                 row = sqlsocket->row;
1393                 if (row == NULL)
1394                         break;
1395                 if (!row[2]){
1396                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1397                         sql_release_socket(inst, sqlsocket);
1398                         DEBUG("rlm_sql (%s): Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1399                         return RLM_MODULE_FAIL;
1400                 }
1401                 if (!row[1]){
1402                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1403                         sql_release_socket(inst, sqlsocket);
1404                         DEBUG("rlm_sql (%s): Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1405                         return RLM_MODULE_FAIL;
1406                 }
1407                 if (row[3])
1408                         nas_addr = inet_addr(row[3]);
1409                 if (row[4])
1410                         nas_port = atoi(row[4]);
1411
1412                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1413
1414                 if (check == 0) {
1415                         /*
1416                          *      Stale record - zap it.
1417                          */
1418                         if (inst->config->deletestalesessions == TRUE) {
1419                                 uint32_t framed_addr = 0;
1420                                 char proto = 0;
1421                                 int sess_time = 0;
1422
1423                                 if (row[5])
1424                                         framed_addr = inet_addr(row[5]);
1425                                 if (row[7]){
1426                                         if (strcmp(row[7], "PPP") == 0)
1427                                                 proto = 'P';
1428                                         else if (strcmp(row[7], "SLIP") == 0)
1429                                                 proto = 'S';
1430                                 }
1431                                 if (row[8])
1432                                         sess_time = atoi(row[8]);
1433                                 session_zap(request, nas_addr, nas_port,
1434                                             row[2], row[1], framed_addr,
1435                                             proto, sess_time);
1436                         }
1437                 }
1438                 else if (check == 1) {
1439                         /*
1440                          *      User is still logged in.
1441                          */
1442                         ++request->simul_count;
1443
1444                         /*
1445                          *      Does it look like a MPP attempt?
1446                          */
1447                         if (row[5] && ipno && inet_addr(row[5]) == ipno)
1448                                 request->simul_mpp = 2;
1449                         else if (row[6] && call_num &&
1450                                 !strncmp(row[6],call_num,16))
1451                                 request->simul_mpp = 2;
1452                 }
1453                 else {
1454                         /*
1455                          *      Failed to check the terminal server for
1456                          *      duplicate logins: return an error.
1457                          */
1458                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1459                         sql_release_socket(inst, sqlsocket);
1460                         radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Failed to check the terminal server for user '%s'.", inst->config->xlat_name, row[2]);
1461                         return RLM_MODULE_FAIL;
1462                 }
1463         }
1464
1465         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1466         sql_release_socket(inst, sqlsocket);
1467
1468         /*
1469          *      The Auth module apparently looks at request->simul_count,
1470          *      not the return value of this module when deciding to deny
1471          *      a call for too many sessions.
1472          */
1473         return RLM_MODULE_OK;
1474 }
1475
1476 /*
1477  *      Execute postauth_query after authentication
1478  */
1479 static int rlm_sql_postauth(void *instance, REQUEST *request) {
1480         SQLSOCK         *sqlsocket = NULL;
1481         SQL_INST        *inst = instance;
1482         char            querystr[MAX_QUERY_LEN];
1483         char            sqlusername[MAX_STRING_LEN];
1484
1485         DEBUG("rlm_sql (%s): Processing sql_postauth", inst->config->xlat_name);
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[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                        (char *)(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 };