New module as supplied by Siemens
[freeradius.git] / src / modules / rlm_smsotp / rlm_smsotp.c
1 /*\r
2  * rlm_smsotp.c\r
3  *\r
4  * Version:     $Id: rlm_smsotp.c,v 0.2 2009/02/03 08:06:44 wofflger Exp $\r
5  *\r
6  *   This program is free software; you can redistribute it and/or modify\r
7  *   it under the terms of the GNU General Public License as published by\r
8  *   the Free Software Foundation; either version 2 of the License, or\r
9  *   (at your option) any later version.\r
10  *\r
11  *   This program is distributed in the hope that it will be useful,\r
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14  *   GNU General Public License for more details.\r
15  *\r
16  *   You should have received a copy of the GNU General Public License\r
17  *   along with this program; if not, write to the Free Software\r
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA\r
19  *\r
20  * Copyright 2000,2006  The FreeRADIUS server project\r
21  * Copyright 2009  Siemens AG, Holger Wolff holger.wolff@siemens.com\r
22  */\r
23 \r
24 #include <freeradius-devel/ident.h>\r
25 RCSID("$Id: rlm_smsotp.c,v 0.2 2009/02/03 08:06:44 wofflger Exp $")\r
26 \r
27 #include <freeradius-devel/radiusd.h>\r
28 #include <freeradius-devel/modules.h>\r
29 #include <sys/un.h>\r
30 \r
31 #include "rlm_smsotp.h"\r
32 \r
33 /*\r
34  *      A mapping of configuration file names to internal variables.\r
35  *\r
36  *      Note that the string is dynamically allocated, so it MUST\r
37  *      be freed.  When the configuration file parse re-reads the string,\r
38  *      it free's the old one, and strdup's the new one, placing the pointer\r
39  *      to the strdup'd string into 'config.string'.  This gets around\r
40  *      buffer over-flows.\r
41  */\r
42 \r
43 static const CONF_PARSER module_config[] = {\r
44   { "smsotp_socket", PW_TYPE_STRING_PTR, offsetof(rlm_smsotp_t, smsotp_socket), NULL, SMSOTP_SOCKET },\r
45   { "smsotp_challengemessage", PW_TYPE_STRING_PTR, offsetof(rlm_smsotp_t, smsotp_challengemessage), NULL, SMSOTP_CHALLENGEMESSAGE },\r
46   { "smsotp_authtype", PW_TYPE_STRING_PTR, offsetof(rlm_smsotp_t, smsotp_authtype), NULL, SMSOTP_AUTHTYPE },\r
47 \r
48   { NULL, -1, 0, NULL, NULL }           /* end the list */\r
49 };\r
50 \r
51 \r
52 /* socket forward declarations begin */\r
53 static int smsotp_connect(const char *path);\r
54 static smsotp_fd_t * smsotp_getfd(const rlm_smsotp_t *opt);\r
55 static void smsotp_putfd(smsotp_fd_t *fdp, int disconnect);\r
56 static int smsotp_read(smsotp_fd_t *fdp, char *buf, size_t len);\r
57 static int smsotp_write(smsotp_fd_t *fdp, const char *buf, size_t len);\r
58 /* socket forward declarations end */\r
59 \r
60 \r
61 /*\r
62  *      Do any per-module initialization that is separate to each\r
63  *      configured instance of the module.  e.g. set up connections\r
64  *      to external databases, read configuration files, set up\r
65  *      dictionary entries, etc.\r
66  *\r
67  *      If configuration information is given in the config section\r
68  *      that must be referenced in later calls, store a handle to it\r
69  *      in *instance otherwise put a null pointer there.\r
70  */\r
71 static int smsotp_instantiate(CONF_SECTION *conf, void **instance)\r
72 {\r
73         rlm_smsotp_t *data;\r
74 \r
75         /*\r
76          *      Set up a storage area for instance data\r
77          */\r
78         data = rad_malloc(sizeof(*data));\r
79         if (!data) {\r
80                 return -1;\r
81         }\r
82         memset(data, 0, sizeof(*data));\r
83 \r
84         /*\r
85          *      If the configuration parameters can't be parsed, then\r
86          *      fail.\r
87          */\r
88         if (cf_section_parse(conf, data, module_config) < 0) {\r
89                 free(data);\r
90                 return -1;\r
91         }\r
92 \r
93         *instance = data;\r
94 \r
95         return 0;\r
96 }\r
97 \r
98 /*\r
99  *      Authenticate the user with the given password.\r
100  */\r
101 static int smsotp_authenticate(void *instance, REQUEST *request)\r
102 {\r
103         VALUE_PAIR *state;\r
104         VALUE_PAIR *reply;\r
105         rlm_smsotp_t *opt = instance;\r
106         char SocketReply[1000];\r
107         int SocketReplyLen;\r
108 \r
109         /* quiet the compiler */\r
110         instance = instance;\r
111         request = request;\r
112 \r
113   smsotp_fd_t *fdp;\r
114 \r
115   fdp = smsotp_getfd(instance);\r
116   if (!fdp || fdp->fd == -1)\r
117     return RLM_MODULE_FAIL;\r
118 \r
119         /* Get greeting */\r
120         SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
121 \r
122         /*\r
123          *  Look for the 'state' attribute.\r
124          */\r
125         state = pairfind(request->packet->vps, PW_STATE);\r
126         if (state != NULL) {\r
127                 DEBUG("rlm_smsotp: Found reply to access challenge");\r
128                 \r
129                 /* set username */\r
130                 smsotp_write(fdp, "check otp for ", 14);\r
131                 smsotp_write(fdp, (const char *) request->username->vp_strvalue, sizeof(request->username->vp_strvalue));\r
132                 smsotp_write(fdp, "\n", 1);\r
133                 SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
134                 \r
135                 /* set otp password */\r
136                 smsotp_write(fdp, "user otp is ", 12);\r
137                 smsotp_write(fdp, (const char *) request->password->vp_strvalue, sizeof(request->password->vp_strvalue));\r
138                 smsotp_write(fdp, "\n", 1);\r
139                 SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
140                 \r
141                 /* set uuid */\r
142                 smsotp_write(fdp, "otp id is ", 10);\r
143                 smsotp_write(fdp, (const char *) state->vp_strvalue, 36); /* smsotp_write(fdp, (const char *) state->vp_strvalue, sizeof(state->vp_strvalue)); */\r
144                 smsotp_write(fdp, "\n", 1);\r
145                 SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
146                 \r
147                 /* now check the otp */\r
148                 smsotp_write(fdp, "get check result\n", 17);\r
149                 SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
150                 \r
151                 /* end the sesssion */\r
152                 smsotp_write(fdp, "quit\n", 5);\r
153                 smsotp_putfd(fdp, 1);\r
154                 \r
155                 (void) radlog(L_AUTH, "rlm_smsotp: SocketReply is %s ",SocketReply);\r
156                 \r
157                 if (strcmp(SocketReply,"OK") == 0)\r
158                         return RLM_MODULE_OK;\r
159                 return RLM_MODULE_FAIL;\r
160         }\r
161 \r
162         DEBUG("rlm_smsotp: Generate OTP");\r
163   \r
164         /* set username */\r
165   smsotp_write(fdp, "generate otp for ", 17);\r
166   smsotp_write(fdp, (const char *) request->username->vp_strvalue, sizeof(request->username->vp_strvalue));\r
167   smsotp_write(fdp, "\n", 1);\r
168         SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));\r
169 \r
170         /* end the sesssion */\r
171   smsotp_write(fdp, "quit\n", 5);\r
172         smsotp_putfd(fdp, 1);\r
173 \r
174         (void) radlog(L_AUTH, "rlm_smsotp: Uniq id is %s ",SocketReply);\r
175 \r
176         /* check the return string */\r
177         if (strcmp(SocketReply,"FAILED") == 0) { /* smsotp script returns a error */\r
178                 return RLM_MODULE_FAIL;\r
179         } else {\r
180                 /*\r
181                  *  Create the challenge, and add it to the reply.\r
182                  */\r
183                 \r
184                 reply = pairmake("Reply-Message", opt->smsotp_challengemessage, T_OP_EQ);\r
185                 pairadd(&request->reply->vps, reply);\r
186                 state = pairmake("State", SocketReply, T_OP_EQ);\r
187                 pairadd(&request->reply->vps, state);\r
188         \r
189                 /*\r
190                  *  Mark the packet as an Access-Challenge packet.\r
191                  *\r
192                  *  The server will take care of sending it to the user.\r
193                  */\r
194                 request->reply->code = PW_ACCESS_CHALLENGE;\r
195                 DEBUG("rlm_smsotp: Sending Access-Challenge.");\r
196         \r
197                 return RLM_MODULE_HANDLED;\r
198         }\r
199 }\r
200 \r
201 /*\r
202  *      Find the named user in this modules database.  Create the set\r
203  *      of attribute-value pairs to check and reply with for this user\r
204  *      from the database. The authentication code only needs to check\r
205  *      the password, the rest is done here.\r
206  */\r
207 static int smsotp_authorize(void *instance, REQUEST *request)\r
208 {\r
209         VALUE_PAIR *state;\r
210         rlm_smsotp_t *opt = instance;\r
211 \r
212         /* quiet the compiler */\r
213         instance = instance;\r
214         request = request;\r
215 \r
216         /*\r
217          *  Look for the 'state' attribute.\r
218          */\r
219         state = pairfind(request->packet->vps, PW_STATE);\r
220         if (state != NULL) {\r
221                 DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",opt->smsotp_authtype);\r
222                 \r
223                 pairdelete(&request->config_items, PW_AUTH_TYPE); /* delete old auth-type */\r
224                 pairadd(&request->config_items, pairmake("Auth-Type", opt->smsotp_authtype, T_OP_SET));\r
225         }\r
226 \r
227         return RLM_MODULE_OK;\r
228 }\r
229 \r
230 /*\r
231  *      Only free memory we allocated.  The strings allocated via\r
232  *      cf_section_parse() do not need to be freed.\r
233  */\r
234 static int smsotp_detach(void *instance)\r
235 {\r
236         free(instance);\r
237         return 0;\r
238 }\r
239 \r
240 /* forward declarations */\r
241 static smsotp_fd_t *smsotp_fd_head;\r
242 static pthread_mutex_t smsotp_fd_head_mutex = PTHREAD_MUTEX_INITIALIZER;\r
243 /* forward declarations end */\r
244 \r
245 /* socket functions begin */\r
246 /* connect to socket and return fd */\r
247 static int smsotp_connect(const char *path)\r
248 {\r
249   int fd;\r
250   struct sockaddr_un sa;\r
251   size_t sp_len;                /* sun_path length (strlen) */\r
252 \r
253   /* setup for unix domain socket */\r
254   sp_len = strlen(path);\r
255   if (sp_len > sizeof(sa.sun_path) - 1) {\r
256     (void) radlog(L_ERR, "rlm_smsotp: %s: socket name too long", __func__);\r
257     return -1;\r
258   }\r
259   sa.sun_family = AF_UNIX;\r
260   (void) strcpy(sa.sun_path, path);\r
261 \r
262   /* connect to socket */\r
263   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {\r
264     (void) radlog(L_ERR, "rlm_smsotp: %s: socket: %s", __func__, strerror(errno));\r
265     return -1;\r
266   }\r
267   if (connect(fd, (struct sockaddr *) &sa, sizeof(sa.sun_family) + sp_len) == -1) {\r
268     (void) radlog(L_ERR, "rlm_smsotp: %s: connect(%s): %s", __func__, path, strerror(errno));\r
269     (void) close(fd);\r
270     return -1;\r
271   }\r
272   return fd;\r
273 }\r
274 \r
275 /*\r
276  * Retrieve an fd (from pool) to use for socket connection.\r
277  * It'd be simpler to use TLS but FR can have lots of threads\r
278  * and we don't want to waste fd's that way.\r
279  * We can't have a global fd because we'd then be pipelining\r
280  * requests to otpd and we have no way to demultiplex\r
281  * the responses.\r
282  */\r
283 static smsotp_fd_t * smsotp_getfd(const rlm_smsotp_t *opt)\r
284 {\r
285   int rc;\r
286   smsotp_fd_t *fdp;\r
287 \r
288   /* walk the connection pool looking for an available fd */\r
289   for (fdp = smsotp_fd_head; fdp; fdp = fdp->next) {\r
290     rc = smsotp_pthread_mutex_trylock(&fdp->mutex);\r
291     if (!rc)\r
292       if (!strcmp(fdp->path, opt->smsotp_socket))       /* could just use == */\r
293         break;\r
294   }\r
295 \r
296   if (!fdp) {\r
297     /* no fd was available, add a new one */\r
298     fdp = rad_malloc(sizeof(*fdp));\r
299     smsotp_pthread_mutex_init(&fdp->mutex, NULL);\r
300     smsotp_pthread_mutex_lock(&fdp->mutex);\r
301     /* insert new fd at head */\r
302     smsotp_pthread_mutex_lock(&smsotp_fd_head_mutex);\r
303     fdp->next = smsotp_fd_head;\r
304     smsotp_fd_head = fdp;\r
305     smsotp_pthread_mutex_unlock(&smsotp_fd_head_mutex);\r
306     /* initialize */\r
307     fdp->path = opt->smsotp_socket;\r
308     fdp->fd = -1;\r
309   }\r
310 \r
311   /* establish connection */\r
312   if (fdp->fd == -1)\r
313     fdp->fd = smsotp_connect(fdp->path);\r
314 \r
315   return fdp;\r
316 }\r
317 \r
318 /* release fd, and optionally disconnect from otpd */\r
319 static void smsotp_putfd(smsotp_fd_t *fdp, int disconnect)\r
320 {\r
321   if (disconnect) {\r
322     (void) close(fdp->fd);\r
323     fdp->fd = -1;\r
324   }\r
325 \r
326   /* make connection available to another thread */\r
327   smsotp_pthread_mutex_unlock(&fdp->mutex);\r
328 }\r
329 \r
330 /*\r
331  * Full read with logging, and close on failure.\r
332  * Returns nread on success, 0 on EOF, -1 on other failures.\r
333  */\r
334 static int smsotp_read(smsotp_fd_t *fdp, char *buf, size_t len)\r
335 {\r
336   ssize_t n;\r
337   size_t nread = 0;     /* bytes read into buf */\r
338   \r
339   fd_set rfds;\r
340   struct timeval tv;\r
341   int retval;\r
342   FD_ZERO(&rfds);\r
343   FD_SET(fdp->fd, &rfds);\r
344   tv.tv_sec = 0;\r
345   tv.tv_usec = 0;\r
346 \r
347   while (nread < len) {\r
348     if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {\r
349       if (errno == EINTR) {\r
350         continue;\r
351       } else {\r
352         (void) radlog(L_ERR, "rlm_smsotp: %s: read from socket: %s", __func__, strerror(errno));\r
353         smsotp_putfd(fdp, 1);\r
354         return -1;\r
355       }\r
356     }\r
357     if (!n) {\r
358       (void) radlog(L_ERR, "rlm_smsotp: %s: socket disconnect", __func__);\r
359       smsotp_putfd(fdp, 1);\r
360       return 0;\r
361     }\r
362     nread += n;\r
363 //    DEBUG("smsotp_read ... read more ?");\r
364     \r
365     // check if more data is avalible\r
366                 retval = select(1, &rfds, NULL, NULL, &tv);\r
367                 if (!retval) {\r
368                         buf[nread]= '\0';\r
369                         break;\r
370                 }\r
371 //    DEBUG("smsotp_read ... read more ! YES !");\r
372 \r
373   } /*while (more to read) */\r
374 \r
375   return nread;\r
376 }\r
377 \r
378 /*\r
379  * Full write with logging, and close on failure.\r
380  * Returns 0 on success, errno on failure.\r
381  */\r
382 static int smsotp_write(smsotp_fd_t *fdp, const char *buf, size_t len)\r
383 {\r
384   size_t nleft = len;\r
385   ssize_t nwrote;\r
386 \r
387   while (nleft) {\r
388     if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {\r
389       if (errno == EINTR || errno == EPIPE) {\r
390         continue;\r
391       } else {\r
392         (void) radlog(L_ERR, "rlm_smsotp: %s: write to socket: %s", __func__, strerror(errno));\r
393         smsotp_putfd(fdp, 1);\r
394         return errno;\r
395       }\r
396     }\r
397     nleft -= nwrote;\r
398   }\r
399 \r
400   return 0;\r
401 }\r
402 /* socket functions end */\r
403 \r
404 \r
405 /* mutex functions begin*/\r
406 /* guaranteed initialization */\r
407 void _smsotp_pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr, const char *caller)\r
408 {\r
409   int rc;\r
410 \r
411   if ((rc = pthread_mutex_init(mutexp, attr))) {\r
412     (void) radlog(L_ERR|L_CONS, "rlm_smsotp: %s: pthread_mutex_init: %s", caller, strerror(rc));\r
413     exit(1);\r
414   }\r
415 }\r
416 \r
417 /* guaranteed lock */\r
418 void _smsotp_pthread_mutex_lock(pthread_mutex_t *mutexp, const char *caller)\r
419 {\r
420   int rc;\r
421 \r
422   if ((rc = pthread_mutex_lock(mutexp))) {\r
423     (void) radlog(L_ERR|L_CONS, "rlm_smsotp: %s: pthread_mutex_lock: %s", caller, strerror(rc));\r
424     exit(1);\r
425   }\r
426 }\r
427 \r
428 /* guaranteed trylock */\r
429 int _smsotp_pthread_mutex_trylock(pthread_mutex_t *mutexp, const char *caller)\r
430 {\r
431   int rc;\r
432 \r
433   rc = pthread_mutex_trylock(mutexp);\r
434   if (rc && rc != EBUSY) {\r
435     (void) radlog(L_ERR|L_CONS, "rlm_smsotp: %s: pthread_mutex_trylock: %s", caller, strerror(rc));\r
436     exit(1);\r
437   }\r
438 \r
439   return rc;\r
440 }\r
441 \r
442 /* guaranteed unlock */\r
443 void _smsotp_pthread_mutex_unlock(pthread_mutex_t *mutexp, const char *caller)\r
444 {\r
445   int rc;\r
446 \r
447   if ((rc = pthread_mutex_unlock(mutexp))) {\r
448     (void) radlog(L_ERR|L_CONS, "rlm_smsotp: %s: pthread_mutex_unlock: %s", caller, strerror(rc));\r
449     exit(1);\r
450   }\r
451 }\r
452 /* mutex functions end */\r
453 \r
454 \r
455 /*\r
456  *      The module name should be the only globally exported symbol.\r
457  *      That is, everything else should be 'static'.\r
458  *\r
459  *      If the module needs to temporarily modify it's instantiation\r
460  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.\r
461  *      The server will then take care of ensuring that the module\r
462  *      is single-threaded.\r
463  */\r
464 module_t rlm_smsotp = {\r
465         RLM_MODULE_INIT,\r
466         "smsotp",\r
467         RLM_TYPE_THREAD_SAFE,           /* type */\r
468         smsotp_instantiate,             /* instantiation */\r
469         smsotp_detach,                  /* detach */\r
470         {\r
471                 smsotp_authenticate,    /* authentication */\r
472                 smsotp_authorize,       /* authorization */\r
473                 NULL,   /* preaccounting */\r
474                 NULL,   /* accounting */\r
475                 NULL,   /* checksimul */\r
476                 NULL,                   /* pre-proxy */\r
477                 NULL,                   /* post-proxy */\r
478                 NULL                    /* post-auth */\r
479         },\r
480 };\r