Move ident.h macros into build.h
[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 *conn_create(void *ctx)
52 {
53         int fd;
54         struct sockaddr_un sa;
55         rlm_smsotp_t *inst = ctx;
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                 radlog(L_ERR, "Failed opening SMSOTP file %s: %s",
65                        inst->socket, strerror(errno));
66                 return NULL;
67         }
68
69         if (connect(fd, (struct sockaddr *) &sa, socklen) < -1) {
70                 radlog(L_ERR, "Failed connecting to SMSOTP file %s: %s",
71                        inst->socket, strerror(errno));
72                 return NULL;
73         }
74
75         fdp = talloc_zero(ctx, int);
76         *fdp = fd;
77
78         return fdp;
79 }
80
81 static int conn_delete(UNUSED void *ctx, void *connection)
82 {
83         int *fdp = connection;
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, const char *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,
185                                              conn_create,
186                                              NULL,
187                                              conn_delete);
188         if (!inst->pool) {
189                 return -1;
190         }
191
192         return 0;
193 }
194
195 /*
196  *      Authenticate the user with the given password.
197  */
198 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
199 {
200         rlm_smsotp_t *inst = instance;
201         VALUE_PAIR *state;
202         int bufsize;
203         int *fdp;
204         rlm_rcode_t rcode = RLM_MODULE_FAIL;
205         char buffer[1000];
206         char output[1000];
207
208         fdp = fr_connection_get(inst->pool);
209         if (!fdp) {
210                 RDEBUGE("Failed to get handle from connection pool");
211                 return RLM_MODULE_FAIL;
212         }
213         
214         /* Get greeting */
215         bufsize = read_all(fdp, buffer, sizeof(buffer));
216         if (bufsize <= 0) {
217                 RDEBUGE("Failed reading from socket");
218                 goto done;
219         }
220
221         /*
222          *  Look for the 'state' attribute.
223          */
224 #define WRITE_ALL(_a,_b,_c) if (write_all(_a,_b,_c) < 0) goto done;
225         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
226         if (!state) {
227                 RDEBUG("Found reply to access challenge");
228                 
229                 /* send username */
230                 snprintf(output, sizeof(output), "check otp for %s\n",
231                          request->username->vp_strvalue);
232                 WRITE_ALL(fdp, output, strlen(output));
233
234                 bufsize = read_all(fdp, buffer, sizeof(buffer));
235                 
236                 /* send password */
237                 snprintf(output, sizeof(output), "user otp is %s\n",
238                          request->password->vp_strvalue);
239                 WRITE_ALL(fdp, output, strlen(output));
240
241                 bufsize = read_all(fdp, buffer, sizeof(buffer));
242                 
243                 /* set uuid */
244                 snprintf(output, sizeof(output), "otp id is %s\n",
245                          state->vp_strvalue);
246                 WRITE_ALL(fdp, output, strlen(output));
247
248                 bufsize = read_all(fdp, buffer, sizeof(buffer));
249                 
250                 /* now check the otp */
251                 WRITE_ALL(fdp, "get check result\n", 17);
252
253                 bufsize = read_all(fdp, buffer, sizeof(buffer));
254                 
255                 /* end the sesssion */
256                 WRITE_ALL(fdp, "quit\n", 5);
257
258                 RDEBUG("answer is %s", buffer);
259                 if (strcmp(buffer,"OK") == 0) {
260                         rcode = RLM_MODULE_OK;
261                 }
262
263                 goto done;
264         }
265
266         RDEBUG("Generating OTP");
267
268         /* set username */
269         snprintf(output, sizeof(output), "generate otp for %s\n",
270                  request->username->vp_strvalue);
271         WRITE_ALL(fdp, output, strlen(output));
272
273         bufsize = read_all(fdp, buffer, sizeof(buffer));
274
275         /* end the sesssion */
276         WRITE_ALL(fdp, "quit\n", 5);
277
278         RDEBUG("Unique ID is %s", buffer);
279
280         /* check the return string */
281         if (strcmp(buffer,"FAILED") == 0) { /* smsotp script returns a error */
282                 goto done;
283         }
284
285         /*
286          *      Create the challenge, and add it to the reply.
287          */
288                 
289         pairmake_reply("Reply-Message", inst->challenge, T_OP_EQ);
290         pairmake_reply("State", buffer, T_OP_EQ);
291         
292         /*
293          *  Mark the packet as an Access-Challenge packet.
294          *
295          *  The server will take care of sending it to the user.
296          */
297         request->reply->code = PW_ACCESS_CHALLENGE;
298         DEBUG("rlm_smsotp: Sending Access-Challenge.");
299         
300         rcode = RLM_MODULE_HANDLED;
301
302 done:
303         fr_connection_release(inst->pool, fdp);
304         return rcode;
305 }
306
307 /*
308  *      Find the named user in this modules database.  Create the set
309  *      of attribute-value pairs to check and reply with for this user
310  *      from the database. The authentication code only needs to check
311  *      the password, the rest is done here.
312  */
313 static rlm_rcode_t mod_authorize(UNUSED void *instance, UNUSED REQUEST *request)
314 {
315         VALUE_PAIR *state;
316         rlm_smsotp_t *inst = instance;
317
318         /*
319          *  Look for the 'state' attribute.
320          */
321         state = pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY);
322         if (state != NULL) {
323                 DEBUG("rlm_smsotp: Found reply to access challenge (AUTZ), Adding Auth-Type '%s'",inst->authtype);
324                 
325                 pairdelete(&request->config_items, PW_AUTH_TYPE, 0, TAG_ANY); /* delete old auth-type */
326                 pairmake_config("Auth-Type", inst->authtype, T_OP_SET);
327         }
328
329         return RLM_MODULE_OK;
330 }
331
332
333 /*
334  *      The module name should be the only globally exported symbol.
335  *      That is, everything else should be 'static'.
336  *
337  *      If the module needs to temporarily modify it's instantiation
338  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
339  *      The server will then take care of ensuring that the module
340  *      is single-threaded.
341  */
342 module_t rlm_smsotp = {
343         RLM_MODULE_INIT,
344         "smsotp",
345         RLM_TYPE_THREAD_SAFE,           /* type */
346         sizeof(rlm_smsotp_t),
347         module_config,
348         mod_instantiate,                /* instantiation */
349         NULL,                           /* detach */
350         {
351                 mod_authenticate,       /* authentication */
352                 mod_authorize,  /* authorization */
353                 NULL,   /* preaccounting */
354                 NULL,   /* accounting */
355                 NULL,   /* checksimul */
356                 NULL,                   /* pre-proxy */
357                 NULL,                   /* post-proxy */
358                 NULL                    /* post-auth */
359         },
360 };