4 * Version: $Id: rlm_smsotp.c,v 0.2 2009/02/03 08:06:44 wofflger Exp $
\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
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
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
20 * Copyright 2000,2006 The FreeRADIUS server project
\r
21 * Copyright 2009 Siemens AG, Holger Wolff holger.wolff@siemens.com
\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
27 #include <freeradius-devel/radiusd.h>
\r
28 #include <freeradius-devel/modules.h>
\r
31 #include "rlm_smsotp.h"
\r
34 * A mapping of configuration file names to internal variables.
\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
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
48 { NULL, -1, 0, NULL, NULL } /* end the list */
\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
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
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
71 static int smsotp_instantiate(CONF_SECTION *conf, void **instance)
\r
76 * Set up a storage area for instance data
\r
78 data = rad_malloc(sizeof(*data));
\r
82 memset(data, 0, sizeof(*data));
\r
85 * If the configuration parameters can't be parsed, then
\r
88 if (cf_section_parse(conf, data, module_config) < 0) {
\r
99 * Authenticate the user with the given password.
\r
101 static int smsotp_authenticate(void *instance, REQUEST *request)
\r
105 rlm_smsotp_t *opt = instance;
\r
106 char SocketReply[1000];
\r
107 int SocketReplyLen;
\r
109 /* quiet the compiler */
\r
110 instance = instance;
\r
115 fdp = smsotp_getfd(instance);
\r
116 if (!fdp || fdp->fd == -1)
\r
117 return RLM_MODULE_FAIL;
\r
120 SocketReplyLen = smsotp_read(fdp, (char *) SocketReply, sizeof(SocketReply));
\r
123 * Look for the 'state' attribute.
\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
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
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
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
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
151 /* end the sesssion */
\r
152 smsotp_write(fdp, "quit\n", 5);
\r
153 smsotp_putfd(fdp, 1);
\r
155 (void) radlog(L_AUTH, "rlm_smsotp: SocketReply is %s ",SocketReply);
\r
157 if (strcmp(SocketReply,"OK") == 0)
\r
158 return RLM_MODULE_OK;
\r
159 return RLM_MODULE_FAIL;
\r
162 DEBUG("rlm_smsotp: Generate OTP");
\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
170 /* end the sesssion */
\r
171 smsotp_write(fdp, "quit\n", 5);
\r
172 smsotp_putfd(fdp, 1);
\r
174 (void) radlog(L_AUTH, "rlm_smsotp: Uniq id is %s ",SocketReply);
\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
181 * Create the challenge, and add it to the reply.
\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
190 * Mark the packet as an Access-Challenge packet.
\r
192 * The server will take care of sending it to the user.
\r
194 request->reply->code = PW_ACCESS_CHALLENGE;
\r
195 DEBUG("rlm_smsotp: Sending Access-Challenge.");
\r
197 return RLM_MODULE_HANDLED;
\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
207 static int smsotp_authorize(void *instance, REQUEST *request)
\r
210 rlm_smsotp_t *opt = instance;
\r
212 /* quiet the compiler */
\r
213 instance = instance;
\r
217 * Look for the 'state' attribute.
\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
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
227 return RLM_MODULE_OK;
\r
231 * Only free memory we allocated. The strings allocated via
\r
232 * cf_section_parse() do not need to be freed.
\r
234 static int smsotp_detach(void *instance)
\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
245 /* socket functions begin */
\r
246 /* connect to socket and return fd */
\r
247 static int smsotp_connect(const char *path)
\r
250 struct sockaddr_un sa;
\r
251 size_t sp_len; /* sun_path length (strlen) */
\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
259 sa.sun_family = AF_UNIX;
\r
260 (void) strcpy(sa.sun_path, path);
\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
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
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
283 static smsotp_fd_t * smsotp_getfd(const rlm_smsotp_t *opt)
\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
292 if (!strcmp(fdp->path, opt->smsotp_socket)) /* could just use == */
\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
307 fdp->path = opt->smsotp_socket;
\r
311 /* establish connection */
\r
313 fdp->fd = smsotp_connect(fdp->path);
\r
318 /* release fd, and optionally disconnect from otpd */
\r
319 static void smsotp_putfd(smsotp_fd_t *fdp, int disconnect)
\r
322 (void) close(fdp->fd);
\r
326 /* make connection available to another thread */
\r
327 smsotp_pthread_mutex_unlock(&fdp->mutex);
\r
331 * Full read with logging, and close on failure.
\r
332 * Returns nread on success, 0 on EOF, -1 on other failures.
\r
334 static int smsotp_read(smsotp_fd_t *fdp, char *buf, size_t len)
\r
337 size_t nread = 0; /* bytes read into buf */
\r
343 FD_SET(fdp->fd, &rfds);
\r
347 while (nread < len) {
\r
348 if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {
\r
349 if (errno == EINTR) {
\r
352 (void) radlog(L_ERR, "rlm_smsotp: %s: read from socket: %s", __func__, strerror(errno));
\r
353 smsotp_putfd(fdp, 1);
\r
358 (void) radlog(L_ERR, "rlm_smsotp: %s: socket disconnect", __func__);
\r
359 smsotp_putfd(fdp, 1);
\r
363 // DEBUG("smsotp_read ... read more ?");
\r
365 // check if more data is avalible
\r
366 retval = select(1, &rfds, NULL, NULL, &tv);
\r
371 // DEBUG("smsotp_read ... read more ! YES !");
\r
373 } /*while (more to read) */
\r
379 * Full write with logging, and close on failure.
\r
380 * Returns 0 on success, errno on failure.
\r
382 static int smsotp_write(smsotp_fd_t *fdp, const char *buf, size_t len)
\r
384 size_t nleft = len;
\r
388 if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {
\r
389 if (errno == EINTR || errno == EPIPE) {
\r
392 (void) radlog(L_ERR, "rlm_smsotp: %s: write to socket: %s", __func__, strerror(errno));
\r
393 smsotp_putfd(fdp, 1);
\r
402 /* socket functions end */
\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
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
417 /* guaranteed lock */
\r
418 void _smsotp_pthread_mutex_lock(pthread_mutex_t *mutexp, const char *caller)
\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
428 /* guaranteed trylock */
\r
429 int _smsotp_pthread_mutex_trylock(pthread_mutex_t *mutexp, const char *caller)
\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
442 /* guaranteed unlock */
\r
443 void _smsotp_pthread_mutex_unlock(pthread_mutex_t *mutexp, const char *caller)
\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
452 /* mutex functions end */
\r
456 * The module name should be the only globally exported symbol.
\r
457 * That is, everything else should be 'static'.
\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
464 module_t rlm_smsotp = {
\r
467 RLM_TYPE_THREAD_SAFE, /* type */
\r
468 smsotp_instantiate, /* instantiation */
\r
469 smsotp_detach, /* detach */
\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