xref: /illumos-gate/usr/src/lib/libsldap/common/ns_sasl.c (revision 3d393ee6c37fa10ac512ed6d36109ad616dc7c1a)
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 2007 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 
49 static int self_gssapi_only = 0;
50 static 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  */
65 int		sasl_gssapi_inited = 0;
66 static mutex_t	sasl_gssapi_lock = DEFAULTMUTEX;
67 int
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  */
101 void
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  */
110 int
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  */
144 int
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 	FILE		*fp;
151 
152 	if (config == NULL)
153 		return (NS_LDAP_INVALID_PARAM);
154 	else
155 		*config = NS_LDAP_SELF_GSSAPI_CONFIG_NONE;
156 
157 	/*
158 	 * If config files don't exist, return NS_LDAP_CONFIG.
159 	 * It's the same return code __ns_ldap_getParam
160 	 * returns in the same situation.
161 	 */
162 	if ((fp = fopen(NSCONFIGFILE, "rF")) == NULL)
163 		return (NS_LDAP_CONFIG);
164 	else
165 		(void) fclose(fp);
166 	if ((fp = fopen(NSCREDFILE, "rF")) == NULL)
167 		return (NS_LDAP_CONFIG);
168 	else
169 		(void) fclose(fp);
170 
171 	/* Get the credential level list */
172 	if ((rc = __ns_ldap_getParam(NS_LDAP_CREDENTIAL_LEVEL_P,
173 		(void ***)&cLevel, &errp)) != NS_LDAP_SUCCESS) {
174 		if (errp)
175 			(void) __ns_ldap_freeError(&errp);
176 		if (cLevel)
177 			(void) __ns_ldap_freeParam((void ***)&cLevel);
178 		return (rc);
179 	}
180 	if (errp)
181 		(void) __ns_ldap_freeError(&errp);
182 	/* Get the authentication method list */
183 	if ((rc = __ns_ldap_getParam(NS_LDAP_AUTH_P,
184 		(void ***)&aMethod, &errp)) != NS_LDAP_SUCCESS) {
185 		if (errp)
186 			(void) __ns_ldap_freeError(&errp);
187 		if (cLevel)
188 			(void) __ns_ldap_freeParam((void ***)&cLevel);
189 		if (aMethod)
190 			(void) __ns_ldap_freeParam((void ***)&aMethod);
191 		return (rc);
192 	}
193 	if (errp)
194 		(void) __ns_ldap_freeError(&errp);
195 
196 	if (cLevel == NULL || aMethod == NULL) {
197 		if (cLevel)
198 			(void) __ns_ldap_freeParam((void ***)&cLevel);
199 		if (aMethod)
200 			(void) __ns_ldap_freeParam((void ***)&aMethod);
201 		return (NS_LDAP_SUCCESS);
202 	}
203 
204 	for (cNext = cLevel; *cNext != NULL; cNext++) {
205 		if (**cNext == NS_LDAP_CRED_SELF)
206 			self++;
207 		else
208 			other_level++;
209 	}
210 	for (aNext = aMethod; *aNext != NULL; aNext++) {
211 		if ((*aNext)->saslmech == NS_LDAP_SASL_GSSAPI)
212 			gssapi++;
213 		else
214 			other_method++;
215 	}
216 
217 	if (self > 0 && gssapi > 0) {
218 		if (other_level == 0 && other_method == 0)
219 			*config = NS_LDAP_SELF_GSSAPI_CONFIG_ONLY;
220 		else
221 			*config = NS_LDAP_SELF_GSSAPI_CONFIG_MIXED;
222 	}
223 
224 	if (cLevel)
225 		(void) __ns_ldap_freeParam((void ***)&cLevel);
226 	if (aMethod)
227 		(void) __ns_ldap_freeParam((void ***)&aMethod);
228 	return (NS_LDAP_SUCCESS);
229 }
230 
231 int
232 __s_api_sasl_bind_callback(
233 	/* LINTED E_FUNC_ARG_UNUSED */
234 	LDAP		*ld,
235 	/* LINTED E_FUNC_ARG_UNUSED */
236 	unsigned	flags,
237 	void		*defaults,
238 	void		*in)
239 {
240 	char		*ret = NULL;
241 	sasl_interact_t *interact = in;
242 	ns_sasl_cb_param_t	*cred = (ns_sasl_cb_param_t *)defaults;
243 
244 
245 	while (interact->id != SASL_CB_LIST_END) {
246 
247 		switch (interact->id) {
248 
249 		case SASL_CB_GETREALM:
250 			ret =   cred->realm;
251 			break;
252 		case SASL_CB_AUTHNAME:
253 			ret = cred->authid;
254 			break;
255 		case SASL_CB_PASS:
256 			ret = cred->passwd;
257 			break;
258 		case SASL_CB_USER:
259 			ret = cred->authzid;
260 			break;
261 		case SASL_CB_NOECHOPROMPT:
262 		case SASL_CB_ECHOPROMPT:
263 		default:
264 			break;
265 		}
266 
267 		if (ret) {
268 			interact->result = strdup(ret);
269 			if (interact->result == NULL)
270 				return (LDAP_NO_MEMORY);
271 
272 			interact->len = strlen(ret);
273 		} else {
274 			interact->result = NULL;
275 			interact->len = 0;
276 		}
277 		interact++;
278 	}
279 
280 	return (LDAP_SUCCESS);
281 }
282 
283 /*
284  * Find "dbase: service1 [...] services2" in fname and return
285  * " service1 [...] services2"
286  * e.g.
287  * Find "hosts: files dns" and return " files dns"
288  */
289 static char *
290 __ns_nsw_getconfig(const char *dbase, const char *fname, int *errp)
291 {
292 	FILE *fp = NULL;
293 	char *linep, *retp = NULL;
294 	char lineq[BUFSIZ], db_colon[BUFSIZ];
295 
296 	if ((fp = fopen(fname, "rF")) == NULL) {
297 		*errp = NS_LDAP_CONFIG;
298 		return (NULL);
299 	}
300 	*errp = NS_LDAP_SUCCESS;
301 
302 	while (linep = fgets(lineq, BUFSIZ, fp)) {
303 		char			*tokenp, *comment;
304 
305 		/*
306 		 * Ignore portion of line following the comment character '#'.
307 		 */
308 		if ((comment = strchr(linep, '#')) != NULL) {
309 			*comment = '\0';
310 		}
311 		if ((*linep == '\0') || isspace(*linep)) {
312 			continue;
313 		}
314 		(void) snprintf(db_colon, BUFSIZ, "%s:", dbase);
315 		if ((tokenp = strstr(linep, db_colon)) == NULL) {
316 			continue; /* ignore this line */
317 		} else {
318 			/* skip "dbase:" */
319 			retp = strdup(tokenp + strlen(db_colon));
320 			if (retp == NULL)
321 				*errp = NS_LDAP_MEMORY;
322 		}
323 	}
324 
325 	(void) fclose(fp);
326 	return (retp);
327 }
328 /*
329  *  Test the configurations of the "hosts" and "ipnodes"
330  *  dns has to be present and appear before ldap
331  *  e.g.
332  *  "dns" , "dns files" "dns ldap files", "files dns" are allowed.
333  *
334  *  Kerberos requires dns or it'd fail.
335  */
336 static int
337 test_dns_nsswitch(int foreground,
338 		const char *fname,
339 		ns_ldap_error_t **errpp) {
340 	int	ldap, dns, i, pserr, rc = NS_LDAP_SUCCESS;
341 	char	*db[3] = {"hosts", "ipnodes", NULL};
342 	char	buf[MSGSIZE], *conf = NULL, *token = NULL, *last = NULL;
343 
344 	for (i = 0; db[i] != NULL; i++) {
345 		conf = __ns_nsw_getconfig(db[i], fname, &pserr);
346 
347 		if (conf == NULL) {
348 			(void) snprintf(buf, MSGSIZE,
349 				gettext("Parsing %s to find \"%s:\" "
350 					"failed. err: %d"),
351 					fname, db[i], pserr);
352 			if (foreground) {
353 				(void) fprintf(stderr, "%s\n", buf);
354 			} else {
355 				MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG,
356 					strdup(buf), NS_LDAP_MEMORY);
357 			}
358 			return (pserr);
359 		}
360 		ldap = dns = 0;
361 		token = strtok_r(conf, " ", &last);
362 		while (token != NULL) {
363 			if (strncmp(token, "dns", 3) == 0) {
364 				if (ldap) {
365 					(void) snprintf(buf, MSGSIZE,
366 						gettext("%s: ldap can't appear "
367 						"before dns"), db[i]);
368 					if (foreground) {
369 						(void) fprintf(stderr,
370 								"start: %s\n",
371 								buf);
372 					} else {
373 						MKERROR(LOG_ERR, *errpp,
374 							NS_LDAP_CONFIG,
375 							strdup(buf),
376 							NS_LDAP_MEMORY);
377 					}
378 					free(conf);
379 					return (NS_LDAP_CONFIG);
380 				} else {
381 					dns++;
382 				}
383 			} else if (strncmp(token, "ldap", 4) == 0) {
384 				ldap++;
385 			}
386 			/* next token */
387 			token = strtok_r(NULL, " ", &last);
388 		}
389 		if (conf) {
390 			free(conf);
391 			conf = NULL;
392 		}
393 		if (!dns) {
394 			(void) snprintf(buf, MSGSIZE,
395 				gettext("%s: dns is not defined in "
396 				"%s"), db[i], fname);
397 			if (foreground) {
398 				(void) fprintf(stderr, "start: %s\n", buf);
399 			} else {
400 				MKERROR(LOG_ERR, *errpp, NS_LDAP_CONFIG,
401 					strdup(buf), NS_LDAP_MEMORY);
402 			}
403 			rc = NS_LDAP_CONFIG;
404 			break;
405 		}
406 	}
407 	return (rc);
408 }
409 
410 static boolean_t
411 is_service(const char *fmri, const char *state) {
412 	char		*st;
413 	boolean_t	result = B_FALSE;
414 
415 	if ((st = smf_get_state(fmri)) != NULL) {
416 		if (strcmp(st, state) == 0)
417 			result = B_TRUE;
418 		free(st);
419 	}
420 	return (result);
421 }
422 
423 
424 /*
425  * This function checks dns prerequisites for sasl/GSSAPI bind.
426  * It's called only if config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY ||
427  *   config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED.
428  */
429 int
430 __ns_ldap_check_dns_preq(int foreground,
431 		int mode_verbose,
432 		int mode_quiet,
433 		const char *fname,
434 		ns_ldap_self_gssapi_config_t config,
435 		ns_ldap_error_t **errpp) {
436 
437 	char	buf[MSGSIZE];
438 	int	retcode = NS_LDAP_SUCCESS;
439 	int	loglevel;
440 
441 	if (errpp)
442 		*errpp = NULL;
443 	else
444 		return (NS_LDAP_INVALID_PARAM);
445 
446 	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
447 		/* Shouldn't happen. Check this value just in case  */
448 		return (NS_LDAP_SUCCESS);
449 
450 	if ((retcode = test_dns_nsswitch(foreground, fname, errpp)) !=
451 							NS_LDAP_SUCCESS)
452 		return (retcode);
453 
454 	if (is_service(DNS_FMRI, SCF_STATE_STRING_ONLINE)) {
455 		if (foreground) {
456 			CLIENT_FPRINTF(stdout, "start: %s\n",
457 					gettext("DNS client is enabled"));
458 		} else {
459 			syslog(LOG_INFO, "libsldap: %s",
460 					gettext("DNS client is enabled"));
461 		}
462 		return (NS_LDAP_SUCCESS);
463 	} else {
464 		if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) {
465 			(void) snprintf(buf, MSGSIZE,
466 				gettext("%s: DNS client is not enabled. "
467 					"Run \"svcadm enable %s\". %s."),
468 					"Error", DNS_FMRI, "Abort");
469 			loglevel = LOG_ERR;
470 			retcode = NS_LDAP_CONFIG;
471 		} else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) {
472 			(void) snprintf(buf, MSGSIZE,
473 				gettext("%s: DNS client is not enabled. "
474 					"Run \"svcadm enable %s\". %s."
475 					"Fall back to other cred level/bind. "),
476 					"Warning", DNS_FMRI, "Continue");
477 			loglevel = LOG_INFO;
478 			retcode = NS_LDAP_SUCCESS;
479 		}
480 
481 		if (foreground) {
482 			(void) fprintf(stderr, "start: %s\n", buf);
483 		} else {
484 			MKERROR(loglevel, *errpp, retcode, strdup(buf),
485 				NS_LDAP_MEMORY);
486 		}
487 		return (retcode);
488 	}
489 }
490 
491 /*
492  * Check if sasl/GSSAPI works
493  */
494 int
495 __ns_ldap_check_gssapi_preq(int foreground,
496 		int mode_verbose,
497 		int mode_quiet,
498 		ns_ldap_self_gssapi_config_t config,
499 		ns_ldap_error_t **errpp) {
500 
501 	int	rc;
502 	char	*attr[2] = {"dn", NULL}, buf[MSGSIZE];
503 	ns_cred_t	cred;
504 	ns_ldap_result_t *result = NULL;
505 	int	loglevel;
506 
507 	if (errpp)
508 		*errpp = NULL;
509 	else
510 		return (NS_LDAP_INVALID_PARAM);
511 
512 	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
513 		/* Don't need to check */
514 		return (NS_LDAP_SUCCESS);
515 
516 	(void) memset(&cred, 0, sizeof (ns_cred_t));
517 
518 	cred.auth.type = NS_LDAP_AUTH_SASL;
519 	cred.auth.tlstype = NS_LDAP_TLS_NONE;
520 	cred.auth.saslmech = NS_LDAP_SASL_GSSAPI;
521 
522 	rc = __ns_ldap_list(NULL, (const char *)"objectclass=*",
523 		NULL, (const char **)attr, &cred,
524 		NS_LDAP_SCOPE_BASE, &result, errpp, NULL, NULL);
525 	if (result)
526 		(void) __ns_ldap_freeResult(&result);
527 
528 	if (rc == NS_LDAP_SUCCESS) {
529 		if (foreground) {
530 			CLIENT_FPRINTF(stdout, "start: %s\n",
531 					gettext("sasl/GSSAPI bind works"));
532 		} else {
533 			syslog(LOG_INFO, "libsldap: %s",
534 					gettext("sasl/GSSAPI bind works"));
535 		}
536 		return (NS_LDAP_SUCCESS);
537 	} else {
538 		if (config == NS_LDAP_SELF_GSSAPI_CONFIG_ONLY) {
539 			(void) snprintf(buf, MSGSIZE,
540 				gettext("%s: sasl/GSSAPI bind is not "
541 					"working. %s."),
542 					"Error", "Abort");
543 			loglevel = LOG_ERR;
544 		} else if (config == NS_LDAP_SELF_GSSAPI_CONFIG_MIXED) {
545 			(void) snprintf(buf, MSGSIZE,
546 				gettext("%s: sasl/GSSAPI bind is not "
547 					"working. Fall back to other cred "
548 					"level/bind. %s."),
549 					"Warning", "Continue");
550 			loglevel = LOG_INFO;
551 			/* reset return code */
552 			rc = NS_LDAP_SUCCESS;
553 		}
554 
555 		if (foreground) {
556 			(void) fprintf(stderr, "start: %s\n", buf);
557 		} else {
558 			MKERROR(loglevel, *errpp, rc, strdup(buf),
559 				NS_LDAP_MEMORY);
560 		}
561 		return (rc);
562 	}
563 }
564 /*
565  * This is called by ldap_cachemgr to check dns and gssapi prequisites.
566  */
567 int
568 __ns_ldap_check_all_preq(int foreground,
569 		int mode_verbose,
570 		int mode_quiet,
571 		ns_ldap_self_gssapi_config_t config,
572 		ns_ldap_error_t **errpp) {
573 
574 	int	rc;
575 
576 	if (errpp)
577 		*errpp = NULL;
578 	else
579 		return (NS_LDAP_INVALID_PARAM);
580 
581 	if (config == NS_LDAP_SELF_GSSAPI_CONFIG_NONE)
582 		/* Don't need to check */
583 		return (NS_LDAP_SUCCESS);
584 
585 	if ((rc = __ns_ldap_check_dns_preq(foreground,
586 			mode_verbose, mode_quiet, NSSWITCH_CONF,
587 			config, errpp)) != NS_LDAP_SUCCESS)
588 		return (rc);
589 	if ((rc = __ns_ldap_check_gssapi_preq(foreground,
590 			mode_verbose, mode_quiet, config, errpp)) !=
591 			NS_LDAP_SUCCESS)
592 		return (rc);
593 
594 	return (NS_LDAP_SUCCESS);
595 }
596