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