Merge pull request #589 from nchaigne/v3.0.x
[freeradius.git] / src / modules / rlm_smsotp / rlm_smsotp.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_smsotp.c
19  * @brief Supports OTP authentication using SMS.
20  *
21  * @copyright 2000,2006  The FreeRADIUS server project
22  * @copyright 2009  Siemens AG, Holger Wolff holger.wolff@siemens.com
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <sys/un.h>
29
30 typedef struct rlm_smsotp_t {
31         char            *socket;
32         char            *challenge;
33         char            *authtype;
34         fr_connection_pool_t *pool;
35 } rlm_smsotp_t;
36
37 static const CONF_PARSER module_config[] = {
38         { "socket", PW_TYPE_STRING_PTR,
39           offsetof(rlm_smsotp_t, socket),
40           NULL, "/var/run/smsotp_socket" },
41         { "challenge_message", PW_TYPE_STRING_PTR,
42           offsetof(rlm_smsotp_t, challenge), NULL, "Enter Mobile PIN" },
43         { "challenge_type", PW_TYPE_STRING_PTR,
44           offsetof(rlm_smsotp_t, authtype),
45           NULL, "smsotp-reply" },
46
47         { NULL, -1, 0, NULL, NULL }             /* end the list */
48 };
49
50
51 static void *mod_conn_create(void *instance)
52 {
53         int fd;
54         struct sockaddr_un sa;
55         rlm_smsotp_t *inst = instance;
56         socklen_t socklen = sizeof(sa);
57         int *fdp;
58
59         sa.sun_family = AF_UNIX;
60         strlcpy(sa.sun_path, inst->socket, sizeof(sa.sun_path));
61
62         fd = socket(PF_UNIX, SOCK_STREAM, 0);
63         if (fd < 0) {
64                 ERROR("Failed opening SMSOTP file %s: %s",
65                        inst->socket, fr_syserror(errno));
66                 return NULL;
67         }
68
69         if (connect(fd, (struct sockaddr *) &sa, socklen) < -1) {
70                 ERROR("Failed connecting to SMSOTP file %s: %s",
71                        inst->socket, fr_syserror(errno));
72                 return NULL;
73         }
74
75         fdp = talloc_zero(instance, int);
76         *fdp = fd;
77
78         return fdp;
79 }
80
81 static int mod_conn_delete(UNUSED void *instance, void *handle)
82 {
83         int *fdp = handle;
84
85         close(*fdp);
86         talloc_free(fdp);
87         return 0;
88 }
89
90
91 /*
92  * Full read with logging, and close on failure.
93  * Returns nread on success, 0 on EOF, -1 on other failures.
94  */
95 static size_t read_all(int *fdp, char *buf, size_t len)
96 {
97         ssize_t n;
98         size_t total = 0;
99
100         fd_set fds;
101         struct timeval tv;
102         int retval;
103
104         FD_ZERO(&fds);
105         FD_SET(*fdp, &fds);
106         tv.tv_sec = 0;
107         tv.tv_usec = 0;
108
109         while (total < len) {
110                 n = read(*fdp, &buf[total], len - total);
111                 if (n < 0) {
112                         if (errno == EINTR) {
113                                 continue;
114                         }
115                         return -1;
116                 }
117
118                 /*
119                  *      Socket was closed.  Don't try to re-open it.
120                  */
121                 if (n == 0) return 0;
122                 total += n;
123
124                 /*
125                  *      Check if there's more data.  If not, return
126                  *      now.
127                  */
128                 retval = select(1, &fds, NULL, NULL, &tv);
129                 if (!retval) {
130                         buf[total]= '\0';
131                         break;
132                 }
133         }
134
135         return total;
136 }
137
138
139 /*
140  *      Write all of the data, taking care of EINTR, etc.
141  */
142 static int write_all(int *fdp, char const *buf, size_t len)
143 {
144         size_t left = len;
145         ssize_t n;
146
147         while (left) {
148                 n = write(*fdp, &buf[len - left], left);
149                 if (n < 0) {
150                         if ((errno == EINTR) || (errno == EPIPE)) {
151                                 continue;
152                         }
153                         return -1;
154                 }
155                 left -= n;
156         }
157
158         return 0;
159 }
160
161
162 /*
163  *      Do any per-module initialization that is separate to each
164  *      configured instance of the module.  e.g. set up connections
165  *      to external databases, read configuration files, set up
166  *      dictionary entries, etc.
167  *
168  *      If configuration information is given in the config section
169  *      that must be referenced in later calls, store a handle to it
170  *      in *instance otherwise put a null pointer there.
171  */
172 static int mod_instantiate(CONF_SECTION *conf, void *instance)
173 {
174         rlm_smsotp_t *inst = instance;
175         struct sockaddr_un sa;
176         if (strlen(inst->socket) > (sizeof(sa.sun_path) - 1)) {
177                 cf_log_err_cs(conf, "Socket filename is too long");
178                 return -1;
179         }
180
181         /*
182          *      Initialize the socket pool.
183          */
184         inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, mod_conn_delete, NULL);
185         if (!inst->pool) {
186                 return -1;
187         }
188
189         return 0;
190 }
191
192 /*
193  *      Authenticate the user with the given password.
194  */
195 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
196 {
197         rlm_smsotp_t *inst = instance;
198         VALUE_PAIR *state;
199         int bufsize;
200         int *fdp;
201         rlm_rcode_t rcode = RLM_MODULE_FAIL;
202         char buffer[1000];
203         char output[1000];
204
205         fdp = fr_connection_get(inst->pool);
206         if (!fdp) {
207                 REDEBUG("Failed to get handle from connection pool");
208                 return RLM_MODULE_FAIL;
209         }
210
211         /* Get greeting */
212         bufsize = read_all(fdp, buffer, sizeof(buffer));
213         if (bufsize <= 0) {
214                 REDEBUG("Failed reading from socket");
215                 goto done;
216         }
217
218         /*
219          *  Look for the 'state' attribute.
220          */
221 #define WRITE_ALL(_a,_b,_c) if (write_all(_a,_b,_c) < 0) goto done;
222         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
223         if (state) {
224                 RDEBUG("Found reply to access challenge");
225
226                 /* send username */
227                 snprintf(output, sizeof(output), "check otp for %s\n",
228                          request->username->vp_strvalue);
229                 WRITE_ALL(fdp, output, strlen(output));
230
231                 (void) read_all(fdp, buffer, sizeof(buffer));
232
233                 /* send password */
234                 snprintf(output, sizeof(output), "user otp is %s\n",
235                          request->password->vp_strvalue);
236                 WRITE_ALL(fdp, output, strlen(output));
237
238                 (void) read_all(fdp, buffer, sizeof(buffer));
239
240                 /* set uuid */
241                 snprintf(output, sizeof(output), "otp id is %s\n",
242                          state->vp_strvalue);
243                 WRITE_ALL(fdp, output, strlen(output));
244
245                 (void) read_all(fdp, buffer, sizeof(buffer));
246
247                 /* now check the otp */
248                 WRITE_ALL(fdp, "get check result\n", 17);
249
250                 (void) read_all(fdp, buffer, sizeof(buffer));
251
252                 /* end the sesssion */
253                 WRITE_ALL(fdp, "quit\n", 5);
254
255                 RDEBUG("answer is %s", buffer);
256                 if (strcmp(buffer,"OK") == 0) {
257                         rcode = RLM_MODULE_OK;
258                 }
259
260                 goto done;
261         }
262
263         RDEBUG("Generating OTP");
264
265         /* set username */
266         snprintf(output, sizeof(output), "generate otp for %s\n",
267                  request->username->vp_strvalue);
268         WRITE_ALL(fdp, output, strlen(output));
269
270         (void) read_all(fdp, buffer, sizeof(buffer));
271
272         /* end the sesssion */
273         WRITE_ALL(fdp, "quit\n", 5);
274
275         RDEBUG("Unique ID is %s", buffer);
276
277         /* check the return string */
278         if (strcmp(buffer,"FAILED") == 0) { /* smsotp script returns a error */
279                 goto done;
280         }
281
282         /*
283          *      Create the challenge, and add it to the reply.
284          */
285
286         pairmake_reply("Reply-Message", inst->challenge, T_OP_EQ);
287         pairmake_reply("State", buffer, T_OP_EQ);
288
289         /*
290          *  Mark the packet as an Access-Challenge packet.
291          *
292          *  The server will take care of sending it to the user.
293          */
294         request->reply->code = PW_CODE_ACCESS_CHALLENGE;
295         DEBUG("rlm_smsotp: Sending Access-Challenge.");
296
297         rcode = RLM_MODULE_HANDLED;
298
299 done:
300         fr_connection_release(inst->pool, fdp);
301         return rcode;
302 }
303
304 /*
305  *      Find the named user in this modules database.  Create the set
306  *      of attribute-value pairs to check and reply with for this user
307  *      from the database. The authentication code only needs to check
308  *      the password, the rest is done here.
309  */
310 static rlm_rcode_t mod_authorize(UNUSED void *instance, UNUSED REQUEST *request)
311 {
312         VALUE_PAIR *state;
313         rlm_smsotp_t *inst = instance;
314
315         /*
316          *  Look for the 'state' attribute.
317          */
318         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
319         if (state != NULL) {
320                 DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",inst->authtype);
321
322                 pairdelete(&request->config_items, PW_AUTH_TYPE, 0, TAG_ANY); /* delete old auth-type */
323                 pairmake_config("Auth-Type", inst->authtype, T_OP_SET);
324         }
325
326         return RLM_MODULE_OK;
327 }
328
329
330 /*
331  *      The module name should be the only globally exported symbol.
332  *      That is, everything else should be 'static'.
333  *
334  *      If the module needs to temporarily modify it's instantiation
335  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
336  *      The server will then take care of ensuring that the module
337  *      is single-threaded.
338  */
339 module_t rlm_smsotp = {
340         RLM_MODULE_INIT,
341         "smsotp",
342         RLM_TYPE_THREAD_SAFE,           /* type */
343         sizeof(rlm_smsotp_t),
344         module_config,
345         mod_instantiate,                /* instantiation */
346         NULL,                           /* detach */
347         {
348                 mod_authenticate,       /* authentication */
349                 mod_authorize,  /* authorization */
350                 NULL,   /* preaccounting */
351                 NULL,   /* accounting */
352                 NULL,   /* checksimul */
353                 NULL,                   /* pre-proxy */
354                 NULL,                   /* post-proxy */
355                 NULL                    /* post-auth */
356         },
357 };