Unconfigured strings are NULL, not ""
[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, 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 ||
467             (inst->config->groupmemb_query[0] == 0))
468                 return 1;
469
470         if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, request, sql_escape_func)) {
471                 radlog(L_ERR, "rlm_sql (%s): xlat failed.",
472                         inst->config->xlat_name);
473                 return -1;
474         }
475
476         if (rlm_sql_select_query(sqlsocket, inst, querystr) < 0) {
477                 radlog(L_ERR, "rlm_sql (%s): database query error, %s: %s",
478                         inst->config->xlat_name,querystr,
479                         (char *)(inst->module->sql_error)(sqlsocket,inst->config));
480                 return -1;
481         }
482         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
483                 row = sqlsocket->row;
484                 if (row == NULL)
485                         break;
486                 if (row[0] == NULL){
487                         DEBUG("rlm_sql (%s): row[0] returned NULL",
488                                 inst->config->xlat_name);
489                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
490                         sql_grouplist_free(group_list);
491                         return -1;
492                 }
493                 if (*group_list == NULL) {
494                         *group_list = rad_malloc(sizeof(SQL_GROUPLIST));
495                         group_list_tmp = *group_list;
496                 } else {
497                         group_list_tmp->next = rad_malloc(sizeof(SQL_GROUPLIST));
498                         group_list_tmp = group_list_tmp->next;
499                 }
500                 group_list_tmp->next = NULL;
501                 strlcpy(group_list_tmp->groupname, row[0], MAX_STRING_LEN);
502         }
503
504         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
505
506         return num_groups;
507 }
508
509
510 /*
511  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
512  * with the group memberships reciding in sql
513  * The group membership query should only return one element which is the username. The returned
514  * username will then be checked with the passed check string.
515  */
516
517 static int sql_groupcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
518                         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
519 {
520         SQLSOCK *sqlsocket;
521         SQL_INST *inst = instance;
522         char sqlusername[MAX_STRING_LEN];
523         SQL_GROUPLIST *group_list, *group_list_tmp;
524
525         check_pairs = check_pairs;
526         reply_pairs = reply_pairs;
527         request = request;
528
529         DEBUG("rlm_sql (%s): - sql_groupcmp", inst->config->xlat_name);
530         if (!check || !check->vp_strvalue || !check->length){
531                 DEBUG("rlm_sql (%s): sql_groupcmp: Illegal group name",
532                       inst->config->xlat_name);
533                 return 1;
534         }
535         if (req == NULL){
536                 DEBUG("rlm_sql (%s): sql_groupcmp: NULL request",
537                       inst->config->xlat_name);
538                 return 1;
539         }
540         /*
541          * Set, escape, and check the user attr here
542          */
543         if (sql_set_user(inst, req, sqlusername, NULL) < 0)
544                 return 1;
545
546         /*
547          *      Get a socket for this lookup
548          */
549         sqlsocket = sql_get_socket(inst);
550         if (sqlsocket == NULL) {
551                 /* Remove the username we (maybe) added above */
552                 pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
553                 return 1;
554         }
555
556         /*
557          *      Get the list of groups this user is a member of
558          */
559         if (sql_get_grouplist(inst, sqlsocket, req, &group_list)) {
560                 radlog(L_ERR, "rlm_sql (%s): Error getting group membership",
561                        inst->config->xlat_name);
562                 /* Remove the username we (maybe) added above */
563                 pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
564                 sql_release_socket(inst, sqlsocket);
565                 return 1;
566         }
567
568         for (group_list_tmp = group_list; group_list_tmp != NULL; group_list_tmp = group_list_tmp->next) {
569                 if (strcmp(group_list_tmp->groupname, check->vp_strvalue) == 0){
570                         DEBUG("rlm_sql (%s): - sql_groupcmp finished: User is a member of group %s",
571                               inst->config->xlat_name,
572                               (char *)check->vp_strvalue);
573                         /* Free the grouplist */
574                         sql_grouplist_free(&group_list);
575                         /* Remove the username we (maybe) added above */
576                         pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
577                         sql_release_socket(inst, sqlsocket);
578                         return 0;
579                 }
580         }
581
582         /* Free the grouplist */
583         sql_grouplist_free(&group_list);
584         /* Remove the username we (maybe) added above */
585         pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
586         sql_release_socket(inst,sqlsocket);
587
588         DEBUG("rlm_sql (%s): - sql_groupcmp finished: User is NOT a member of group %s",
589               inst->config->xlat_name, (char *)check->vp_strvalue);
590
591         return 1;
592 }
593
594
595
596 static int rlm_sql_process_groups(SQL_INST *inst, REQUEST *request, SQLSOCK *sqlsocket, int *dofallthrough)
597 {
598         VALUE_PAIR *check_tmp = NULL;
599         VALUE_PAIR *reply_tmp = NULL;
600         SQL_GROUPLIST *group_list, *group_list_tmp;
601         VALUE_PAIR *sql_group = NULL;
602         char    querystr[MAX_QUERY_LEN];
603         int found = 0;
604         int rows;
605
606         /*
607          *      Get the list of groups this user is a member of
608          */
609         if (sql_get_grouplist(inst, sqlsocket, request, &group_list)) {
610                 radlog(L_ERR, "rlm_sql (%s): Error retrieving group list",
611                        inst->config->xlat_name);
612                 return -1;
613         }
614
615         for (group_list_tmp = group_list; group_list_tmp != NULL && *dofallthrough != 0; group_list_tmp = group_list_tmp->next) {
616                 /*
617                  *      Add the Sql-Group attribute to the request list so we know
618                  *      which group we're retrieving attributes for
619                  */
620                 sql_group = pairmake("Sql-Group", group_list_tmp->groupname, T_OP_EQ);
621                 if (!sql_group) {
622                         radlog(L_ERR, "rlm_sql (%s): Error creating Sql-Group attribute",
623                                inst->config->xlat_name);
624                         return -1;
625                 }
626                 pairadd(&request->packet->vps, sql_group);
627                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_check_query, request, sql_escape_func)) {
628                         radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
629                                inst->config->xlat_name);
630                         /* Remove the grouup we added above */
631                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
632                         return -1;
633                 }
634                 rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr);
635                 if (rows < 0) {
636                         radlog(L_ERR, "rlm_sql (%s): Error retrieving check pairs for group %s",
637                                inst->config->xlat_name, group_list_tmp->groupname);
638                         /* Remove the grouup we added above */
639                         pairdelete(&request->packet->vps, PW_SQL_GROUP);
640                         pairfree(&check_tmp);
641                         return -1;
642                 } else if (rows > 0) {
643                         /*
644                          *      Only do this if *some* check pairs were returned
645                          */
646                         if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
647                                 found = 1;
648                                 DEBUG2("rlm_sql (%s): User found in group %s",
649                                         inst->config->xlat_name, group_list_tmp->groupname);
650                                 /*
651                                  *      Now get the reply pairs since the paircompare matched
652                                  */
653                                 if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_group_reply_query, request, sql_escape_func)) {
654                                         radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
655                                                inst->config->xlat_name);
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(L_ERR, "rlm_sql (%s): Error retrieving reply pairs for group %s",
663                                                inst->config->xlat_name, 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                         DEBUG2("rlm_sql (%s): User found in group %s",
683                                 inst->config->xlat_name, 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(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
689                                        inst->config->xlat_name);
690                                 /* Remove the grouup we added above */
691                                 pairdelete(&request->packet->vps, PW_SQL_GROUP);
692                                 pairfree(&check_tmp);
693                                 return -1;
694                         }
695                         if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) {
696                                 radlog(L_ERR, "rlm_sql (%s): Error retrieving reply pairs for group %s",
697                                        inst->config->xlat_name, group_list_tmp->groupname);
698                                 /* Remove the grouup we added above */
699                                 pairdelete(&request->packet->vps, PW_SQL_GROUP);
700                                 pairfree(&check_tmp);
701                                 pairfree(&reply_tmp);
702                                 return -1;
703                         }
704                         *dofallthrough = fallthrough(reply_tmp);
705                         pairxlatmove(request, &request->reply->vps, &reply_tmp);
706                         pairxlatmove(request, &request->config_items, &check_tmp);
707                 }
708
709                 /*
710                  * Delete the Sql-Group we added above
711                  * And clear out the pairlists
712                  */
713                 pairdelete(&request->packet->vps, PW_SQL_GROUP);
714                 pairfree(&check_tmp);
715                 pairfree(&reply_tmp);
716         }
717
718         sql_grouplist_free(&group_list);
719         return found;
720 }
721
722
723 static int rlm_sql_detach(void *instance)
724 {
725         SQL_INST *inst = instance;
726
727         paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
728
729         if (inst->config) {
730                 int i;
731
732                 if (inst->sqlpool) {
733                         sql_poolfree(inst);
734                 }
735
736                 if (inst->config->xlat_name) {
737                         xlat_unregister(inst->config->xlat_name,(RAD_XLAT_FUNC)sql_xlat);
738                         free(inst->config->xlat_name);
739                 }
740
741                 /*
742                  *      Free up dynamically allocated string pointers.
743                  */
744                 for (i = 0; module_config[i].name != NULL; i++) {
745                         char **p;
746                         if (module_config[i].type != PW_TYPE_STRING_PTR) {
747                                 continue;
748                         }
749
750                         /*
751                          *      Treat 'config' as an opaque array of bytes,
752                          *      and take the offset into it.  There's a
753                          *      (char*) pointer at that offset, and we want
754                          *      to point to it.
755                          */
756                         p = (char **) (((char *)inst->config) + module_config[i].offset);
757                         if (!*p) { /* nothing allocated */
758                                 continue;
759                         }
760                         free(*p);
761                         *p = NULL;
762                 }
763                 allowed_chars = NULL;
764                 free(inst->config);
765                 inst->config = NULL;
766         }
767
768         if (inst->handle) {
769 #if 0
770                 /*
771                  *      FIXME: Call the modules 'destroy' function?
772                  */
773                 lt_dlclose(inst->handle);       /* ignore any errors */
774 #endif
775         }
776         free(inst);
777
778         return 0;
779 }
780 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance)
781 {
782         SQL_INST *inst;
783         const char *xlat_name;
784
785         inst = rad_malloc(sizeof(SQL_INST));
786         memset(inst, 0, sizeof(SQL_INST));
787
788         inst->config = rad_malloc(sizeof(SQL_CONFIG));
789         memset(inst->config, 0, sizeof(SQL_CONFIG));
790
791         /*
792          * If the configuration parameters can't be parsed, then
793          * fail.
794          */
795         if (cf_section_parse(conf, inst->config, module_config) < 0) {
796                 rlm_sql_detach(inst);
797                 return -1;
798         }
799
800         xlat_name = cf_section_name2(conf);
801         if (xlat_name == NULL)
802                 xlat_name = cf_section_name1(conf);
803         if (xlat_name){
804                 inst->config->xlat_name = strdup(xlat_name);
805                 xlat_register(xlat_name, (RAD_XLAT_FUNC)sql_xlat, inst);
806         }
807
808         if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
809                 radlog(L_ERR | L_CONS, "rlm_sql (%s): sql_instantiate: number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d",
810                        inst->config->xlat_name, MAX_SQL_SOCKS);
811                 rlm_sql_detach(inst);
812                 return -1;
813         }
814
815         /*
816          *      Sanity check for crazy people.
817          */
818         if (strncmp(inst->config->sql_driver, "rlm_sql_", 8) != 0) {
819                 radlog(L_ERR, "rlm_sql (%s): \"%s\" is NOT an SQL driver!",
820                        inst->config->xlat_name, inst->config->sql_driver);
821                 rlm_sql_detach(inst);
822                 return -1;
823         }
824
825         inst->handle = lt_dlopenext(inst->config->sql_driver);
826         if (inst->handle == NULL) {
827                 radlog(L_ERR, "rlm_sql (%s): Could not link driver %s: %s",
828                        inst->config->xlat_name, inst->config->sql_driver,
829                        lt_dlerror());
830                 radlog(L_ERR, "rlm_sql (%s): Make sure it (and all its dependent libraries!) are in the search path of your system's ld.",
831                        inst->config->xlat_name);
832                 rlm_sql_detach(inst);
833                 return -1;
834         }
835
836         inst->module = (rlm_sql_module_t *) lt_dlsym(inst->handle, inst->config->sql_driver);
837         if (!inst->module) {
838                 radlog(L_ERR, "rlm_sql (%s): Could not link symbol %s: %s",
839                        inst->config->xlat_name, inst->config->sql_driver,
840                        lt_dlerror());
841                 rlm_sql_detach(inst);
842                 return -1;
843         }
844
845         radlog(L_INFO, "rlm_sql (%s): Driver %s (module %s) loaded and linked",
846                inst->config->xlat_name, inst->config->sql_driver,
847                inst->module->name);
848         radlog(L_INFO, "rlm_sql (%s): Attempting to connect to %s@%s:%s/%s",
849                inst->config->xlat_name, inst->config->sql_login,
850                inst->config->sql_server, inst->config->sql_port,
851                inst->config->sql_db);
852
853         if (sql_init_socketpool(inst) < 0) {
854                 rlm_sql_detach(inst);
855                 return -1;
856         }
857         paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
858
859         if (inst->config->do_clients){
860                 if (generate_sql_clients(inst) == -1){
861                         radlog(L_ERR, "rlm_sql (%s): generate_sql_clients() returned error",inst->config->xlat_name);
862                         rlm_sql_detach(inst);
863                         return -1;
864                 }
865         }
866         allowed_chars = inst->config->allowed_chars;
867
868         *instance = inst;
869
870         return RLM_MODULE_OK;
871 }
872
873
874 static int rlm_sql_authorize(void *instance, REQUEST * request)
875 {
876         VALUE_PAIR *check_tmp = NULL;
877         VALUE_PAIR *reply_tmp = NULL;
878         VALUE_PAIR *user_profile = NULL;
879         int     found = 0;
880         int     dofallthrough = 1;
881         int     rows;
882         SQLSOCK *sqlsocket;
883         SQL_INST *inst = instance;
884         char    querystr[MAX_QUERY_LEN];
885         char    sqlusername[MAX_STRING_LEN];
886         /*
887          * the profile username is used as the sqlusername during
888          * profile checking so that we don't overwrite the orignal
889          * sqlusername string
890          */
891         char   profileusername[MAX_STRING_LEN];
892
893         /*
894          * Set, escape, and check the user attr here
895          */
896         if (sql_set_user(inst, request, sqlusername, NULL) < 0)
897                 return RLM_MODULE_FAIL;
898
899
900         /*
901          * reserve a socket
902          */
903         sqlsocket = sql_get_socket(inst);
904         if (sqlsocket == NULL) {
905                 /* Remove the username we (maybe) added above */
906                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
907                 return RLM_MODULE_FAIL;
908         }
909
910
911         /*
912          *  After this point, ALL 'return's MUST release the SQL socket!
913          */
914
915         /*
916          * Alright, start by getting the specific entry for the user
917          */
918         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_check_query, request, sql_escape_func)) {
919                 radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
920                        inst->config->xlat_name);
921                 sql_release_socket(inst, sqlsocket);
922                 /* Remove the username we (maybe) added above */
923                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
924                 return RLM_MODULE_FAIL;
925         }
926         rows = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr);
927         if (rows < 0) {
928                 radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user",
929                        inst->config->xlat_name);
930                 sql_release_socket(inst, sqlsocket);
931                 /* Remove the username we (maybe) added above */
932                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
933                 pairfree(&check_tmp);
934                 return RLM_MODULE_FAIL;
935         } else if (rows > 0) {
936                 /*
937                  *      Only do this if *some* check pairs were returned
938                  */
939                 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) == 0) {
940                         found = 1;
941                         DEBUG2("rlm_sql (%s): User found in radcheck table", inst->config->xlat_name);
942                         /*
943                          *      Now get the reply pairs since the paircompare matched
944                          */
945                         if (!radius_xlat(querystr, sizeof(querystr), inst->config->authorize_reply_query, request, sql_escape_func)) {
946                                 radlog(L_ERR, "rlm_sql (%s): Error generating query; rejecting user",
947                                        inst->config->xlat_name);
948                                 sql_release_socket(inst, sqlsocket);
949                                 /* Remove the username we (maybe) added above */
950                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
951                                 pairfree(&check_tmp);
952                                 return RLM_MODULE_FAIL;
953                         }
954                         if (sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr) < 0) {
955                                 radlog(L_ERR, "rlm_sql (%s): SQL query error; rejecting user",
956                                        inst->config->xlat_name);
957                                 sql_release_socket(inst, sqlsocket);
958                                 /* Remove the username we (maybe) added above */
959                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
960                                 pairfree(&check_tmp);
961                                 pairfree(&reply_tmp);
962                                 return RLM_MODULE_FAIL;
963                         }
964                         if (!inst->config->read_groups)
965                                 dofallthrough = fallthrough(reply_tmp);
966                         pairxlatmove(request, &request->reply->vps, &reply_tmp);
967                         pairxlatmove(request, &request->config_items, &check_tmp);
968                 }
969         }
970
971         /*
972          *      Clear out the pairlists
973          */
974         pairfree(&check_tmp);
975         pairfree(&reply_tmp);
976
977         /*
978          *      dofallthrough is set to 1 by default so that if the user information
979          *      is not found, we will still process groups.  If the user information,
980          *      however, *is* found, Fall-Through must be set in order to process
981          *      the groups as well
982          */
983         if (dofallthrough) {
984                 rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
985                 if (rows < 0) {
986                         radlog(L_ERR, "rlm_sql (%s): Error processing groups; rejecting user",
987                                inst->config->xlat_name);
988                         sql_release_socket(inst, sqlsocket);
989                         /* Remove the username we (maybe) added above */
990                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
991                         return RLM_MODULE_FAIL;
992                 } else if (rows > 0) {
993                         found = 1;
994                 }
995         }
996
997         /*
998          *      repeat the above process with the default profile or User-Profile
999          */
1000         if (dofallthrough) {
1001                 int profile_found = 0;
1002                 /*
1003                 * Check for a default_profile or for a User-Profile.
1004                 */
1005                 user_profile = pairfind(request->config_items, PW_USER_PROFILE);
1006                 if (inst->config->default_profile[0] != 0 || user_profile != NULL){
1007                         char *profile = inst->config->default_profile;
1008
1009                         if (user_profile != NULL)
1010                                 profile = user_profile->vp_strvalue;
1011                         if (profile && strlen(profile)){
1012                                 radlog(L_DBG, "rlm_sql (%s): Checking profile %s",
1013                                        inst->config->xlat_name, profile);
1014                                 if (sql_set_user(inst, request, profileusername, profile) < 0) {
1015                                         radlog(L_ERR, "rlm_sql (%s): Error setting profile; rejecting user",
1016                                                inst->config->xlat_name);
1017                                         sql_release_socket(inst, sqlsocket);
1018                                         /* Remove the username we (maybe) added above */
1019                                         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1020                                         return RLM_MODULE_FAIL;
1021                                 } else {
1022                                         profile_found = 1;
1023                                 }
1024                         }
1025                 }
1026
1027                 if (profile_found) {
1028                         rows = rlm_sql_process_groups(inst, request, sqlsocket, &dofallthrough);
1029                         if (rows < 0) {
1030                                 radlog(L_ERR, "rlm_sql (%s): Error processing profile groups; rejecting user",
1031                                        inst->config->xlat_name);
1032                                 sql_release_socket(inst, sqlsocket);
1033                                 /* Remove the username we (maybe) added above */
1034                                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1035                                 return RLM_MODULE_FAIL;
1036                         } else if (rows > 0) {
1037                                 found = 1;
1038                         }
1039                 }
1040         }
1041
1042         /* Remove the username we (maybe) added above */
1043         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
1044         sql_release_socket(inst, sqlsocket);
1045
1046         if (!found) {
1047                 radlog(L_DBG, "rlm_sql (%s): User %s not found",
1048                        inst->config->xlat_name, sqlusername);
1049                 return RLM_MODULE_NOTFOUND;
1050         } else {
1051                 return RLM_MODULE_OK;
1052         }
1053 }
1054
1055 /*
1056  *      Accounting: save the account data to our sql table
1057  */
1058 static int rlm_sql_accounting(void *instance, REQUEST * request) {
1059
1060         SQLSOCK *sqlsocket = NULL;
1061         VALUE_PAIR *pair;
1062         SQL_INST *inst = instance;
1063         int     ret = RLM_MODULE_OK;
1064         int     numaffected = 0;
1065         int     acctstatustype = 0;
1066         char    querystr[MAX_QUERY_LEN];
1067         char    logstr[MAX_QUERY_LEN];
1068         char    sqlusername[MAX_STRING_LEN];
1069
1070 #ifdef CISCO_ACCOUNTING_HACK
1071         int     acctsessiontime = 0;
1072 #endif
1073
1074         memset(querystr, 0, MAX_QUERY_LEN);
1075
1076         /*
1077          * Find the Acct Status Type
1078          */
1079         if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
1080                 acctstatustype = pair->vp_integer;
1081         } else {
1082                 radius_xlat(logstr, sizeof(logstr), "packet has no accounting status type. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
1083                 radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s",
1084                        inst->config->xlat_name, logstr);
1085                 return RLM_MODULE_INVALID;
1086         }
1087
1088         switch (acctstatustype) {
1089                         /*
1090                          * The Terminal server informed us that it was rebooted
1091                          * STOP all records from this NAS
1092                          */
1093                 case PW_STATUS_ACCOUNTING_ON:
1094                 case PW_STATUS_ACCOUNTING_OFF:
1095                         radlog(L_INFO, "rlm_sql (%s): received Acct On/Off packet", inst->config->xlat_name);
1096                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_onoff_query, request, sql_escape_func);
1097                         query_log(request, inst, querystr);
1098
1099                         sqlsocket = sql_get_socket(inst);
1100                         if (sqlsocket == NULL)
1101                                 return(RLM_MODULE_FAIL);
1102                         if (*querystr) { /* non-empty query */
1103                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1104                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting for Acct On/Off packet - %s",
1105                                                inst->config->xlat_name,
1106                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1107                                         ret = RLM_MODULE_FAIL;
1108                                 }
1109                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1110                         }
1111
1112                         break;
1113
1114                         /*
1115                          * Got an update accounting packet
1116                          */
1117                 case PW_STATUS_ALIVE:
1118
1119                         /*
1120                          * Set, escape, and check the user attr here
1121                          */
1122                         sql_set_user(inst, request, sqlusername, NULL);
1123
1124                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query, request, sql_escape_func);
1125                         query_log(request, inst, querystr);
1126
1127                         sqlsocket = sql_get_socket(inst);
1128                         if (sqlsocket == NULL)
1129                                 return(RLM_MODULE_FAIL);
1130                         if (*querystr) { /* non-empty query */
1131                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1132                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting ALIVE record - %s",
1133                                                inst->config->xlat_name,
1134                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1135                                         ret = RLM_MODULE_FAIL;
1136                                 }
1137                                 else {
1138                                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
1139                                         if (numaffected < 1) {
1140
1141                                                 /*
1142                                                  * If our update above didn't match anything
1143                                                  * we assume it's because we haven't seen a
1144                                                  * matching Start record.  So we have to
1145                                                  * insert this update rather than do an update
1146                                                  */
1147                                                 radius_xlat(querystr, sizeof(querystr), inst->config->accounting_update_query_alt, request, sql_escape_func);
1148                                                 query_log(request, inst, querystr);
1149                                                 if (*querystr) { /* non-empty query */
1150                                                         if (rlm_sql_query(sqlsocket, inst, querystr)) {
1151                                                                 radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting ALIVE record - %s",
1152                                                                            inst->config->xlat_name,
1153                                                                            (char *)(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(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting START record - %s",
1183                                                inst->config->xlat_name,
1184                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1185
1186                                         /*
1187                                          * We failed the insert above.  It's probably because
1188                                          * the stop record came before the start.  We try
1189                                          * our alternate query now (typically an UPDATE)
1190                                          */
1191                                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_start_query_alt, request, sql_escape_func);
1192                                         query_log(request, inst, querystr);
1193
1194                                         if (*querystr) { /* non-empty query */
1195                                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1196                                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting START record - %s",
1197                                                                inst->config->xlat_name,
1198                                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1199                                                         ret = RLM_MODULE_FAIL;
1200                                                 }
1201                                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1202                                         }
1203                                 }
1204                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1205                         }
1206                         break;
1207
1208                         /*
1209                          * Got accounting stop packet
1210                          */
1211                 case PW_STATUS_STOP:
1212
1213                         /*
1214                          * Set, escape, and check the user attr here
1215                          */
1216                         sql_set_user(inst, request, sqlusername, NULL);
1217
1218                         radius_xlat(querystr, sizeof(querystr), inst->config->accounting_stop_query, request, sql_escape_func);
1219                         query_log(request, inst, querystr);
1220
1221                         sqlsocket = sql_get_socket(inst);
1222                         if (sqlsocket == NULL)
1223                                 return(RLM_MODULE_FAIL);
1224                         if (*querystr) { /* non-empty query */
1225                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
1226                                         radlog(L_ERR, "rlm_sql (%s): Couldn't update SQL accounting STOP record - %s",
1227                                                inst->config->xlat_name,
1228                                                (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1229                                         ret = RLM_MODULE_FAIL;
1230                                 }
1231                                 else {
1232                                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
1233                                         if (numaffected < 1) {
1234                                                 /*
1235                                                  * If our update above didn't match anything
1236                                                  * we assume it's because we haven't seen a
1237                                                  * matching Start record.  So we have to
1238                                                  * insert this stop rather than do an update
1239                                                  */
1240 #ifdef CISCO_ACCOUNTING_HACK
1241                                                 /*
1242                                                  * If stop but zero session length AND no previous
1243                                                  * session found, drop it as in invalid packet
1244                                                  * This is to fix CISCO's aaa from filling our
1245                                                  * table with bogus crap
1246                                                  */
1247                                                 if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
1248                                                         acctsessiontime = pair->vp_integer;
1249
1250                                                 if (acctsessiontime <= 0) {
1251                                                         radius_xlat(logstr, sizeof(logstr), "stop packet with zero session length. [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, NULL);
1252                                                         radlog(L_ERR, "rlm_sql (%s) in sql_accounting: %s", inst->config->xlat_name, logstr);
1253                                                         sql_release_socket(inst, sqlsocket);
1254                                                         ret = RLM_MODULE_NOOP;
1255                                                 }
1256 #endif
1257
1258                                                 radius_xlat(querystr, sizeof(querystr), inst->config->accounting_stop_query_alt, request, sql_escape_func);
1259                                                 query_log(request, inst, querystr);
1260
1261                                                 if (*querystr) { /* non-empty query */
1262                                                         if (rlm_sql_query(sqlsocket, inst, querystr)) {
1263                                                                 radlog(L_ERR, "rlm_sql (%s): Couldn't insert SQL accounting STOP record - %s",
1264                                                                                 inst->config->xlat_name,
1265                                                                                 (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1266                                                                 ret = RLM_MODULE_FAIL;
1267                                                         }
1268                                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
1269                                                 }
1270                                         }
1271                                 }
1272                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
1273                         }
1274                         break;
1275
1276                         /*
1277                          *      Anything else is ignored.
1278                          */
1279                 default:
1280                         radlog(L_INFO, "rlm_sql (%s): Unsupported Acct-Status-Type = %d", inst->config->xlat_name, acctstatustype);
1281                         return RLM_MODULE_NOOP;
1282                         break;
1283
1284         }
1285
1286         sql_release_socket(inst, sqlsocket);
1287
1288         return ret;
1289 }
1290
1291
1292 /*
1293  *        See if a user is already logged in. Sets request->simul_count to the
1294  *        current session count for this user.
1295  *
1296  *        Check twice. If on the first pass the user exceeds his
1297  *        max. number of logins, do a second pass and validate all
1298  *        logins by querying the terminal server (using eg. SNMP).
1299  */
1300
1301 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
1302         SQLSOCK         *sqlsocket;
1303         SQL_INST        *inst = instance;
1304         SQL_ROW         row;
1305         char            querystr[MAX_QUERY_LEN];
1306         char            sqlusername[MAX_STRING_LEN];
1307         int             check = 0;
1308         uint32_t        ipno = 0;
1309         char            *call_num = NULL;
1310         VALUE_PAIR      *vp;
1311         int             ret;
1312         uint32_t        nas_addr = 0;
1313         int             nas_port = 0;
1314
1315         /* If simul_count_query is not defined, we don't do any checking */
1316         if (!inst->config->simul_count_query ||
1317             (inst->config->simul_count_query[0] == 0)) {
1318                 return RLM_MODULE_NOOP;
1319         }
1320
1321         if((request->username == NULL) || (request->username->length == 0)) {
1322                 radlog(L_ERR, "rlm_sql (%s): Zero Length username not permitted\n", inst->config->xlat_name);
1323                 return RLM_MODULE_INVALID;
1324         }
1325
1326
1327         if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1328                 return RLM_MODULE_FAIL;
1329
1330         radius_xlat(querystr, sizeof(querystr), inst->config->simul_count_query, request, sql_escape_func);
1331
1332         /* initialize the sql socket */
1333         sqlsocket = sql_get_socket(inst);
1334         if(sqlsocket == NULL)
1335                 return RLM_MODULE_FAIL;
1336
1337         if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
1338                 radlog(L_ERR, "rlm_sql (%s) sql_checksimul: Database query failed", inst->config->xlat_name);
1339                 sql_release_socket(inst, sqlsocket);
1340                 return RLM_MODULE_FAIL;
1341         }
1342
1343         ret = rlm_sql_fetch_row(sqlsocket, inst);
1344
1345         if (ret != 0) {
1346                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1347                 sql_release_socket(inst, sqlsocket);
1348                 return RLM_MODULE_FAIL;
1349         }
1350
1351         row = sqlsocket->row;
1352         if (row == NULL) {
1353                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1354                 sql_release_socket(inst, sqlsocket);
1355                 return RLM_MODULE_FAIL;
1356         }
1357
1358         request->simul_count = atoi(row[0]);
1359         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1360
1361         if(request->simul_count < request->simul_max) {
1362                 sql_release_socket(inst, sqlsocket);
1363                 return RLM_MODULE_OK;
1364         }
1365
1366         /*
1367          *      Looks like too many sessions, so let's start verifying
1368          *      them, unless told to rely on count query only.
1369          */
1370         if (!inst->config->simul_verify_query ||
1371             (inst->config->simul_verify_query[0] == '\0')) {
1372                 sql_release_socket(inst, sqlsocket);
1373                 return RLM_MODULE_OK;
1374         }
1375
1376         radius_xlat(querystr, sizeof(querystr), inst->config->simul_verify_query, request, sql_escape_func);
1377         if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
1378                 radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Database query error", inst->config->xlat_name);
1379                 sql_release_socket(inst, sqlsocket);
1380                 return RLM_MODULE_FAIL;
1381         }
1382
1383         /*
1384          *      Setup some stuff, like for MPP detection.
1385          */
1386         request->simul_count = 0;
1387
1388         if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
1389                 ipno = vp->vp_ipaddr;
1390         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
1391                 call_num = vp->vp_strvalue;
1392
1393
1394         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
1395                 row = sqlsocket->row;
1396                 if (row == NULL)
1397                         break;
1398                 if (!row[2]){
1399                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1400                         sql_release_socket(inst, sqlsocket);
1401                         DEBUG("rlm_sql (%s): Cannot zap stale entry. No username present in entry.", inst->config->xlat_name);
1402                         return RLM_MODULE_FAIL;
1403                 }
1404                 if (!row[1]){
1405                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1406                         sql_release_socket(inst, sqlsocket);
1407                         DEBUG("rlm_sql (%s): Cannot zap stale entry. No session id in entry.", inst->config->xlat_name);
1408                         return RLM_MODULE_FAIL;
1409                 }
1410                 if (row[3])
1411                         nas_addr = inet_addr(row[3]);
1412                 if (row[4])
1413                         nas_port = atoi(row[4]);
1414
1415                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1416
1417                 if (check == 0) {
1418                         /*
1419                          *      Stale record - zap it.
1420                          */
1421                         if (inst->config->deletestalesessions == TRUE) {
1422                                 uint32_t framed_addr = 0;
1423                                 char proto = 0;
1424                                 int sess_time = 0;
1425
1426                                 if (row[5])
1427                                         framed_addr = inet_addr(row[5]);
1428                                 if (row[7]){
1429                                         if (strcmp(row[7], "PPP") == 0)
1430                                                 proto = 'P';
1431                                         else if (strcmp(row[7], "SLIP") == 0)
1432                                                 proto = 'S';
1433                                 }
1434                                 if (row[8])
1435                                         sess_time = atoi(row[8]);
1436                                 session_zap(request, nas_addr, nas_port,
1437                                             row[2], row[1], framed_addr,
1438                                             proto, sess_time);
1439                         }
1440                 }
1441                 else if (check == 1) {
1442                         /*
1443                          *      User is still logged in.
1444                          */
1445                         ++request->simul_count;
1446
1447                         /*
1448                          *      Does it look like a MPP attempt?
1449                          */
1450                         if (row[5] && ipno && inet_addr(row[5]) == ipno)
1451                                 request->simul_mpp = 2;
1452                         else if (row[6] && call_num &&
1453                                 !strncmp(row[6],call_num,16))
1454                                 request->simul_mpp = 2;
1455                 }
1456                 else {
1457                         /*
1458                          *      Failed to check the terminal server for
1459                          *      duplicate logins: return an error.
1460                          */
1461                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1462                         sql_release_socket(inst, sqlsocket);
1463                         radlog(L_ERR, "rlm_sql (%s): sql_checksimul: Failed to check the terminal server for user '%s'.", inst->config->xlat_name, row[2]);
1464                         return RLM_MODULE_FAIL;
1465                 }
1466         }
1467
1468         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
1469         sql_release_socket(inst, sqlsocket);
1470
1471         /*
1472          *      The Auth module apparently looks at request->simul_count,
1473          *      not the return value of this module when deciding to deny
1474          *      a call for too many sessions.
1475          */
1476         return RLM_MODULE_OK;
1477 }
1478
1479 /*
1480  *      Execute postauth_query after authentication
1481  */
1482 static int rlm_sql_postauth(void *instance, REQUEST *request) {
1483         SQLSOCK         *sqlsocket = NULL;
1484         SQL_INST        *inst = instance;
1485         char            querystr[MAX_QUERY_LEN];
1486         char            sqlusername[MAX_STRING_LEN];
1487
1488         DEBUG("rlm_sql (%s): Processing sql_postauth", inst->config->xlat_name);
1489
1490         if(sql_set_user(inst, request, sqlusername, NULL) < 0)
1491                 return RLM_MODULE_FAIL;
1492
1493         /* If postauth_query is not defined, we stop here */
1494         if (!inst->config->postauth_query ||
1495             (inst->config->postauth_query[0] == '\0'))
1496                 return RLM_MODULE_NOOP;
1497
1498         /* Expand variables in the query */
1499         memset(querystr, 0, MAX_QUERY_LEN);
1500         radius_xlat(querystr, sizeof(querystr), inst->config->postauth_query,
1501                     request, sql_escape_func);
1502         query_log(request, inst, querystr);
1503         DEBUG2("rlm_sql (%s) in sql_postauth: query is %s",
1504                inst->config->xlat_name, querystr);
1505
1506         /* Initialize the sql socket */
1507         sqlsocket = sql_get_socket(inst);
1508         if (sqlsocket == NULL)
1509                 return RLM_MODULE_FAIL;
1510
1511         /* Process the query */
1512         if (rlm_sql_query(sqlsocket, inst, querystr)) {
1513                 radlog(L_ERR, "rlm_sql (%s) in sql_postauth: Database query error - %s",
1514                        inst->config->xlat_name,
1515                        (char *)(inst->module->sql_error)(sqlsocket, inst->config));
1516                 sql_release_socket(inst, sqlsocket);
1517                 return RLM_MODULE_FAIL;
1518         }
1519         (inst->module->sql_finish_query)(sqlsocket, inst->config);
1520
1521         sql_release_socket(inst, sqlsocket);
1522         return RLM_MODULE_OK;
1523 }
1524
1525 /* globally exported name */
1526 module_t rlm_sql = {
1527         RLM_MODULE_INIT,
1528         "SQL",
1529         RLM_TYPE_THREAD_SAFE,   /* type: reserved */
1530         rlm_sql_instantiate,    /* instantiation */
1531         rlm_sql_detach,         /* detach */
1532         {
1533                 NULL,                   /* authentication */
1534                 rlm_sql_authorize,      /* authorization */
1535                 NULL,                   /* preaccounting */
1536                 rlm_sql_accounting,     /* accounting */
1537                 rlm_sql_checksimul,     /* checksimul */
1538                 NULL,                   /* pre-proxy */
1539                 NULL,                   /* post-proxy */
1540                 rlm_sql_postauth        /* post-auth */
1541         },
1542 };