Pass a threadsafe ctx into fr_connection_pool create callback
[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 const      *socket;
32         char const      *challenge;
33         char const      *authtype;
34         fr_connection_pool_t *pool;
35 } rlm_smsotp_t;
36
37 static const CONF_PARSER module_config[] = {
38         { "socket", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, socket), "/var/run/smsotp_socket" },
39         { "challenge_message", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, challenge), "Enter Mobile PIN" },
40         { "challenge_type", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_smsotp_t, authtype), "smsotp-reply" },
41
42         { NULL, -1, 0, NULL, NULL }             /* end the list */
43 };
44
45 static int _mod_conn_free(int *fdp)
46 {
47         close(*fdp);
48         return 0;
49 }
50
51 static void *mod_conn_create(TALLOC_CTX *ctx, 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(ctx, int);
76         talloc_set_destructor(fdp, _mod_conn_free);
77         *fdp = fd;
78
79         return fdp;
80 }
81
82 /*
83  * Full read with logging, and close on failure.
84  * Returns nread on success, 0 on EOF, -1 on other failures.
85  */
86 static size_t read_all(int *fdp, char *buf, size_t len)
87 {
88         ssize_t n;
89         size_t total = 0;
90
91         fd_set fds;
92         struct timeval tv;
93         int retval;
94
95         FD_ZERO(&fds);
96         FD_SET(*fdp, &fds);
97         tv.tv_sec = 0;
98         tv.tv_usec = 0;
99
100         while (total < len) {
101                 n = read(*fdp, &buf[total], len - total);
102                 if (n < 0) {
103                         if (errno == EINTR) {
104                                 continue;
105                         }
106                         return -1;
107                 }
108
109                 /*
110                  *      Socket was closed.  Don't try to re-open it.
111                  */
112                 if (n == 0) return 0;
113                 total += n;
114
115                 /*
116                  *      Check if there's more data.  If not, return
117                  *      now.
118                  */
119                 retval = select(1, &fds, NULL, NULL, &tv);
120                 if (!retval) {
121                         buf[total]= '\0';
122                         break;
123                 }
124         }
125
126         return total;
127 }
128
129
130 /*
131  *      Write all of the data, taking care of EINTR, etc.
132  */
133 static int write_all(int *fdp, char const *buf, size_t len)
134 {
135         size_t left = len;
136         ssize_t n;
137
138         while (left) {
139                 n = write(*fdp, &buf[len - left], left);
140                 if (n < 0) {
141                         if ((errno == EINTR) || (errno == EPIPE)) {
142                                 continue;
143                         }
144                         return -1;
145                 }
146                 left -= n;
147         }
148
149         return 0;
150 }
151
152
153 /*
154  *      Do any per-module initialization that is separate to each
155  *      configured instance of the module.  e.g. set up connections
156  *      to external databases, read configuration files, set up
157  *      dictionary entries, etc.
158  *
159  *      If configuration information is given in the config section
160  *      that must be referenced in later calls, store a handle to it
161  *      in *instance otherwise put a null pointer there.
162  */
163 static int mod_instantiate(CONF_SECTION *conf, void *instance)
164 {
165         rlm_smsotp_t *inst = instance;
166         struct sockaddr_un sa;
167         if (strlen(inst->socket) > (sizeof(sa.sun_path) - 1)) {
168                 cf_log_err_cs(conf, "Socket filename is too long");
169                 return -1;
170         }
171
172         /*
173          *      Initialize the socket pool.
174          */
175         inst->pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL);
176         if (!inst->pool) {
177                 return -1;
178         }
179
180         return 0;
181 }
182
183 /*
184  *      Authenticate the user with the given password.
185  */
186 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
187 {
188         rlm_smsotp_t *inst = instance;
189         VALUE_PAIR *state;
190         int bufsize;
191         int *fdp;
192         rlm_rcode_t rcode = RLM_MODULE_FAIL;
193         char buffer[1000];
194         char output[1000];
195
196         fdp = fr_connection_get(inst->pool);
197         if (!fdp) return RLM_MODULE_FAIL;
198
199         /* Get greeting */
200         bufsize = read_all(fdp, buffer, sizeof(buffer));
201         if (bufsize <= 0) {
202                 REDEBUG("Failed reading from socket");
203                 goto done;
204         }
205
206         /*
207          *  Look for the 'state' attribute.
208          */
209 #define WRITE_ALL(_a,_b,_c) if (write_all(_a,_b,_c) < 0) goto done;
210         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
211         if (state) {
212                 RDEBUG("Found reply to access challenge");
213
214                 /* send username */
215                 snprintf(output, sizeof(output), "check otp for %s\n",
216                          request->username->vp_strvalue);
217                 WRITE_ALL(fdp, output, strlen(output));
218
219                 (void) read_all(fdp, buffer, sizeof(buffer));
220
221                 /* send password */
222                 snprintf(output, sizeof(output), "user otp is %s\n",
223                          request->password->vp_strvalue);
224                 WRITE_ALL(fdp, output, strlen(output));
225
226                 (void) read_all(fdp, buffer, sizeof(buffer));
227
228                 /* set uuid */
229                 snprintf(output, sizeof(output), "otp id is %s\n",
230                          state->vp_strvalue);
231                 WRITE_ALL(fdp, output, strlen(output));
232
233                 (void) read_all(fdp, buffer, sizeof(buffer));
234
235                 /* now check the otp */
236                 WRITE_ALL(fdp, "get check result\n", 17);
237
238                 (void) read_all(fdp, buffer, sizeof(buffer));
239
240                 /* end the sesssion */
241                 WRITE_ALL(fdp, "quit\n", 5);
242
243                 RDEBUG("answer is %s", buffer);
244                 if (strcmp(buffer,"OK") == 0) {
245                         rcode = RLM_MODULE_OK;
246                 }
247
248                 goto done;
249         }
250
251         RDEBUG("Generating OTP");
252
253         /* set username */
254         snprintf(output, sizeof(output), "generate otp for %s\n",
255                  request->username->vp_strvalue);
256         WRITE_ALL(fdp, output, strlen(output));
257
258         (void) read_all(fdp, buffer, sizeof(buffer));
259
260         /* end the sesssion */
261         WRITE_ALL(fdp, "quit\n", 5);
262
263         RDEBUG("Unique ID is %s", buffer);
264
265         /* check the return string */
266         if (strcmp(buffer,"FAILED") == 0) { /* smsotp script returns a error */
267                 goto done;
268         }
269
270         /*
271          *      Create the challenge, and add it to the reply.
272          */
273
274         pairmake_reply("Reply-Message", inst->challenge, T_OP_EQ);
275         pairmake_reply("State", buffer, T_OP_EQ);
276
277         /*
278          *  Mark the packet as an Access-Challenge packet.
279          *
280          *  The server will take care of sending it to the user.
281          */
282         request->reply->code = PW_CODE_ACCESS_CHALLENGE;
283         DEBUG("rlm_smsotp: Sending Access-Challenge");
284
285         rcode = RLM_MODULE_HANDLED;
286
287 done:
288         fr_connection_release(inst->pool, fdp);
289         return rcode;
290 }
291
292 /*
293  *      Find the named user in this modules database.  Create the set
294  *      of attribute-value pairs to check and reply with for this user
295  *      from the database. The authentication code only needs to check
296  *      the password, the rest is done here.
297  */
298 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, UNUSED REQUEST *request)
299 {
300         VALUE_PAIR *state;
301         rlm_smsotp_t *inst = instance;
302
303         /*
304          *  Look for the 'state' attribute.
305          */
306         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
307         if (state != NULL) {
308                 DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",inst->authtype);
309
310                 pairdelete(&request->config_items, PW_AUTH_TYPE, 0, TAG_ANY); /* delete old auth-type */
311                 pairmake_config("Auth-Type", inst->authtype, T_OP_SET);
312         }
313
314         return RLM_MODULE_OK;
315 }
316
317
318 /*
319  *      The module name should be the only globally exported symbol.
320  *      That is, everything else should be 'static'.
321  *
322  *      If the module needs to temporarily modify it's instantiation
323  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
324  *      The server will then take care of ensuring that the module
325  *      is single-threaded.
326  */
327 module_t rlm_smsotp = {
328         RLM_MODULE_INIT,
329         "smsotp",
330         RLM_TYPE_THREAD_SAFE,           /* type */
331         sizeof(rlm_smsotp_t),
332         module_config,
333         mod_instantiate,                /* instantiation */
334         NULL,                           /* detach */
335         {
336                 mod_authenticate,       /* authentication */
337                 mod_authorize,  /* authorization */
338                 NULL,   /* preaccounting */
339                 NULL,   /* accounting */
340                 NULL,   /* checksimul */
341                 NULL,                   /* pre-proxy */
342                 NULL,                   /* post-proxy */
343                 NULL                    /* post-auth */
344         },
345 };