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