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