xref: /illumos-gate/usr/src/lib/libsasl/lib/checkpw.c (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
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_
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  */
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
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  */
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 */
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  */
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 */
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
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