1 /*
2 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5 #pragma ident "%Z%%M% %I% %E% SMI"
6
7 /* SASL server API implementation
8 * Rob Siemborski
9 * Tim Martin
10 * $Id: checkpw.c,v 1.62 2003/03/19 18:25:27 rjs3 Exp $
11 */
12 /*
13 * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 *
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
21 *
22 * 2. Redistributions in binary form must reproduce the above copyright
23 * notice, this list of conditions and the following disclaimer in
24 * the documentation and/or other materials provided with the
25 * distribution.
26 *
27 * 3. The name "Carnegie Mellon University" must not be used to
28 * endorse or promote products derived from this software without
29 * prior written permission. For permission or any other legal
30 * details, please contact
31 * Office of Technology Transfer
32 * Carnegie Mellon University
33 * 5000 Forbes Avenue
34 * Pittsburgh, PA 15213-3890
35 * (412) 268-4387, fax: (412) 268-7395
36 * tech-transfer@andrew.cmu.edu
37 *
38 * 4. Redistributions of any form whatsoever must retain the following
39 * acknowledgment:
40 * "This product includes software developed by Computing Services
41 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
42 *
43 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
44 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
45 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
46 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
47 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
48 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
49 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
50 */
51
52 #include <config.h>
53
54 /* checkpw stuff */
55
56 #include <stdio.h>
57 #include "sasl.h"
58 #include "saslutil.h"
59 #include "saslplug.h"
60 #include "saslint.h"
61
62 #include <assert.h>
63 #ifdef HAVE_UNISTD_H
64 #include <unistd.h>
65 #endif
66 #include <fcntl.h>
67 #ifdef USE_DOORS
68 #include <sys/mman.h>
69 #include <door.h>
70 #endif
71
72 #include <stdlib.h>
73
74 #ifndef WIN32
75 #include <strings.h>
76 #include <netdb.h>
77 #include <netinet/in.h>
78 #include <sys/un.h>
79 #else
80 #include <string.h>
81 #endif
82
83 #include <sys/types.h>
84 #include <ctype.h>
85
86 #ifdef HAVE_PWD_H
87 #include <pwd.h>
88 #endif /* HAVE_PWD_H */
89 #ifdef HAVE_SHADOW_H
90 #include <shadow.h>
91 #endif /* HAVE_SHADOW_H */
92
93 #if defined(HAVE_PWCHECK) || defined(HAVE_SASLAUTHD)
94 # include <errno.h>
95 # include <sys/types.h>
96 # include <sys/socket.h>
97 # include <sys/un.h>
98 # ifdef HAVE_UNISTD_H
99 # include <unistd.h>
100 # endif
101
102 extern int errno;
103 #endif
104
105
106 /* we store the following secret to check plaintext passwords:
107 *
108 * <salt> \0 <secret>
109 *
110 * where <secret> = MD5(<salt>, "sasldb", <pass>)
111 */
112 #ifdef _SUN_SDK_
_sasl_make_plain_secret(const sasl_utils_t * utils,const char * salt,const char * passwd,size_t passlen,sasl_secret_t ** secret)113 static int _sasl_make_plain_secret(const sasl_utils_t *utils, const char *salt,
114 const char *passwd, size_t passlen,
115 sasl_secret_t **secret)
116 #else
117 static int _sasl_make_plain_secret(const char *salt,
118 const char *passwd, size_t passlen,
119 sasl_secret_t **secret)
120 #endif /* _SUN_SDK_ */
121 {
122 MD5_CTX ctx;
123 unsigned sec_len = 16 + 1 + 16; /* salt + "\0" + hash */
124
125 #ifdef _SUN_SDK_
126 *secret = (sasl_secret_t *)utils->malloc(sizeof(sasl_secret_t) +
127 sec_len * sizeof(char));
128 #else
129 *secret = (sasl_secret_t *) sasl_ALLOC(sizeof(sasl_secret_t) +
130 sec_len * sizeof(char));
131 #endif /* _SUN_SDK_ */
132 if (*secret == NULL) {
133 return SASL_NOMEM;
134 }
135
136 _sasl_MD5Init(&ctx);
137 _sasl_MD5Update(&ctx, salt, 16);
138 _sasl_MD5Update(&ctx, "sasldb", 6);
139 _sasl_MD5Update(&ctx, passwd, passlen);
140 memcpy((*secret)->data, salt, 16);
141 (*secret)->data[16] = '\0';
142 _sasl_MD5Final((*secret)->data + 17, &ctx);
143 (*secret)->len = sec_len;
144
145 return SASL_OK;
146 }
147
148 /* erase & dispose of a sasl_secret_t
149 */
auxprop_verify_password(sasl_conn_t * conn,const char * userstr,const char * passwd,const char * service,const char * user_realm)150 static int auxprop_verify_password(sasl_conn_t *conn,
151 const char *userstr,
152 const char *passwd,
153 const char *service __attribute__((unused)),
154 const char *user_realm __attribute__((unused)))
155 {
156 int ret = SASL_FAIL;
157 char *userid = NULL;
158 #ifndef _SUN_SDK_
159 char *realm = NULL;
160 #endif /* !_SUN_SDK_ */
161 int result = SASL_OK;
162 sasl_server_conn_t *sconn = (sasl_server_conn_t *)conn;
163 const char *password_request[] = { SASL_AUX_PASSWORD,
164 "*cmusaslsecretPLAIN",
165 NULL };
166 struct propval auxprop_values[3];
167
168 if (!conn || !userstr)
169 return SASL_BADPARAM;
170
171 /* We need to clear any previous results and re-canonify to
172 * ensure correctness */
173
174 prop_clear(sconn->sparams->propctx, 0);
175
176 /* ensure its requested */
177 result = prop_request(sconn->sparams->propctx, password_request);
178
179 if(result != SASL_OK) return result;
180
181 result = _sasl_canon_user(conn, userstr, 0,
182 SASL_CU_AUTHID | SASL_CU_AUTHZID,
183 &(conn->oparams));
184 if(result != SASL_OK) return result;
185
186 result = prop_getnames(sconn->sparams->propctx, password_request,
187 auxprop_values);
188 if(result < 0)
189 return result;
190
191 if((!auxprop_values[0].name
192 || !auxprop_values[0].values || !auxprop_values[0].values[0])
193 && (!auxprop_values[1].name
194 || !auxprop_values[1].values || !auxprop_values[1].values[0]))
195 return SASL_NOUSER;
196
197 /* It is possible for us to get useful information out of just
198 * the lookup, so we won't check that we have a password until now */
199 if(!passwd) {
200 ret = SASL_BADPARAM;
201 goto done;
202 }
203
204 /* At the point this has been called, the username has been canonified
205 * and we've done the auxprop lookup. This should be easy. */
206 if(auxprop_values[0].name
207 && auxprop_values[0].values
208 && auxprop_values[0].values[0]
209 && !strcmp(auxprop_values[0].values[0], passwd)) {
210 /* We have a plaintext version and it matched! */
211 return SASL_OK;
212 } else if(auxprop_values[1].name
213 && auxprop_values[1].values
214 && auxprop_values[1].values[0]) {
215 const char *db_secret = auxprop_values[1].values[0];
216 sasl_secret_t *construct;
217
218 #ifdef _SUN_SDK_
219 ret = _sasl_make_plain_secret(sconn->sparams->utils, db_secret,
220 passwd, strlen(passwd),
221 &construct);
222 #else
223 ret = _sasl_make_plain_secret(db_secret, passwd,
224 strlen(passwd),
225 &construct);
226 #endif /* _SUN_SDK_ */
227 if (ret != SASL_OK) {
228 goto done;
229 }
230
231 if (!memcmp(db_secret, construct->data, construct->len)) {
232 /* password verified! */
233 ret = SASL_OK;
234 } else {
235 /* passwords do not match */
236 ret = SASL_BADAUTH;
237 }
238
239 #ifdef _SUN_SDK_
240 sconn->sparams->utils->free(construct);
241 #else
242 sasl_FREE(construct);
243 #endif /* _SUN_SDK_ */
244 } else {
245 /* passwords do not match */
246 ret = SASL_BADAUTH;
247 }
248
249 done:
250 #ifdef _SUN_SDK_
251 if (userid) sconn->sparams->utils->free(userid);
252 #else
253 if (userid) sasl_FREE(userid);
254 if (realm) sasl_FREE(realm);
255 #endif /* _SUN_SDK_ */
256
257 /* We're not going to erase the property here because other people
258 * may want it */
259 return ret;
260 }
261
262 #ifdef DO_SASL_CHECKAPOP
_sasl_auxprop_verify_apop(sasl_conn_t * conn,const char * userstr,const char * challenge,const char * response,const char * user_realm)263 int _sasl_auxprop_verify_apop(sasl_conn_t *conn,
264 const char *userstr,
265 const char *challenge,
266 const char *response,
267 const char *user_realm __attribute__((unused)))
268 {
269 int ret = SASL_BADAUTH;
270 char *userid = NULL;
271 #ifndef _SUN_SDK_
272 char *realm = NULL;
273 #endif /* !_SUN_SDK_ */
274 unsigned char digest[16];
275 char digeststr[33];
276 const char *password_request[] = { SASL_AUX_PASSWORD, NULL };
277 struct propval auxprop_values[2];
278 sasl_server_conn_t *sconn = (sasl_server_conn_t *)conn;
279 MD5_CTX ctx;
280 int i;
281
282 if (!conn || !userstr || !challenge || !response)
283 PARAMERROR(conn)
284
285 /* We've done the auxprop lookup already (in our caller) */
286 /* sadly, APOP has no provision for storing secrets */
287 ret = prop_getnames(sconn->sparams->propctx, password_request,
288 auxprop_values);
289 if(ret < 0) {
290 #ifdef _SUN_SDK_
291 _sasl_log(conn, SASL_LOG_ERR, "could not perform password lookup");
292 #else
293 sasl_seterror(conn, 0, "could not perform password lookup");
294 #endif /* _SUN_SDK_ */
295 goto done;
296 }
297
298 if(!auxprop_values[0].name ||
299 !auxprop_values[0].values ||
300 !auxprop_values[0].values[0]) {
301 #ifdef _INTEGRATED_SOLARIS_
302 sasl_seterror(conn, 0, gettext("could not find password"));
303 #else
304 sasl_seterror(conn, 0, "could not find password");
305 #endif /* _INTEGRATED_SOLARIS_ */
306 ret = SASL_NOUSER;
307 goto done;
308 }
309
310 _sasl_MD5Init(&ctx);
311 _sasl_MD5Update(&ctx, challenge, strlen(challenge));
312 _sasl_MD5Update(&ctx, auxprop_values[0].values[0],
313 strlen(auxprop_values[0].values[0]));
314 _sasl_MD5Final(digest, &ctx);
315
316 /* convert digest from binary to ASCII hex */
317 for (i = 0; i < 16; i++)
318 sprintf(digeststr + (i*2), "%02x", digest[i]);
319
320 if (!strncasecmp(digeststr, response, 32)) {
321 /* password verified! */
322 ret = SASL_OK;
323 } else {
324 /* passwords do not match */
325 ret = SASL_BADAUTH;
326 }
327
328 done:
329 #ifdef _INTEGRATED_SOLARIS_
330 if (ret == SASL_BADAUTH) sasl_seterror(conn, SASL_NOLOG,
331 gettext("login incorrect"));
332 #else
333 if (ret == SASL_BADAUTH) sasl_seterror(conn, SASL_NOLOG,
334 "login incorrect");
335 #endif /* _INTEGRATED_SOLARIS_ */
336 #ifdef _SUN_SDK_
337 if (userid) sconn->sparams->utils->free(userid);
338 #else
339 if (userid) sasl_FREE(userid);
340 if (realm) sasl_FREE(realm);
341 #endif /* _SUN_SDK_ */
342
343 return ret;
344 }
345 #endif /* DO_SASL_CHECKAPOP */
346
347 #if defined(HAVE_PWCHECK) || defined(HAVE_SASLAUTHD)
348 /*
349 * Keep calling the writev() system call with 'fd', 'iov', and 'iovcnt'
350 * until all the data is written out or an error occurs.
351 */
retry_writev(int fd,struct iovec * iov,int iovcnt)352 static int retry_writev(int fd, struct iovec *iov, int iovcnt)
353 {
354 int n;
355 int i;
356 int written = 0;
357 static int iov_max =
358 #ifdef MAXIOV
359 MAXIOV
360 #else
361 #ifdef IOV_MAX
362 IOV_MAX
363 #else
364 8192
365 #endif
366 #endif
367 ;
368
369 for (;;) {
370 while (iovcnt && iov[0].iov_len == 0) {
371 iov++;
372 iovcnt--;
373 }
374
375 if (!iovcnt) return written;
376
377 n = writev(fd, iov, iovcnt > iov_max ? iov_max : iovcnt);
378 if (n == -1) {
379 if (errno == EINVAL && iov_max > 10) {
380 iov_max /= 2;
381 continue;
382 }
383 if (errno == EINTR) continue;
384 return -1;
385 }
386
387 written += n;
388
389 for (i = 0; i < iovcnt; i++) {
390 if (iov[i].iov_len > (unsigned) n) {
391 iov[i].iov_base = (char *)iov[i].iov_base + n;
392 iov[i].iov_len -= n;
393 break;
394 }
395 n -= iov[i].iov_len;
396 iov[i].iov_len = 0;
397 }
398
399 if (i == iovcnt) return written;
400 }
401 }
402
403 #endif
404
405 #ifdef HAVE_PWCHECK
406 /* pwcheck daemon-authenticated login */
pwcheck_verify_password(sasl_conn_t * conn,const char * userid,const char * passwd,const char * service,const char * user_realm)407 static int pwcheck_verify_password(sasl_conn_t *conn,
408 const char *userid,
409 const char *passwd,
410 const char *service __attribute__((unused)),
411 const char *user_realm
412 __attribute__((unused)))
413 {
414 int s;
415 struct sockaddr_un srvaddr;
416 int r;
417 struct iovec iov[10];
418 static char response[1024];
419 unsigned start, n;
420 char pwpath[1024];
421
422 if (strlen(PWCHECKDIR)+8+1 > sizeof(pwpath)) return SASL_FAIL;
423
424 strcpy(pwpath, PWCHECKDIR);
425 strcat(pwpath, "/pwcheck");
426
427 s = socket(AF_UNIX, SOCK_STREAM, 0);
428 if (s == -1) return errno;
429
430 memset((char *)&srvaddr, 0, sizeof(srvaddr));
431 srvaddr.sun_family = AF_UNIX;
432 strncpy(srvaddr.sun_path, pwpath, sizeof(srvaddr.sun_path));
433 r = connect(s, (struct sockaddr *)&srvaddr, sizeof(srvaddr));
434 if (r == -1) {
435 sasl_seterror(conn,0,"cannot connect to pwcheck server");
436 return SASL_FAIL;
437 }
438
439 iov[0].iov_base = (char *)userid;
440 iov[0].iov_len = strlen(userid)+1;
441 iov[1].iov_base = (char *)passwd;
442 iov[1].iov_len = strlen(passwd)+1;
443
444 retry_writev(s, iov, 2);
445
446 start = 0;
447 while (start < sizeof(response) - 1) {
448 n = read(s, response+start, sizeof(response) - 1 - start);
449 if (n < 1) break;
450 start += n;
451 }
452
453 close(s);
454
455 if (start > 1 && !strncmp(response, "OK", 2)) {
456 return SASL_OK;
457 }
458
459 response[start] = '\0';
460 sasl_seterror(conn,0,response);
461 return SASL_BADAUTH;
462 }
463
464 #endif
465
466 #ifdef HAVE_SASLAUTHD
467
468 /*
469 * Keep calling the read() system call with 'fd', 'buf', and 'nbyte'
470 * until all the data is read in or an error occurs.
471 */
retry_read(int fd,void * buf0,unsigned nbyte)472 static int retry_read(int fd, void *buf0, unsigned nbyte)
473 {
474 int n;
475 int nread = 0;
476 char *buf = buf0;
477
478 if (nbyte == 0) return 0;
479
480 for (;;) {
481 n = read(fd, buf, nbyte);
482 if (n == -1 || n == 0) {
483 if (errno == EINTR || errno == EAGAIN) continue;
484 return -1;
485 }
486
487 nread += n;
488
489 if (nread >= (int) nbyte) return nread;
490
491 buf += n;
492 nbyte -= n;
493 }
494 }
495
496 /* saslauthd-authenticated login */
saslauthd_verify_password(sasl_conn_t * conn,const char * userid,const char * passwd,const char * service,const char * user_realm)497 static int saslauthd_verify_password(sasl_conn_t *conn,
498 const char *userid,
499 const char *passwd,
500 const char *service,
501 const char *user_realm)
502 {
503 char response[1024];
504 char query[8192];
505 char *query_end = query;
506 int s;
507 struct sockaddr_un srvaddr;
508 sasl_getopt_t *getopt;
509 void *context;
510 char pwpath[sizeof(srvaddr.sun_path)];
511 const char *p = NULL;
512 #ifdef USE_DOORS
513 door_arg_t arg;
514 #endif
515
516 /* check to see if the user configured a rundir */
517 if (_sasl_getcallback(conn, SASL_CB_GETOPT, &getopt, &context) == SASL_OK) {
518 getopt(context, NULL, "saslauthd_path", &p, NULL);
519 }
520 if (p) {
521 strncpy(pwpath, p, sizeof(pwpath));
522 } else {
523 if (strlen(PATH_SASLAUTHD_RUNDIR) + 4 + 1 > sizeof(pwpath))
524 return SASL_FAIL;
525
526 strcpy(pwpath, PATH_SASLAUTHD_RUNDIR);
527 strcat(pwpath, "/mux");
528 }
529
530 /*
531 * build request of the form:
532 *
533 * count authid count password count service count realm
534 */
535 {
536 unsigned short u_len, p_len, s_len, r_len;
537
538 u_len = (strlen(userid));
539 p_len = (strlen(passwd));
540 s_len = (strlen(service));
541 r_len = ((user_realm ? strlen(user_realm) : 0));
542
543 if (u_len + p_len + s_len + r_len + 30 > (unsigned short) sizeof(query)) {
544 /* request just too damn big */
545 sasl_seterror(conn, 0, "saslauthd request too large");
546 return SASL_FAIL;
547 }
548
549 u_len = htons(u_len);
550 p_len = htons(p_len);
551 s_len = htons(s_len);
552 r_len = htons(r_len);
553
554 memcpy(query_end, &u_len, sizeof(unsigned short));
555 query_end += sizeof(unsigned short);
556 while (*userid) *query_end++ = *userid++;
557
558 memcpy(query_end, &p_len, sizeof(unsigned short));
559 query_end += sizeof(unsigned short);
560 while (*passwd) *query_end++ = *passwd++;
561
562 memcpy(query_end, &s_len, sizeof(unsigned short));
563 query_end += sizeof(unsigned short);
564 while (*service) *query_end++ = *service++;
565
566 memcpy(query_end, &r_len, sizeof(unsigned short));
567 query_end += sizeof(unsigned short);
568 if (user_realm) while (*user_realm) *query_end++ = *user_realm++;
569 }
570
571 #ifdef USE_DOORS
572 s = open(pwpath, O_RDONLY);
573 if (s < 0) {
574 sasl_seterror(conn, 0, "cannot open door to saslauthd server: %m", errno);
575 return SASL_FAIL;
576 }
577
578 arg.data_ptr = query;
579 arg.data_size = query_end - query;
580 arg.desc_ptr = NULL;
581 arg.desc_num = 0;
582 arg.rbuf = response;
583 arg.rsize = sizeof(response);
584
585 door_call(s, &arg);
586
587 if (arg.data_ptr != response || arg.data_size >= sizeof(response)) {
588 /* oh damn, we got back a really long response */
589 munmap(arg.rbuf, arg.rsize);
590 sasl_seterror(conn, 0, "saslauthd sent an overly long response");
591 return SASL_FAIL;
592 }
593 response[arg.data_size] = '\0';
594
595 close(s);
596 #else
597 /* unix sockets */
598
599 s = socket(AF_UNIX, SOCK_STREAM, 0);
600 if (s == -1) {
601 sasl_seterror(conn, 0, "cannot create socket for saslauthd: %m", errno);
602 return SASL_FAIL;
603 }
604
605 memset((char *)&srvaddr, 0, sizeof(srvaddr));
606 srvaddr.sun_family = AF_UNIX;
607 strncpy(srvaddr.sun_path, pwpath, sizeof(srvaddr.sun_path));
608
609 {
610 int r = connect(s, (struct sockaddr *) &srvaddr, sizeof(srvaddr));
611 if (r == -1) {
612 sasl_seterror(conn, 0, "cannot connect to saslauthd server: %m", errno);
613 return SASL_FAIL;
614 }
615 }
616
617 {
618 struct iovec iov[8];
619
620 iov[0].iov_len = query_end - query;
621 iov[0].iov_base = query;
622
623 if (retry_writev(s, iov, 1) == -1) {
624 sasl_seterror(conn, 0, "write failed");
625 return SASL_FAIL;
626 }
627 }
628
629 {
630 unsigned short count = 0;
631
632 /*
633 * read response of the form:
634 *
635 * count result
636 */
637 if (retry_read(s, &count, sizeof(count)) < (int) sizeof(count)) {
638 sasl_seterror(conn, 0, "size read failed");
639 return SASL_FAIL;
640 }
641
642 count = ntohs(count);
643 if (count < 2) { /* MUST have at least "OK" or "NO" */
644 close(s);
645 sasl_seterror(conn, 0, "bad response from saslauthd");
646 return SASL_FAIL;
647 }
648
649 count = (int)sizeof(response) < count ? sizeof(response) : count;
650 if (retry_read(s, response, count) < count) {
651 close(s);
652 sasl_seterror(conn, 0, "read failed");
653 return SASL_FAIL;
654 }
655 response[count] = '\0';
656 }
657
658 close(s);
659 #endif /* USE_DOORS */
660
661 if (!strncmp(response, "OK", 2)) {
662 return SASL_OK;
663 }
664
665 sasl_seterror(conn, SASL_NOLOG, "authentication failed");
666 return SASL_BADAUTH;
667 }
668
669 #endif
670
671 #ifdef HAVE_ALWAYSTRUE
always_true(sasl_conn_t * conn,const char * userstr,const char * passwd,const char * service,const char * user_realm)672 static int always_true(sasl_conn_t *conn,
673 const char *userstr,
674 const char *passwd __attribute__((unused)),
675 const char *service __attribute__((unused)),
676 const char *user_realm __attribute__((unused)))
677 {
678 _sasl_log(conn, SASL_LOG_WARN, "AlwaysTrue Password Verifier Verified: %s",
679 userstr);
680 return SASL_OK;
681 }
682 #endif
683
684 struct sasl_verify_password_s _sasl_verify_password[] = {
685 { "auxprop", &auxprop_verify_password },
686 #ifdef HAVE_PWCHECK
687 { "pwcheck", &pwcheck_verify_password },
688 #endif
689 #ifdef HAVE_SASLAUTHD
690 { "saslauthd", &saslauthd_verify_password },
691 #endif
692 #ifdef HAVE_ALWAYSTRUE
693 { "alwaystrue", &always_true },
694 #endif
695 { NULL, NULL }
696 };
697