xref: /netbsd/usr.bin/login/k5login.c (revision bf9ec67e)
1 /*	$NetBSD: k5login.c,v 1.22 2002/02/20 08:17:17 joda Exp $	*/
2 
3 /*-
4  * Copyright (c) 1990 The Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 /*
37  * Copyright (c) 1980, 1987, 1988 The Regents of the University of California.
38  * All rights reserved.
39  *
40  * Redistribution and use in source and binary forms are permitted
41  * provided that the above copyright notice and this paragraph are
42  * duplicated in all such forms and that any documentation,
43  * advertising materials, and other materials related to such
44  * distribution and use acknowledge that the software was developed
45  * by the University of California, Berkeley.  The name of the
46  * University may not be used to endorse or promote products derived
47  * from this software without specific prior written permission.
48  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
49  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
50  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
51  */
52 
53 #include <sys/cdefs.h>
54 #ifndef lint
55 #if 0
56 static char sccsid[] = "@(#)klogin.c	5.11 (Berkeley) 7/12/92";
57 #endif
58 __RCSID("$NetBSD: k5login.c,v 1.22 2002/02/20 08:17:17 joda Exp $");
59 #endif /* not lint */
60 
61 #ifdef KERBEROS5
62 #include <sys/param.h>
63 #include <sys/syslog.h>
64 #include <krb5/krb5.h>
65 #include <kerberosIV/krb.h>
66 #include <pwd.h>
67 #include <netdb.h>
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <unistd.h>
72 #include <errno.h>
73 
74 #define KRB5_DEFAULT_OPTIONS 0
75 #define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
76 
77 krb5_context kcontext;
78 
79 int notickets;
80 int krb5_configured;
81 char *krb5tkfile_env;
82 extern char *tty;
83 extern int login_krb5_forwardable_tgt;
84 extern int has_ccache;
85 
86 static char tkt_location[MAXPATHLEN];
87 #ifdef KERBEROS
88 char krb4tkfile[MAXPATHLEN];
89 #endif
90 static krb5_creds forw_creds;
91 int have_forward;
92 static krb5_principal me, server;
93 
94 int k5_read_creds(char *);
95 int k5_write_creds(void);
96 int k5_verify_creds(krb5_context, krb5_ccache);
97 int k5login(struct passwd *, char *, char *, char *);
98 void k5destroy(void);
99 
100 #ifdef KERBEROS
101 static krb5_error_code
102 krb5_to4 (struct passwd *pw, krb5_context context, krb5_ccache id);
103 #endif
104 
105 #ifndef krb5_realm_length
106 #define krb5_realm_length(r)	((r).length)
107 #endif
108 #ifndef krb5_realm_data
109 #define krb5_realm_data(r)	((r).data)
110 #endif
111 
112 /*
113  * Verify the Kerberos ticket-granting ticket just retrieved for the
114  * user.  If the Kerberos server doesn't respond, assume the user is
115  * trying to fake us out (since we DID just get a TGT from what is
116  * supposedly our KDC).  If the host/<host> service is unknown (i.e.,
117  * the local keytab doesn't have it), let her in.
118  *
119  * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
120  */
121 int
122 k5_verify_creds(c, ccache)
123 	krb5_context c;
124 	krb5_ccache ccache;
125 {
126 	char phost[MAXHOSTNAMELEN];
127 	int retval, have_keys;
128 	krb5_principal princ;
129 	krb5_keyblock *kb = 0;
130 	krb5_error_code kerror;
131 	krb5_data packet;
132 	krb5_auth_context auth_context = NULL;
133 	krb5_ticket *ticket = NULL;
134 
135 	kerror = krb5_sname_to_principal(c, 0, 0, KRB5_NT_SRV_HST, &princ);
136 	if (kerror) {
137 		krb5_warn(kcontext, kerror, "constructing local service name");
138 		return (-1);
139 	}
140 
141 	/* Do we have host/<host> keys? */
142 	/* (use default keytab, kvno IGNORE_VNO to get the first match,
143 	 * and default enctype.) */
144 	kerror = krb5_kt_read_service_key(c, NULL, princ, 0, 0, &kb);
145 	if (kb)
146 		krb5_free_keyblock(c, kb);
147 	/* any failure means we don't have keys at all. */
148 	have_keys = kerror ? 0 : 1;
149 
150 	/* XXX there should be a krb5 function like mk_req, but taking a full
151 	 * principal, instead of a service/hostname.  (Did I miss one?) */
152 	gethostname(phost, sizeof(phost));
153 	phost[sizeof(phost) - 1] = '\0';
154 
155 	/* talk to the kdc and construct the ticket */
156 	kerror = krb5_mk_req(c, &auth_context, 0, "host", phost,
157 	                     0, ccache, &packet);
158 	/* wipe the auth context for rd_req */
159 	if (auth_context) {
160 		krb5_auth_con_free(c, auth_context);
161 		auth_context = NULL;
162 	}
163 	if (kerror == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
164 		/* we have a service key, so something should be
165 		 * in the database, therefore this error packet could
166 		 * have come from an attacker. */
167 		if (have_keys) {
168 			retval = -1;
169 			goto EGRESS;
170 		}
171 		/* but if it is unknown and we've got no key, we don't
172 		 * have any security anyhow, so it is ok. */
173 		else {
174 			retval = 0;
175 			goto EGRESS;
176 		}
177 	}
178 	else if (kerror) {
179 		krb5_warn(kcontext, kerror,
180 			  "Unable to verify Kerberos V5 TGT: %s", phost);
181 		syslog(LOG_NOTICE, "Kerberos V5 TGT bad: %s",
182 		       krb5_get_err_text(kcontext, kerror));
183 		retval = -1;
184 		goto EGRESS;
185 	}
186 	/* got ticket, try to use it */
187 	kerror = krb5_rd_req(c, &auth_context, &packet,
188 	                     princ, NULL, NULL, &ticket);
189 	if (kerror) {
190 		if (!have_keys) {
191 			/* The krb5 errors aren't specified well, but I think
192 			 * these values cover the cases we expect. */
193 			switch (kerror) {
194 			case ENOENT:	/* no keytab */
195 			case KRB5_KT_NOTFOUND:
196 				retval = 0;
197 				break;
198 			default:
199 				/* unexpected error: fail */
200 				retval = -1;
201 				break;
202 			}
203 		}
204 		else {
205 			/* we have keys, so if we got any error, we could be
206 			 * under attack. */
207 			retval = -1;
208 		}
209 		krb5_warn(kcontext, kerror, "Unable to verify host ticket");
210 		syslog(LOG_NOTICE, "can't verify v5 ticket: %s; %s\n",
211 		       krb5_get_err_text(kcontext, kerror),
212 		       retval
213 		         ? "keytab found, assuming failure"
214 		         : "no keytab found, assuming success");
215 		goto EGRESS;
216 	}
217 	/*
218 	 * The host/<host> ticket has been received _and_ verified.
219 	 */
220 	retval = 1;
221 
222 	/* do cleanup and return */
223 EGRESS:
224 	if (auth_context)
225 		krb5_auth_con_free(c, auth_context);
226 	krb5_free_principal(c, princ);
227 	/* possibly ticket and packet need freeing here as well */
228 	return (retval);
229 }
230 
231 /*
232  * Attempt to read forwarded kerberos creds
233  *
234  * return 0 on success (forwarded creds in memory)
235  *        1 if no forwarded creds.
236  */
237 int
238 k5_read_creds(username)
239 	char *username;
240 {
241 	krb5_error_code kerror;
242 	krb5_creds mcreds;
243 	krb5_ccache ccache;
244 
245 	have_forward = 0;
246 	memset((char*) &mcreds, 0, sizeof(forw_creds));
247 	memset((char*) &forw_creds, 0, sizeof(forw_creds));
248 
249 	kerror = krb5_cc_default(kcontext, &ccache);
250 	if (kerror) {
251 		krb5_warn(kcontext, kerror, "while getting default ccache");
252 		return(1);
253 	}
254 
255 	kerror = krb5_parse_name(kcontext, username, &me);
256 	if (kerror) {
257 		krb5_warn(kcontext, kerror, "when parsing name %s", username);
258 		return(1);
259 	}
260 
261 	mcreds.client = me;
262 	kerror = krb5_build_principal_ext(kcontext, &mcreds.server,
263 			krb5_realm_length(*krb5_princ_realm(kcontext, me)),
264 			krb5_realm_data(*krb5_princ_realm(kcontext, me)),
265 			KRB5_TGS_NAME_SIZE,
266 			KRB5_TGS_NAME,
267 			krb5_realm_length(*krb5_princ_realm(kcontext, me)),
268 			krb5_realm_data(*krb5_princ_realm(kcontext, me)),
269 			0);
270 	if (kerror) {
271 		krb5_warn(kcontext, kerror, "while building server name");
272 		goto nuke_ccache;
273 	}
274 
275 	kerror = krb5_cc_retrieve_cred(kcontext, ccache, 0,
276 				       &mcreds, &forw_creds);
277 	if (kerror) {
278 		krb5_warn(kcontext, kerror,
279 			  "while retrieving V5 initial ticket for copy");
280 		goto nuke_ccache;
281 	}
282 
283 	have_forward = 1;
284 
285 	strlcpy(tkt_location, getenv("KRB5CCNAME"), sizeof(tkt_location));
286 	krb5tkfile_env = tkt_location;
287 	has_ccache = 1;
288 	notickets = 0;
289 
290 nuke_ccache:
291 	krb5_cc_destroy(kcontext, ccache);
292 	return(!have_forward);
293 }
294 
295 int
296 k5_write_creds()
297 {
298 	krb5_error_code kerror;
299 	krb5_ccache ccache;
300 
301 	if (!have_forward)
302 		return (1);
303 
304 	kerror = krb5_cc_default(kcontext, &ccache);
305 	if (kerror) {
306 		krb5_warn(kcontext, kerror, "while getting default ccache");
307 		return (1);
308 	}
309 
310 	kerror = krb5_cc_initialize(kcontext, ccache, me);
311 	if (kerror) {
312 		krb5_warn(kcontext, kerror,
313 			  "while re-initializing V5 ccache as user");
314 		goto nuke_ccache_contents;
315 	}
316 
317 	kerror = krb5_cc_store_cred(kcontext, ccache, &forw_creds);
318 	if (kerror) {
319 		krb5_warn(kcontext, kerror,
320 			  "while re-storing V5 ccache as user");
321 		goto nuke_ccache_contents;
322 	}
323 
324 nuke_ccache_contents:
325 	krb5_free_cred_contents(kcontext, &forw_creds);
326 	return (kerror != 0);
327 }
328 
329 /*
330  * Get krb4 credentials if needed
331  */
332 #ifdef KERBEROS
333 static krb5_error_code
334 krb5_to4 (struct passwd *pw, krb5_context context, krb5_ccache id)
335 {
336 	if (krb5_config_get_bool(context, NULL,
337 				 "libdefaults",
338 				 "krb4_get_tickets",
339 				 NULL)) {
340 		CREDENTIALS c;
341 		krb5_creds mcred, cred;
342 		krb5_error_code ret;
343 		krb5_principal princ;
344 
345 		ret = krb5_cc_get_principal (context, id, &princ);
346 		if (ret)
347 			return ret;
348 
349 		ret = krb5_make_principal(context, &mcred.server,
350 					  princ->realm,
351 					  "krbtgt",
352 					  princ->realm,
353 					  NULL);
354 		krb5_free_principal (context, princ);
355 		if (ret)
356 			return ret;
357 
358 		ret = krb5_cc_retrieve_cred(context, id, 0, &mcred, &cred);
359 		if(ret == 0) {
360 			ret = krb524_convert_creds_kdc_ccache(context, id,
361 							      &cred, &c);
362 			if(ret == 0) {
363 				snprintf(krb4tkfile, sizeof(krb4tkfile),
364 					 "%s%d",TKT_ROOT, pw->pw_uid);
365 				krb_set_tkt_string(krb4tkfile);
366 				tf_setup(&c, c.pname, c.pinst);
367 				if (chown(krb4tkfile, pw->pw_uid,
368 					  pw->pw_gid) < 0)
369 					syslog(LOG_ERR,
370 					       "chown tkfile (%s): %m",
371 					       krb4tkfile);
372 			}
373 			memset(&c, 0, sizeof(c));
374 			krb5_free_creds_contents(context, &cred);
375 		}
376 		krb5_free_principal(context, mcred.server);
377 	}
378 	return 0;
379 }
380 #endif /* KERBEROS */
381 
382 /*
383  * Attempt to log the user in using Kerberos authentication
384  *
385  * return 0 on success (will be logged in)
386  *	  1 if Kerberos failed (try local password in login)
387  */
388 int
389 k5login(pw, instance, localhost, password)
390 	struct passwd *pw;
391 	char *instance, *localhost, *password;
392 {
393         krb5_error_code kerror;
394 	krb5_creds my_creds;
395 	krb5_timestamp now;
396 	krb5_ccache ccache = NULL;
397 	long lifetime = KRB5_DEFAULT_LIFE;
398 	int options = KRB5_DEFAULT_OPTIONS;
399 	char *realm, *client_name;
400 	char *principal;
401 
402 	krb5_configured = 1;
403 
404 	if (login_krb5_forwardable_tgt)
405 		options |= KDC_OPT_FORWARDABLE;
406 
407 	/*
408 	 * Root logins don't use Kerberos.
409 	 * If we have a realm, try getting a ticket-granting ticket
410 	 * and using it to authenticate.  Otherwise, return
411 	 * failure so that we can try the normal passwd file
412 	 * for a password.  If that's ok, log the user in
413 	 * without issuing any tickets.
414 	 */
415 	if (strcmp(pw->pw_name, "root") == 0 ||
416 	    krb5_get_default_realm(kcontext, &realm)) {
417 		krb5_configured = 0;
418 		return (1);
419 	}
420 
421 	/*
422 	 * get TGT for local realm
423 	 * tickets are stored in a file named TKT_ROOT plus uid
424 	 * except for user.root tickets.
425 	 */
426 
427 	if (strcmp(instance, "root") != 0)
428 		(void)snprintf(tkt_location, sizeof tkt_location,
429 				"FILE:/tmp/krb5cc_%d.%s", pw->pw_uid, tty);
430 	else
431 		(void)snprintf(tkt_location, sizeof tkt_location,
432 				"FILE:/tmp/krb5cc_root_%d.%s", pw->pw_uid, tty);
433 	krb5tkfile_env = tkt_location;
434 	has_ccache = 1;
435 
436 	principal = (char *)malloc(strlen(pw->pw_name)+strlen(instance)+2);
437 	strcpy(principal, pw->pw_name);	/* XXX strcpy is safe */
438 	if (strlen(instance)) {
439 		strcat(principal, "/");		/* XXX strcat is safe */
440 		strcat(principal, instance);	/* XXX strcat is safe */
441 	}
442 
443 	if ((kerror = krb5_cc_resolve(kcontext, tkt_location, &ccache)) != 0) {
444 		syslog(LOG_NOTICE, "warning: %s while getting default ccache",
445 			krb5_get_err_text(kcontext, kerror));
446 		return (1);
447 	}
448 
449 	if ((kerror = krb5_parse_name(kcontext, principal, &me)) != 0) {
450 		syslog(LOG_NOTICE, "warning: %s when parsing name %s",
451 			krb5_get_err_text(kcontext, kerror), principal);
452 		return (1);
453 	}
454 
455 	if ((kerror = krb5_unparse_name(kcontext, me, &client_name)) != 0) {
456 		syslog(LOG_NOTICE, "warning: %s when unparsing name %s",
457 			krb5_get_err_text(kcontext, kerror), principal);
458 		return (1);
459 	}
460 
461 	kerror = krb5_cc_initialize(kcontext, ccache, me);
462 	if (kerror != 0) {
463 		syslog(LOG_NOTICE, "%s when initializing cache %s",
464 			krb5_get_err_text(kcontext, kerror), tkt_location);
465 		return (1);
466 	}
467 
468 	memset((char *)&my_creds, 0, sizeof(my_creds));
469 
470 	my_creds.client = me;
471 
472 	if ((kerror = krb5_build_principal_ext(kcontext,
473 			&server,
474 			krb5_realm_length(*krb5_princ_realm(kcontext, me)),
475 			krb5_realm_data(*krb5_princ_realm(kcontext, me)),
476 			KRB5_TGS_NAME_SIZE,
477 			KRB5_TGS_NAME,
478 			krb5_realm_length(*krb5_princ_realm(kcontext, me)),
479 			krb5_realm_data(*krb5_princ_realm(kcontext, me)),
480 			0)) != 0) {
481 		syslog(LOG_NOTICE, "%s while building server name",
482 			krb5_get_err_text(kcontext, kerror));
483 		return (1);
484 	}
485 
486 	my_creds.server = server;
487 
488 	if ((kerror = krb5_timeofday(kcontext, &now)) != 0) {
489 		syslog(LOG_NOTICE, "%s while getting time of day",
490 			krb5_get_err_text(kcontext, kerror));
491 		return (1);
492 	}
493 
494 	my_creds.times.starttime = 0;	/* start timer when request
495 					   gets to KDC */
496 	my_creds.times.endtime = now + lifetime;
497 	my_creds.times.renew_till = 0;
498 
499 	kerror = krb5_get_in_tkt_with_password(kcontext, options,
500 					       NULL,
501 					       NULL,
502 					       NULL,
503 					       password,
504 					       ccache,
505 					       &my_creds, 0);
506 
507 	if (my_creds.server != NULL)
508 		krb5_free_principal(kcontext, my_creds.server);
509 
510 	if (chown(&tkt_location[5], pw->pw_uid, pw->pw_gid) < 0)
511 		syslog(LOG_ERR, "chown tkfile (%s): %m", &tkt_location[5]);
512 
513 	if (kerror) {
514 		if (kerror == KRB5KRB_AP_ERR_BAD_INTEGRITY)
515 			printf("%s: Kerberos Password incorrect\n", principal);
516 		else
517 			krb5_warn(kcontext, kerror,
518 				  "while getting initial credentials");
519 
520 		return (1);
521 	}
522 
523 	if (k5_verify_creds(kcontext, ccache) < 0)
524 		return (1);
525 
526 #ifdef KERBEROS
527 	if ((kerror = krb5_to4(pw, kcontext, ccache)) != 0)
528 	    krb5_warn(kcontext, kerror, "error converting krb4 creds");
529 #endif
530 
531 	/* Success */
532 	notickets = 0;
533 	return (0);
534 }
535 
536 /*
537  * Remove any credentials
538  */
539 void
540 k5destroy()
541 {
542         krb5_error_code kerror;
543 	krb5_ccache ccache = NULL;
544 
545 	if (krb5tkfile_env == NULL)
546 		return;
547 
548 	kerror = krb5_cc_resolve(kcontext, krb5tkfile_env, &ccache);
549 	if (kerror == 0)
550 		(void)krb5_cc_destroy(kcontext, ccache);
551 }
552 #endif /* KERBEROS5 */
553