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