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