Jeff Carneal <jeff@apex.net>
[freeradius.git] / src / modules / rlm_sql / rlm_sql.c
1 /***************************************************************************
2 *  rlm_sql.c                          rlm_sql - FreeRADIUS SQL Module      *
3 *                                                                          *
4 *      Main SQL module file. Most ICRADIUS code is located in sql.c        *
5 *      $Id$
6 *                                                                          *
7 *                                     Mike Machado <mike@innercite.com>    *
8 ***************************************************************************/
9 static const char rcsid[] = "$Id$";
10
11 #include "autoconf.h"
12
13 #include <stdio.h>
14 #include <sys/stat.h>
15 #include <stdlib.h>
16
17 #include <time.h>
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <string.h>
22
23 #include "radiusd.h"
24 #include "modules.h"
25 #include "conffile.h"
26 #include "rlm_sql.h"
27
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31
32 static SQL_CONFIG config = {
33         NULL,                   /* "localhost" */
34         NULL,                   /* "root" */
35         NULL,                   /* "" */
36         NULL,                   /* "radius" */
37         NULL,                   /* "radacct" */
38         NULL,                   /* "radcheck" */
39         NULL,                   /* "radreply" */
40         NULL,                   /* "radgroupcheck" */
41         NULL,                   /* "radgroupreply" */
42         NULL,                   /* "usergroup" */
43         NULL,                   /* "realm" */
44         NULL,                   /* "realmgroup" */
45         NULL,                   /* "nas" */
46         NULL,                   /* "dictionary" */
47         0,
48         0,
49         1,
50         5
51 };
52
53 static CONF_PARSER module_config[] = {
54         { "sensitiveusername",          PW_TYPE_BOOLEAN,
55           &config.sensitiveusername,    "1" },
56         { "deletestalesessions",        PW_TYPE_BOOLEAN,
57           &config.deletestalesessions,  "0" },
58         { "sqltrace",                   PW_TYPE_BOOLEAN,
59           &config.sqltrace,             "0" },
60         { "max_sql_socks",              PW_TYPE_INTEGER,
61           &config.max_sql_socks,        Stringify(MAX_SQL_SOCKS) },
62         { "server",                     PW_TYPE_STRING_PTR,
63           &config.sql_server,           "localhost" },
64         { "login",                      PW_TYPE_STRING_PTR,
65           &config.sql_login,            "" },
66         { "password",                   PW_TYPE_STRING_PTR,
67           &config.sql_password,         "" },
68         { "db",                         PW_TYPE_STRING_PTR,
69           &config.sql_db,               "radius" },
70         { "authcheck_table",            PW_TYPE_STRING_PTR,
71           &config.sql_authcheck_table,  "radcheck" },
72         { "authreply_table",            PW_TYPE_STRING_PTR,
73           &config.sql_authreply_table,  "radreply" },
74         { "groupcheck_table",           PW_TYPE_STRING_PTR,
75           &config.sql_groupcheck_table, "radgroupcheck" },
76         { "groupreply_table",           PW_TYPE_STRING_PTR,
77           &config.sql_groupreply_table, "radgroupreply" },
78         { "usergroup_table",            PW_TYPE_STRING_PTR,
79           &config.sql_usergroup_table,  "usergroup" },
80         { "realmgroup_table",           PW_TYPE_STRING_PTR,
81           &config.sql_realmgroup_table, "realmgroup" },
82         { "acct_table",                 PW_TYPE_STRING_PTR,
83           &config.sql_acct_table,       "radacct" },
84         { "nas_table",                  PW_TYPE_STRING_PTR,
85           &config.sql_nas_table,        "nas" },
86         { "realm_table",                PW_TYPE_STRING_PTR,
87           &config.sql_realm_table,      "realms" },
88         { "dict_table",                 PW_TYPE_STRING_PTR,
89           &config.sql_dict_table,       "dictionary" },
90         { NULL, -1, NULL, NULL }
91 };
92
93
94 /***********************************************************************
95  * start of main routines
96  ***********************************************************************/
97
98 static int rlm_sql_init(void) {
99
100         /* Where is the flag that tells us about a HUP?*/
101         int     reload = 0;
102
103         if ((sql = malloc(sizeof(SQL))) == NULL) {
104                 radlog(L_ERR|L_CONS, "no memory");
105                 exit(1);
106         }
107
108 /*
109         if (reload)
110                 free(sql->config);
111         if ((sql->config = malloc(sizeof(SQL_CONFIG))) == NULL) {
112                 radlog(L_ERR|L_CONS, "no memory");
113                 exit(1);
114         }
115 */
116
117         sql_init(module_config, &config, reload);
118
119        return 0;
120 }
121
122 static int rlm_sql_destroy(void) {
123
124   return 0;
125 }
126
127
128 static int rlm_sql_authorize(REQUEST *request)
129 {
130         int             nas_port = 0;
131         VALUE_PAIR      *check_tmp = NULL;
132         VALUE_PAIR      *reply_tmp = NULL;
133         VALUE_PAIR      *tmp;
134         int             found = 0;
135         char            *name;
136         SQLSOCK         *socket;
137         
138         name = request->username->strvalue;
139
140        /*
141         *      Check for valid input, zero length names not permitted
142         */
143        if (name[0] == 0) {
144                radlog(L_ERR, "zero length username not permitted\n");
145                return -1;
146        }
147
148         socket = sql_get_socket();
149
150        /*
151         *      Find the NAS port ID.
152         */
153        if ((tmp = pairfind(request->packet->vps, PW_NAS_PORT_ID)) != NULL)
154                nas_port = tmp->lvalue;
155
156        /*
157         *      Find the entry for the user.
158         */
159        if ((found = sql_getvpdata(socket, sql->config->sql_authcheck_table, &check_tmp, name, PW_VP_USERDATA)) > 0) {
160                sql_getvpdata(socket, sql->config->sql_groupcheck_table, &check_tmp, name, PW_VP_GROUPDATA);
161                sql_getvpdata(socket, sql->config->sql_authreply_table, &reply_tmp, name, PW_VP_USERDATA);
162                sql_getvpdata(socket, sql->config->sql_groupreply_table, &reply_tmp, name, PW_VP_GROUPDATA);
163        } else {
164                
165                int gcheck, greply;
166                gcheck = sql_getvpdata(socket, sql->config->sql_groupcheck_table, &check_tmp, "DEFAULT", PW_VP_GROUPDATA);
167                greply = sql_getvpdata(socket, sql->config->sql_groupreply_table, &reply_tmp, "DEFAULT", PW_VP_GROUPDATA);
168                if (gcheck && greply)
169                        found = 1;
170        }
171        sql_release_socket(socket);
172        
173        if (!found) {
174                DEBUG2("User %s not found and DEFAULT not found", name);
175                return RLM_MODULE_NOTFOUND;
176        }
177        
178        if (paircmp(request->packet->vps, check_tmp, &reply_tmp) != 0) {
179                DEBUG2("Pairs do not match [%s]", name);
180                return RLM_MODULE_OK;
181        }
182        
183        pairmove(&request->reply->vps, &reply_tmp);
184        pairmove(&request->config_items, &check_tmp);
185        pairfree(reply_tmp);
186        pairfree(check_tmp);
187        
188        
189         return RLM_MODULE_OK;
190 }
191
192 static int rlm_sql_authenticate(REQUEST *request)
193 {
194         
195         SQL_ROW         row;
196         SQLSOCK         *socket;
197         char            *querystr;
198         char            escaped_user[AUTH_STRING_LEN*3];
199         char            *user;
200         const char      query[] = "SELECT Value FROM %s WHERE UserName = '%s' AND Attribute = 'Password'";
201         
202         user = request->username->strvalue;
203         
204         /*
205          *      Ensure that a password attribute exists.
206          */
207         if ((request->password == NULL) ||
208             (request->password->length == 0) ||
209             (request->password->attribute != PW_PASSWORD)) {
210                 radlog(L_AUTH, "rlm_sql: Attribute \"Password\" is required for authentication.");
211                 return RLM_MODULE_INVALID;
212         }
213         
214         sql_escape_string(escaped_user, user, strlen(user));
215         
216         /*
217          *      This should really be replaced with a static buffer...
218          */
219         if ((querystr = malloc(strlen(escaped_user) +
220                                strlen(sql->config->sql_authcheck_table) +
221                                sizeof(query))) == NULL) {
222                 radlog(L_ERR|L_CONS, "no memory");
223                 exit(1);
224         }
225         
226         sprintf(querystr, query, sql->config->sql_authcheck_table, escaped_user);
227         socket = sql_get_socket();
228         sql_select_query(socket, querystr);
229         row = sql_fetch_row(socket);
230         sql_finish_select_query(socket);
231         free(querystr);
232         
233         if (strncmp(request->password->strvalue, row[0], request->password->length) != 0)
234                 return RLM_MODULE_REJECT;
235         else
236                 return RLM_MODULE_OK;
237 }
238
239 /*
240  *      Accounting: does nothing for now.
241  */
242 static int rlm_sql_accounting(REQUEST *request) {
243
244         time_t          nowtime;
245         struct tm       *tim;
246         char            datebuf[20];
247         VALUE_PAIR      *pair;
248         SQLACCTREC      *sqlrecord;
249         SQLSOCK         *socket;
250         DICT_VALUE      *dval;
251
252
253         if ((sqlrecord = malloc(sizeof(SQLACCTREC))) == NULL) {
254                 radlog(L_ERR|L_CONS, "no memory");
255                 exit(1);        
256         }
257         
258         pair = request->packet->vps;
259         while(pair != (VALUE_PAIR *)NULL) {
260
261            /* Check the pairs to see if they are anything we are interested in. */
262             switch(pair->attribute) {
263                 case PW_ACCT_SESSION_ID:
264                         strncpy(sqlrecord->AcctSessionId, pair->strvalue, SQLBIGREC);
265                         break;
266                         
267                 case PW_USER_NAME:
268                         strncpy(sqlrecord->UserName, pair->strvalue, SQLBIGREC);
269                         break;
270                         
271                 case PW_NAS_IP_ADDRESS:
272                         ip_ntoa(sqlrecord->NASIPAddress, pair->lvalue);
273                         //ipaddr2str(sqlrecord->NASIPAddress, pair->lvalue);
274                         break;
275
276                 case PW_NAS_PORT_ID:
277                         sqlrecord->NASPortId = pair->lvalue;
278                         break;
279
280                 case PW_NAS_PORT_TYPE:
281                                                 dval = dict_valbyattr(PW_NAS_PORT_TYPE, pair->lvalue);
282                                                 if(dval != NULL) {
283                                 strncpy(sqlrecord->NASPortType, dval->attrname, SQLBIGREC);
284                                                 }
285                                                 break;
286
287                 case PW_ACCT_STATUS_TYPE:
288                                                 sqlrecord->AcctStatusTypeId = pair->lvalue;
289                                                 dval = dict_valbyattr(PW_ACCT_STATUS_TYPE, pair->lvalue);
290                                                 if(dval != NULL) {
291                                 strncpy(sqlrecord->AcctStatusType, dval->attrname, SQLBIGREC);
292                                                 }
293                                                 break;
294
295                 case PW_ACCT_SESSION_TIME:
296                         sqlrecord->AcctSessionTime = pair->lvalue;
297                         break;
298
299                 case PW_ACCT_AUTHENTIC:
300                                                 dval = dict_valbyattr(PW_ACCT_AUTHENTIC, pair->lvalue);
301                                                 if(dval != NULL) {
302                                 strncpy(sqlrecord->AcctAuthentic, dval->attrname, SQLBIGREC);
303                                                 }
304                                                 break;
305
306                 case PW_CONNECT_INFO:
307                         strncpy(sqlrecord->ConnectInfo, pair->strvalue, SQLBIGREC);
308                         break;
309
310                 case PW_ACCT_INPUT_OCTETS:
311                         sqlrecord->AcctInputOctets = pair->lvalue;
312                         break;
313
314                 case PW_ACCT_OUTPUT_OCTETS:
315                         sqlrecord->AcctOutputOctets = pair->lvalue;
316                         break;
317
318                 case PW_CALLED_STATION_ID:
319                         strncpy(sqlrecord->CalledStationId, pair->strvalue, SQLLILREC);
320                         break;
321
322                 case PW_CALLING_STATION_ID:
323                         strncpy(sqlrecord->CallingStationId, pair->strvalue, SQLLILREC);
324                         break;
325
326 /*                case PW_ACCT_TERMINATE_CAUSE:
327                                                 dval = dict_valbyattr(PW_ACCT_TERMINATE_CAUSE, pair->lvalue);
328                                                 if(dval != NULL) {
329                                 strncpy(sqlrecord->AcctTerminateCause, dval->attrname, SQLBIGREC);
330                                                 }
331                                                 break;
332 */
333
334
335                 case PW_SERVICE_TYPE:
336                                                 dval = dict_valbyattr(PW_SERVICE_TYPE, pair->lvalue);
337                                                 if(dval != NULL) {
338                                 strncpy(sqlrecord->ServiceType, dval->attrname, SQLBIGREC);
339                                                 }
340                                                 break;
341
342                 case PW_FRAMED_PROTOCOL:
343                                                 dval = dict_valbyattr(PW_FRAMED_PROTOCOL, pair->lvalue);
344                                                 if(dval != NULL) {
345                                 strncpy(sqlrecord->FramedProtocol, dval->attrname, SQLBIGREC);
346                                                 }
347                                                 break;
348
349                 case PW_FRAMED_IP_ADDRESS:
350                         ip_ntoa(sqlrecord->FramedIPAddress, pair->lvalue);
351                         //ipaddr2str(sqlrecord->FramedIPAddress, pair->lvalue);
352                         break;
353
354                 case PW_ACCT_DELAY_TIME:
355                         sqlrecord->AcctDelayTime = pair->lvalue;
356                         break;
357
358                 default:
359                         break;
360                 }
361
362                 pair = pair->next;
363         }
364
365
366         nowtime = request->timestamp - sqlrecord->AcctDelayTime;
367         tim = localtime(&nowtime);
368         strftime(datebuf, sizeof(datebuf), "%Y%m%d%H%M%S", tim);
369
370         strncpy(sqlrecord->AcctTimeStamp, datebuf, 20);
371        
372
373         socket = sql_get_socket();
374         if (sql_save_acct(socket, sqlrecord) == 0)
375                 return RLM_MODULE_FAIL;
376         sql_release_socket(socket);
377
378         return RLM_MODULE_OK;
379 }
380
381
382 /* globally exported name */
383 module_t rlm_sql = {
384   "SQL",
385   0,                    /* type: reserved */
386   rlm_sql_init,         /* initialization */
387   NULL,                 /* instantiation */
388   rlm_sql_authorize,    /* authorization */
389   rlm_sql_authenticate, /* authentication */
390   NULL,                 /* preaccounting */
391   rlm_sql_accounting,   /* accounting */
392   NULL,                 /* detach */
393   rlm_sql_destroy,      /* destroy */
394 };