ns_sasl.c revision 2830:5228d1267a01
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <strings.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <unistd.h>
34#include <thread.h>
35#include <synch.h>
36#include <sasl/sasl.h>
37#include <sys/socket.h>
38#include <netdb.h>
39#include <netinet/in.h>
40#include <arpa/inet.h>
41#include <syslog.h>
42#include <ctype.h>
43#include <libscf.h>
44#include <libintl.h>
45#include <locale.h>
46#include "ns_sldap.h"
47#include "ns_internal.h"
48
49static int self_gssapi_only = 0;
50static mutex_t self_gssapi_only_lock = DEFAULTMUTEX;
51
52#define	DNS_FMRI	"svc:/network/dns/client:default"
53#define	MSGSIZE		256
54
55#define	NSSWITCH_CONF	"/etc/nsswitch.conf"
56
57/*
58 * Error Handling
59 */
60#define	CLIENT_FPRINTF if (mode_verbose && !mode_quiet) (void) fprintf
61
62/*
63 * One time initializtion
64 */
65int		sasl_gssapi_inited = 0;
66static mutex_t	sasl_gssapi_lock = DEFAULTMUTEX;
67int
68__s_api_sasl_gssapi_init(void) {
69	int rc = NS_LDAP_SUCCESS;
70	(void) mutex_lock(&sasl_gssapi_lock);
71	if (!sasl_gssapi_inited) {
72			if (getuid() == 0) {
73				if (system(
74					"/usr/sbin/cryptoadm disable metaslot")
75					== 0) {
76					syslog(LOG_WARNING,
77						"libsldap: Metaslot disabled "
78						"for self credential mode");
79					sasl_gssapi_inited = 1;
80				} else {
81					syslog(LOG_ERR,
82						"libsldap: Can't disable "
83						"Metaslot for self credential "
84						"mode");
85					rc = NS_LDAP_INTERNAL;
86				}
87			}
88	}
89	(void) mutex_unlock(&sasl_gssapi_lock);
90
91	return (rc);
92}
93
94/*
95 * nscd calls this function to set self_gssapi_only flag so libsldap performs
96 * sasl/GSSAPI bind only. Also see comments of __ns_ldap_self_gssapi_config.
97 *
98 * Input: flag 0 use any kind of connection
99 *             1 use self/gssapi connection only
100 */
101void
102__ns_ldap_self_gssapi_only_set(int flag) {
103	(void) mutex_lock(&self_gssapi_only_lock);
104	self_gssapi_only = flag;
105	(void) mutex_unlock(&self_gssapi_only_lock);
106}
107/*
108 * Get the flag value of self_gssapi_only
109 */
110int
111__s_api_self_gssapi_only_get(void) {
112	int flag;
113	(void) mutex_lock(&self_gssapi_only_lock);
114	flag = self_gssapi_only;
115	(void) mutex_unlock(&self_gssapi_only_lock);
116	return (flag);
117}
118/*
119 * nscd calls this function to detect the current native ldap configuration.
120 * The output are
121 * NS_LDAP_SELF_GSSAPI_CONFIG_NONE: No credential level self and
122 *                                  no authentication method sasl/GSSAPI is
123 *                                  configured.
124 * NS_LDAP_SELF_GSSAPI_CONFIG_ONLY: Only credential level self and
125 *                                  authentication method sasl/GSSAPI are
126 *                                  configured.
127 * NS_LDAP_SELF_GSSAPI_CONFIG_MIXED: More than one credential level are
128 *                                   configured, including self.
129 *                                   More than one authentication method
130 *                                   are configured, including sasl/GSSAPI.
131 *
132 * __s_api_crosscheck makes sure self and sasl/GSSAPI pair up if they do
133 * get configured.
134 *
135 * When nscd detects it's MIXED case, it calls __ns_ldap_self_gssapi_only_set
136 * to force libsldap to do sasl/GSSAPI bind only for per-user lookup.
137 *
138 * Return: NS_LDAP_SUCCESS
139 *         OTHERWISE - FAILURE
140 *
141 * Output: config. See comments above.
142 *
143 */
144int
145__ns_ldap_self_gssapi_config(ns_ldap_self_gssapi_config_t *config) {
146	int	self = 0, other_level = 0, gssapi = 0, other_method = 0;
147	ns_auth_t	**aMethod = NULL, **aNext = NULL;
148	int		**cLevel = NULL, **cNext = NULL, rc;
149	ns_ldap_error_t	*errp = NULL;
150
151	if (config == NULL)
152		return (NS_LDAP_INVALID_PARAM);
153	else
154		*config = NS_LDAP_SELF_GSSAPI_CONFIG_NONE;
155
156	/* Get the credential level list */
157	if ((rc = __ns_ldap_getParam(NS_LDAP_CREDENTIAL_LEVEL_P,
158		(void ***)&cLevel, &errp)) != NS_LDAP_SUCCESS) {
159		if (errp)
160			(void) __ns_ldap_freeError(&errp);
161		if (cLevel)
162			(void) __ns_ldap_freeParam((void ***)&cLevel);
163		return (rc);
164	}
165	if (errp)
166		(void) __ns_ldap_freeError(&errp);
167	/* Get the authentication method list */
168	if ((rc = __ns_ldap_getParam(NS_LDAP_AUTH_P,
169		(void ***)&aMethod, &errp)) != NS_LDAP_SUCCESS) {
170		if (errp)
171			(void) __ns_ldap_freeError(&errp);
172		if (cLevel)
173			(void) __ns_ldap_freeParam((void ***)&cLevel);
174		if (aMethod)
175			(void) __ns_ldap_freeParam((void ***)&aMethod);
176		return (rc);
177	}
178	if (errp)
179		(void) __ns_ldap_freeError(&errp);
180
181	if (cLevel == NULL || aMethod == NULL) {
182		if (cLevel)
183			(void) __ns_ldap_freeParam((void ***)&cLevel);
184		if (aMethod)
185			(void) __ns_ldap_freeParam((void ***)&aMethod);
186		return (NS_LDAP_SUCCESS);
187	}
188
189	for (cNext = cLevel; *cNext != NULL; cNext++) {
190		if (**cNext == NS_LDAP_CRED_SELF)
191			self++;
192		else
193			other_level++;
194	}
195	for (aNext = aMethod; *aNext != NULL; aNext++) {
196		if ((*aNext)->saslmech == NS_LDAP_SASL_GSSAPI)
197			gssapi++;
198		else
199			other_method++;
200	}
201
202	if (self > 0 && gssapi > 0) {
203		if (other_level == 0 && other_method == 0)
204			*config = NS_LDAP_SELF_GSSAPI_CONFIG_ONLY;
205		else
206			*config = NS_LDAP_SELF_GSSAPI_CONFIG_MIXED;
207	}
208
209	if (cLevel)
210		(void) __ns_ldap_freeParam((void ***)&cLevel);
211	if (aMethod)
212		(void) __ns_ldap_freeParam((void ***)&aMethod);
213	return (NS_LDAP_SUCCESS);
214}
215
216int
217__s_api_sasl_bind_callback(
218	/* LINTED E_FUNC_ARG_UNUSED */
219	LDAP		*ld,
220	/* LINTED E_FUNC_ARG_UNUSED */
221	unsigned	flags,
222	void		*defaults,
223	void		*in)
224{
225	char		*ret = NULL;
226	sasl_interact_t *interact = in;
227	ns_sasl_cb_param_t	*cred = (ns_sasl_cb_param_t *)defaults;
228
229
230	while (interact->id != SASL_CB_LIST_END) {
231
232		switch (interact->id) {
233
234		case SASL_CB_GETREALM:
235			ret =   cred->realm;
236			break;
237		case SASL_CB_AUTHNAME:
238			ret = cred->authid;
239			break;
240		case SASL_CB_PASS:
241			ret = cred->passwd;
242			break;
243		case SASL_CB_USER:
244			ret = cred->authzid;
245			break;
246		case SASL_CB_NOECHOPROMPT:
247		case SASL_CB_ECHOPROMPT:
248		default:
249			break;
250		}
251
252		if (ret) {
253			interact->result = strdup(ret);
254			if (interact->result == NULL)
255				return (LDAP_NO_MEMORY);
256
257			interact->len = strlen(ret);
258		} else {
259			interact->result = NULL;
260			interact->len = 0;
261		}
262		interact++;
263	}
264
265	return (LDAP_SUCCESS);
266}
267
268/*
269 * Find "dbase: service1 [...] services2" in fname and return
270 * " service1 [...] services2"
271 * e.g.
272 * Find "hosts: files dns" and return " files dns"
273 */
274static char *
275__ns_nsw_getconfig(const char *dbase, const char *fname, int *errp)
276{
277	FILE *fp = NULL;
278	char *linep, *retp = NULL;
279	char lineq[BUFSIZ], db_colon[BUFSIZ];
280
281	if ((fp = fopen(fname, "rF")) == NULL) {
282		*errp = NS_LDAP_CONFIG;
283		return (NULL);
284	}
285	*errp = NS_LDAP_SUCCESS;
286
287	while (linep = fgets(lineq, BUFSIZ, fp)) {
288		char			*tokenp, *comment;
289
290		/*
291		 * Ignore portion of line following the comment character '#'.
292		 */
293		if ((comment = strchr(linep, '#')) != NULL) {
294			*comment = '\0';
295		}
296		if ((*linep == '\0') || isspace(*linep)) {
297			continue;
298		}
299		(void) snprintf(db_colon, BUFSIZ, "%s:", dbase);
300		if ((tokenp = strstr(linep, db_colon)) == NULL) {
301			continue; /* ignore this line */
302		} else {
303			/* skip "dbase:" */
304			retp = strdup(tokenp + strlen(db_colon));
305			if (retp == NULL)
306				*errp = NS_LDAP_MEMORY;
307		}
308	}
309
310	(void) fclose(fp);
311	return (retp);
312}
313/*
314 *  Test the configurations of the "hosts" and "ipnodes"
315 *  dns has to be present and appear before ldap
316 *  e.g.
317 *  "dns" , "dns files" "dns ldap files", "files dns" are allowed.
318 *
319 *  Kerberos requires dns or it'd fail.
320 */
321static int
322test_dns_nsswitch(int foreground,
323		const char *fname,
324		ns_ldap_error_t **errpp) {
325	int	ldap, dns, i, pserr, rc = NS_LDAP_SUCCESS;
326	char	*db[3] = {"hosts", "ipnodes", NULL};
327	char	buf[MSGSIZE], *conf = NULL, *token = NULL, *last = NULL;
328
329	for (i = 0; db[i] != NULL; i++) {
330		conf = __ns_nsw_getconfig(db[i], fname, &pserr);
331
332		if (conf == NULL) {
333			(void) snprintf(buf, MSGSIZE,
334				gettext("Parsing %s to find \"%s:\" "
335					"failed. err: %d"),
336					fname, db[i], pserr);
337			if (foreground) {
338				(void) fprintf(stderr, "%s\n", buf);
339			} else {
340				MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG,
341					strdup(buf), NS_LDAP_MEMORY);
342			}
343			return (pserr);
344		}
345		ldap = dns = 0;
346		token = strtok_r(conf, " ", &last);
347		while (token != NULL) {
348			if (strncmp(token, "dns", 3) == 0) {
349				if (ldap) {
350					(void) snprintf(buf, MSGSIZE,
351						gettext("%s: ldap can't appear "
352						"before dns"), db[i]);
353					if (foreground) {
354						(void) fprintf(stderr,
355								"start: %s\n",
356								buf);
357					} else {
358						MKERROR(LOG_ERR, *errpp,
359							NS_LDAP_CONFIG,
360							strdup(buf),
361							NS_LDAP_MEMORY);
362					}
363					free(conf);
364					return (NS_LDAP_CONFIG);
365				} else {
366					dns++;
367				}
368			} else if (strncmp(token, "ldap", 4) == 0) {
369				ldap++;
370			}
371			/* next token */
372			token = strtok_r(NULL, " ", &last);
373		}
374		if (conf) {
375			free(conf);
376			conf = NULL;
377		}
378		if (!dns) {
379			(void) snprintf(buf, MSGSIZE,
380				gettext("%s: dns is not defined in "
381				"%s"), db[i], fname);
382			if (foreground) {
383				(void) fprintf(stderr, "start: %s\n", buf);
384			} else {
385				MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG,
386					strdup(buf), NS_LDAP_MEMORY);
387			}
388			rc = NS_LDAP_CONFIG;
389			break;
390		}
391	}
392	return (rc);
393}
394
395static boolean_t
396is_service(const char *fmri, const char *state) {
397	char		*st;
398	boolean_t	result = B_FALSE;
399
400	if ((st = smf_get_state(fmri)) != NULL) {
401		if (strcmp(st, state) == 0)
402			result = B_TRUE;
403		free(st);
404	}
405	return (result);
406}
407
408
409/*
410 * This function checks dns prerequisites for sasl/GSSAPI bind.
411 * It's called only if config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY ||
412 *   config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED.
413 */
414int
415__ns_ldap_check_dns_preq(int foreground,
416		int mode_verbose,
417		int mode_quiet,
418		const char *fname,
419		ns_ldap_self_gssapi_config_t config,
420		ns_ldap_error_t **errpp) {
421
422	char	buf[MSGSIZE];
423	int	retcode = NS_LDAP_SUCCESS;
424	int	loglevel;
425
426	if (errpp)
427		*errpp = NULL;
428	else
429		return (NS_LDAP_INVALID_PARAM);
430
431	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
432		/* Shouldn't happen. Check this value just in case  */
433		return (NS_LDAP_SUCCESS);
434
435	if ((retcode = test_dns_nsswitch(foreground, fname, errpp)) !=
436							NS_LDAP_SUCCESS)
437		return (retcode);
438
439	if (is_service(DNS_FMRI, SCF_STATE_STRING_ONLINE)) {
440		if (foreground) {
441			CLIENT_FPRINTF(stdout, "start: %s\n",
442					gettext("DNS client is enabled"));
443		} else {
444			syslog(LOG_INFO, "%s",
445					gettext("DNS client is enabled"));
446		}
447		return (NS_LDAP_SUCCESS);
448	} else {
449		if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) {
450			(void) snprintf(buf, MSGSIZE,
451				gettext("%s: DNS client is not enabled. "
452					"Run \"svcadm enable %s\". %s."),
453					"Error", DNS_FMRI, "Abort");
454			loglevel = LOG_ERR;
455			retcode = NS_LDAP_CONFIG;
456		} else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) {
457			(void) snprintf(buf, MSGSIZE,
458				gettext("%s: DNS client is not enabled. "
459					"Run \"svcadm enable %s\". %s."
460					"Fall back to other cred level/bind. "),
461					"Warning", DNS_FMRI, "Continue");
462			loglevel = LOG_INFO;
463			retcode = NS_LDAP_SUCCESS;
464		}
465
466		if (foreground) {
467			(void) fprintf(stderr, "start: %s\n", buf);
468		} else {
469			MKERROR(loglevel, *errpp, retcode, strdup(buf),
470				NS_LDAP_MEMORY);
471		}
472		return (retcode);
473	}
474}
475
476/*
477 * Check if sasl/GSSAPI works
478 */
479int
480__ns_ldap_check_gssapi_preq(int foreground,
481		int mode_verbose,
482		int mode_quiet,
483		ns_ldap_self_gssapi_config_t config,
484		ns_ldap_error_t **errpp) {
485
486	int	rc;
487	char	*attr[2] = {"dn", NULL}, buf[MSGSIZE];
488	ns_cred_t	cred;
489	ns_ldap_result_t *result = NULL;
490	int	loglevel;
491
492	if (errpp)
493		*errpp = NULL;
494	else
495		return (NS_LDAP_INVALID_PARAM);
496
497	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
498		/* Don't need to check */
499		return (NS_LDAP_SUCCESS);
500
501	(void) memset(&cred, 0, sizeof (ns_cred_t));
502
503	cred.auth.type = NS_LDAP_AUTH_SASL;
504	cred.auth.tlstype = NS_LDAP_TLS_NONE;
505	cred.auth.saslmech = NS_LDAP_SASL_GSSAPI;
506
507	rc = __ns_ldap_list(NULL, (const char *)"objectclass=*",
508		NULL, (const char **)attr, &cred,
509		NS_LDAP_SCOPE_BASE, &result, errpp, NULL, NULL);
510	if (result)
511		(void) __ns_ldap_freeResult(&result);
512
513	if (rc == NS_LDAP_SUCCESS) {
514		if (foreground) {
515			CLIENT_FPRINTF(stdout, "start: %s\n",
516					gettext("sasl/GSSAPI bind works"));
517		} else {
518			syslog(LOG_INFO, "%s",
519					gettext("sasl/GSSAPI bind works"));
520		}
521		return (NS_LDAP_SUCCESS);
522	} else {
523		if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) {
524			(void) snprintf(buf, MSGSIZE,
525				gettext("%s: sasl/GSSAPI bind is not "
526					"working. %s."),
527					"Error", "Abort");
528			loglevel = LOG_ERR;
529		} else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) {
530			(void) snprintf(buf, MSGSIZE,
531				gettext("%s: sasl/GSSAPI bind is not "
532					"working. Fall back to other cred "
533					"level/bind. %s."),
534					"Warning", "Continue");
535			loglevel = LOG_INFO;
536			/* reset return code */
537			rc = NS_LDAP_SUCCESS;
538		}
539
540		if (foreground) {
541			(void) fprintf(stderr, "start: %s\n", buf);
542		} else {
543			MKERROR(loglevel, *errpp, rc, strdup(buf),
544				NS_LDAP_MEMORY);
545		}
546		return (rc);
547	}
548}
549/*
550 * This is called by ldap_cachemgr to check dns and gssapi prequisites.
551 */
552int
553__ns_ldap_check_all_preq(int foreground,
554		int mode_verbose,
555		int mode_quiet,
556		ns_ldap_self_gssapi_config_t config,
557		ns_ldap_error_t **errpp) {
558
559	int	rc;
560
561	if (errpp)
562		*errpp = NULL;
563	else
564		return (NS_LDAP_INVALID_PARAM);
565
566	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
567		/* Don't need to check */
568		return (NS_LDAP_SUCCESS);
569
570	if ((rc = __ns_ldap_check_dns_preq(foreground,
571			mode_verbose, mode_quiet, NSSWITCH_CONF,
572			config, errpp)) != NS_LDAP_SUCCESS)
573		return (rc);
574	if ((rc = __ns_ldap_check_gssapi_preq(foreground,
575			mode_verbose, mode_quiet, config, errpp)) !=
576			NS_LDAP_SUCCESS)
577		return (rc);
578
579	return (NS_LDAP_SUCCESS);
580}
581