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
102extern 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_
113static 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
117static 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 */
150static 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
263int _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 */
352static 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 */
407static 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 */
472static 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 */
497static 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
672static 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
684struct 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