import from HEAD:
[freeradius.git] / src / modules / rlm_otp / otp_pw_valid.c
1 /*
2  * $Id$
3  *
4  * Passcode verification function (otpd client) for rlm_otp.
5  *
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.
10  *
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.
15  *
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
19  *
20  *
21  * Copyright 2006 TRI-D Systems, Inc.
22  */
23
24 static const char rcsid[] = "$Id$";
25
26 #include "autoconf.h"
27 #include "radiusd.h"
28 #include "modules.h"
29
30 #include "extern.h"
31 #include "otp.h"
32 #include "otp_pw_valid.h"
33
34 #include <errno.h>
35 #include <pthread.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <sys/un.h>
41 #include <unistd.h>
42
43
44 /* transform otpd return codes into rlm return codes */
45 static int
46 otprc2rlmrc(int rc)
47 {
48   switch (rc) {
49     case OTP_RC_OK:                     return RLM_MODULE_OK;
50     case OTP_RC_USER_UNKNOWN:           return RLM_MODULE_REJECT;
51     case OTP_RC_AUTHINFO_UNAVAIL:       return RLM_MODULE_REJECT;
52     case OTP_RC_AUTH_ERR:               return RLM_MODULE_REJECT;
53     case OTP_RC_MAXTRIES:               return RLM_MODULE_USERLOCK;
54     case OTP_RC_SERVICE_ERR:            return RLM_MODULE_FAIL;
55     default:                            return RLM_MODULE_FAIL;
56   }
57 }
58
59 static otp_fd_t *otp_fd_head;
60 static pthread_mutex_t otp_fd_head_mutex = PTHREAD_MUTEX_INITIALIZER;
61
62 /*
63  * Test for passcode validity by asking otpd.
64  *
65  * If challenge is supplied, it is used to generate the card response
66  * against which the passcode will be compared.  If challenge is not
67  * supplied, or if the comparison fails, synchronous responses are
68  * generated and tested.  NOTE: for async authentications, sync mode
69  * responses are still considered valid!  (Assuming module configuration
70  * allows sync mode.)
71  *
72  * Returns one of the RLM_MODULE_* codes.  passcode is filled in.
73  * NB: The returned passcode will contain the PIN!  DO NOT LOG!
74  */
75 int
76 otp_pw_valid(REQUEST *request, int pwe, const char *challenge,
77              const otp_option_t *opt, char passcode[OTP_MAX_PASSCODE_LEN + 1])
78 {
79   otp_request_t otp_request;
80   otp_reply_t   otp_reply;
81   VALUE_PAIR    *cvp, *rvp;
82   char          *username = request->username->strvalue;
83   int           rc;
84
85   if (request->username->length > OTP_MAX_USERNAME_LEN) {
86     (void) radlog(L_AUTH, "rlm_otp: username [%s] too long", username);
87     return RLM_MODULE_REJECT;
88   }
89   /* we already know challenge is short enough */
90
91   otp_request.version = 1;
92   (void) strcpy(otp_request.username, username);
93   (void) strcpy(otp_request.challenge, challenge);
94   otp_request.pwe.pwe = pwe;
95
96   /* otp_pwe_present() (done by caller) guarantees that both of these exist */
97   cvp = pairfind(request->packet->vps, pwattr[pwe - 1]);
98   rvp = pairfind(request->packet->vps, pwattr[pwe]);
99
100   /*
101    * Validate available vps based on pwe type.
102    * Unfortunately (?) otpd must do this also.
103    */
104   switch (otp_request.pwe.pwe) {
105   case PWE_PAP:
106     if (rvp->length > OTP_MAX_PASSCODE_LEN) {
107       (void) radlog(L_AUTH, "rlm_otp: passcode for [%s] too long", username);
108       return RLM_MODULE_REJECT;
109     }
110     (void) strcpy(otp_request.pwe.passcode, rvp->strvalue);
111     break;
112
113   case PWE_CHAP:
114     if (cvp->length > 16) {
115       (void) radlog(L_AUTH, "rlm_otp: CHAP challenge for [%s] too long",
116                     username);
117       return RLM_MODULE_INVALID;
118     }
119     if (rvp->length != 17) {
120       (void) radlog(L_AUTH, "rlm_otp: CHAP response for [%s] wrong size",
121                     username);
122       return RLM_MODULE_INVALID;
123     }
124     (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
125     otp_request.pwe.clen = cvp->length;
126     (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
127     otp_request.pwe.rlen = rvp->length;
128     break;
129
130   case PWE_MSCHAP:
131     if (cvp->length != 8) {
132       (void) radlog(L_AUTH, "rlm_otp: MS-CHAP challenge for [%s] wrong size",
133                     username);
134       return RLM_MODULE_INVALID;
135     }
136     if (rvp->length != 50) {
137       (void) radlog(L_AUTH, "rlm_otp: MS-CHAP response for [%s] wrong size",
138                     username);
139       return RLM_MODULE_INVALID;
140     }
141     (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
142     otp_request.pwe.clen = cvp->length;
143     (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
144     otp_request.pwe.rlen = rvp->length;
145     break;
146
147   case PWE_MSCHAP2:
148     if (cvp->length != 16) {
149       (void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 challenge for [%s] wrong size",
150                     username);
151       return RLM_MODULE_INVALID;
152     }
153     if (rvp->length != 50) {
154       (void) radlog(L_AUTH, "rlm_otp: MS-CHAP2 response for [%s] wrong size",
155                     username);
156       return RLM_MODULE_INVALID;
157     }
158     (void) memcpy(otp_request.pwe.challenge, cvp->strvalue, cvp->length);
159     otp_request.pwe.clen = cvp->length;
160     (void) memcpy(otp_request.pwe.response, rvp->strvalue, rvp->length);
161     otp_request.pwe.rlen = rvp->length;
162     break;
163   } /* switch (otp_request.pwe.pwe) */
164
165   /* last byte must also be a terminator so otpd can verify length easily */
166   otp_request.username[OTP_MAX_USERNAME_LEN] = '\0';
167   otp_request.challenge[OTP_MAX_CHALLENGE_LEN] = '\0';
168   otp_request.pwe.passcode[OTP_MAX_PASSCODE_LEN] = '\0';
169
170   otp_request.allow_sync = opt->allow_sync;
171   otp_request.allow_async = opt->allow_async;
172   otp_request.challenge_delay = opt->challenge_delay;
173   otp_request.resync = 1;
174
175   rc = otp_verify(opt, &otp_request, &otp_reply);
176   if (rc == OTP_RC_OK)
177     (void) strcpy(passcode, otp_reply.passcode);
178   return otprc2rlmrc(rc);
179 }
180
181 /*
182  * Verify an otp by asking otpd.
183  * Returns an OTP_* code, or -1 on system failure.
184  * Fills in reply.
185  */
186 static int
187 otp_verify(const otp_option_t *opt,
188            const otp_request_t *request, otp_reply_t *reply)
189 {
190   otp_fd_t *fdp;
191   int rc;
192   int tryagain = 2;
193
194 retry:
195   if (!tryagain--)
196     return -1;
197   fdp = otp_getfd(opt);
198   if (!fdp || fdp->fd == -1)
199     return -1;
200
201   if ((rc = otp_write(fdp, (const char *) request, sizeof(*request))) != 0) {
202     if (rc == EPIPE)
203       goto retry;       /* otpd disconnect */   /*TODO: pause */
204     else
205       return -1;
206   }
207
208   if ((rc = otp_read(fdp, (char *) reply, sizeof(*reply))) != sizeof(*reply)) {
209     if (rc == 0)
210       goto retry;       /* otpd disconnect */   /*TODO: pause */
211     else
212       return -1;
213   }
214
215   /* validate the reply */
216   if (reply->version != 1) {
217     (void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid "
218                           "(version %d != 1)",
219                   request->username, reply->version);
220     otp_putfd(fdp, 1);
221     return -1;
222   }
223
224   if (reply->passcode[OTP_MAX_PASSCODE_LEN] != '\0') {
225     (void) radlog(L_AUTH, "rlm_otp: otpd reply for [%s] invalid (passcode)",
226                   request->username);
227     otp_putfd(fdp, 1);
228     return -1;
229   }
230
231   otp_putfd(fdp, 0);
232   return reply->rc;
233 }
234
235 /*
236  * Full read with logging, and close on failure.
237  * Returns nread on success, 0 on EOF, -1 on other failures.
238  */
239 static int
240 otp_read(otp_fd_t *fdp, char *buf, size_t len)
241 {
242   ssize_t n;
243   size_t nread = 0;     /* bytes read into buf */
244
245   while (nread < len) {
246     if ((n = read(fdp->fd, &buf[nread], len - nread)) == -1) {
247       if (errno == EINTR) {
248         continue;
249       } else {
250         (void) radlog(L_ERR, "rlm_otp: %s: read from otpd: %s",
251                       __func__, strerror(errno));
252         otp_putfd(fdp, 1);
253         return -1;
254       }
255     }
256     if (!n) {
257       (void) radlog(L_ERR, "rlm_otp: %s: otpd disconnect", __func__);
258       otp_putfd(fdp, 1);
259       return 0;
260     }
261     nread += n;
262   } /* while (more to read) */
263
264   return nread;
265 }
266
267 /*
268  * Full write with logging, and close on failure.
269  * Returns 0 on success, errno on failure.
270  */
271 static int
272 otp_write(otp_fd_t *fdp, const char *buf, size_t len)
273 {
274   size_t nleft = len;
275   ssize_t nwrote;
276
277   while (nleft) {
278     if ((nwrote = write(fdp->fd, &buf[len - nleft], nleft)) == -1) {
279       if (errno == EINTR || errno == EPIPE) {
280         continue;
281       } else {
282         (void) radlog(L_ERR, "rlm_otp: %s: write to otpd: %s",
283                       __func__, strerror(errno));
284         otp_putfd(fdp, 1);
285         return errno;
286       }
287     }
288     nleft -= nwrote;
289   }
290
291   return 0;
292 }
293
294 /* connect to otpd and return fd */
295 static int
296 otp_connect(const char *path)
297 {
298   int fd;
299   struct sockaddr_un sa;
300   size_t sp_len;                /* sun_path length (strlen) */
301
302   /* setup for unix domain socket */
303   sp_len = strlen(path);
304   if (sp_len > sizeof(sa.sun_path) - 1) {
305     (void) radlog(L_ERR, "rlm_otp: %s: rendezvous point name too long",
306                   __func__);
307     return -1;
308   }
309   sa.sun_family = AF_UNIX;
310   (void) strcpy(sa.sun_path, path);
311     
312   /* connect to otpd */
313   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
314     (void) radlog(L_ERR, "rlm_otp: %s: socket: %s", __func__, strerror(errno));
315     return -1;
316   }
317   if (connect(fd, (struct sockaddr *) &sa,
318               sizeof(sa.sun_family) + sp_len) == -1) {
319     (void) radlog(L_ERR, "rlm_otp: %s: connect(%s): %s",
320                   __func__, path, strerror(errno));
321     (void) close(fd);
322     return -1;
323   }
324   return fd;
325 }
326
327 /*
328  * Retrieve an fd (from pool) to use for otpd connection.
329  * It'd be simpler to use TLS but FR can have lots of threads
330  * and we don't want to waste fd's that way.
331  * We can't have a global fd because we'd then be pipelining
332  * requests to otpd and we have no way to demultiplex
333  * the responses.
334  */
335 static otp_fd_t *
336 otp_getfd(const otp_option_t *opt)
337 {
338   int rc;
339   otp_fd_t *fdp;
340
341   /* walk the connection pool looking for an available fd */
342   for (fdp = otp_fd_head; fdp; fdp = fdp->next) {
343     rc = otp_pthread_mutex_trylock(&fdp->mutex);
344     if (!rc)
345       if (!strcmp(fdp->path, opt->otpd_rp))     /* could just use == */
346         break;
347   }
348
349   if (!fdp) {
350     /* no fd was available, add a new one */
351     fdp = rad_malloc(sizeof(*fdp));
352     otp_pthread_mutex_init(&fdp->mutex, NULL);
353     otp_pthread_mutex_lock(&fdp->mutex);
354     /* insert new fd at head */
355     otp_pthread_mutex_lock(&otp_fd_head_mutex);
356     fdp->next = otp_fd_head;
357     otp_fd_head = fdp;
358     otp_pthread_mutex_unlock(&otp_fd_head_mutex);
359     /* initialize */
360     fdp->path = opt->otpd_rp;
361     fdp->fd = -1;
362   }
363
364   /* establish connection */
365   if (fdp->fd == -1)
366     fdp->fd = otp_connect(fdp->path);
367
368   return fdp;
369 }
370
371 /* release fd, and optionally disconnect from otpd */
372 static void
373 otp_putfd(otp_fd_t *fdp, int disconnect)
374 {
375   if (disconnect) {
376     (void) close(fdp->fd);
377     fdp->fd = -1;
378   }
379
380   /* make connection available to another thread */
381   otp_pthread_mutex_unlock(&fdp->mutex);
382 }