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