k5login.c revision 1.19 1 /* $NetBSD: k5login.c,v 1.19 2001/01/19 21:55:19 pk 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.19 2001/01/19 21:55:19 pk 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 <pwd.h>
66 #include <netdb.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <unistd.h>
71 #include <errno.h>
72
73 #define KRB5_DEFAULT_OPTIONS 0
74 #define KRB5_DEFAULT_LIFE 60*60*10 /* 10 hours */
75
76 krb5_context kcontext;
77
78 int notickets;
79 int krb5_configured;
80 char *krb5tkfile_env;
81 extern char *tty;
82 extern int login_krb5_forwardable_tgt;
83 extern int has_ccache;
84
85 static char tkt_location[MAXPATHLEN];
86 static krb5_creds forw_creds;
87 int have_forward;
88 static krb5_principal me, server;
89
90 int k5_read_creds(char *);
91 int k5_write_creds(void);
92 int k5_verify_creds(krb5_context, krb5_ccache);
93 int k5login(struct passwd *, char *, char *, char *);
94 void k5destroy(void);
95
96 #ifndef krb5_realm_length
97 #define krb5_realm_length(r) ((r).length)
98 #endif
99 #ifndef krb5_realm_data
100 #define krb5_realm_data(r) ((r).data)
101 #endif
102
103 /*
104 * Verify the Kerberos ticket-granting ticket just retrieved for the
105 * user. If the Kerberos server doesn't respond, assume the user is
106 * trying to fake us out (since we DID just get a TGT from what is
107 * supposedly our KDC). If the host/<host> service is unknown (i.e.,
108 * the local keytab doesn't have it), let her in.
109 *
110 * Returns 1 for confirmation, -1 for failure, 0 for uncertainty.
111 */
112 int
113 k5_verify_creds(c, ccache)
114 krb5_context c;
115 krb5_ccache ccache;
116 {
117 char phost[MAXHOSTNAMELEN];
118 int retval, have_keys;
119 krb5_principal princ;
120 krb5_keyblock *kb = 0;
121 krb5_error_code kerror;
122 krb5_data packet;
123 krb5_auth_context auth_context = NULL;
124 krb5_ticket *ticket = NULL;
125
126 kerror = krb5_sname_to_principal(c, 0, 0, KRB5_NT_SRV_HST, &princ);
127 if (kerror) {
128 krb5_warn(kcontext, kerror, "constructing local service name");
129 return (-1);
130 }
131
132 /* Do we have host/<host> keys? */
133 /* (use default keytab, kvno IGNORE_VNO to get the first match,
134 * and default enctype.) */
135 kerror = krb5_kt_read_service_key(c, NULL, princ, 0, 0, &kb);
136 if (kb)
137 krb5_free_keyblock(c, kb);
138 /* any failure means we don't have keys at all. */
139 have_keys = kerror ? 0 : 1;
140
141 /* XXX there should be a krb5 function like mk_req, but taking a full
142 * principal, instead of a service/hostname. (Did I miss one?) */
143 gethostname(phost, sizeof(phost));
144 phost[sizeof(phost) - 1] = '\0';
145
146 /* talk to the kdc and construct the ticket */
147 kerror = krb5_mk_req(c, &auth_context, 0, "host", phost,
148 0, ccache, &packet);
149 /* wipe the auth context for rd_req */
150 if (auth_context) {
151 krb5_auth_con_free(c, auth_context);
152 auth_context = NULL;
153 }
154 if (kerror == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
155 /* we have a service key, so something should be
156 * in the database, therefore this error packet could
157 * have come from an attacker. */
158 if (have_keys) {
159 retval = -1;
160 goto EGRESS;
161 }
162 /* but if it is unknown and we've got no key, we don't
163 * have any security anyhow, so it is ok. */
164 else {
165 retval = 0;
166 goto EGRESS;
167 }
168 }
169 else if (kerror) {
170 krb5_warn(kcontext, kerror,
171 "Unable to verify Kerberos V5 TGT: %s", phost);
172 syslog(LOG_NOTICE, "Kerberos V5 TGT bad: %s",
173 krb5_get_err_text(kcontext, kerror));
174 retval = -1;
175 goto EGRESS;
176 }
177 /* got ticket, try to use it */
178 kerror = krb5_rd_req(c, &auth_context, &packet,
179 princ, NULL, NULL, &ticket);
180 if (kerror) {
181 if (!have_keys) {
182 /* The krb5 errors aren't specified well, but I think
183 * these values cover the cases we expect. */
184 switch (kerror) {
185 case ENOENT: /* no keytab */
186 case KRB5_KT_NOTFOUND:
187 retval = 0;
188 break;
189 default:
190 /* unexpected error: fail */
191 retval = -1;
192 break;
193 }
194 }
195 else {
196 /* we have keys, so if we got any error, we could be
197 * under attack. */
198 retval = -1;
199 }
200 krb5_warn(kcontext, kerror, "Unable to verify host ticket");
201 syslog(LOG_NOTICE, "can't verify v5 ticket: %s; %s\n",
202 krb5_get_err_text(kcontext, kerror),
203 retval
204 ? "keytab found, assuming failure"
205 : "no keytab found, assuming success");
206 goto EGRESS;
207 }
208 /*
209 * The host/<host> ticket has been received _and_ verified.
210 */
211 retval = 1;
212
213 /* do cleanup and return */
214 EGRESS:
215 if (auth_context)
216 krb5_auth_con_free(c, auth_context);
217 krb5_free_principal(c, princ);
218 /* possibly ticket and packet need freeing here as well */
219 return (retval);
220 }
221
222 /*
223 * Attempt to read forwarded kerberos creds
224 *
225 * return 0 on success (forwarded creds in memory)
226 * 1 if no forwarded creds.
227 */
228 int
229 k5_read_creds(username)
230 char *username;
231 {
232 krb5_error_code kerror;
233 krb5_creds mcreds;
234 krb5_ccache ccache;
235
236 have_forward = 0;
237 memset((char*) &mcreds, 0, sizeof(forw_creds));
238 memset((char*) &forw_creds, 0, sizeof(forw_creds));
239
240 kerror = krb5_cc_default(kcontext, &ccache);
241 if (kerror) {
242 krb5_warn(kcontext, kerror, "while getting default ccache");
243 return(1);
244 }
245
246 kerror = krb5_parse_name(kcontext, username, &me);
247 if (kerror) {
248 krb5_warn(kcontext, kerror, "when parsing name %s", username);
249 return(1);
250 }
251
252 mcreds.client = me;
253 kerror = krb5_build_principal_ext(kcontext, &mcreds.server,
254 krb5_realm_length(*krb5_princ_realm(kcontext, me)),
255 krb5_realm_data(*krb5_princ_realm(kcontext, me)),
256 KRB5_TGS_NAME_SIZE,
257 KRB5_TGS_NAME,
258 krb5_realm_length(*krb5_princ_realm(kcontext, me)),
259 krb5_realm_data(*krb5_princ_realm(kcontext, me)),
260 0);
261 if (kerror) {
262 krb5_warn(kcontext, kerror, "while building server name");
263 goto nuke_ccache;
264 }
265
266 kerror = krb5_cc_retrieve_cred(kcontext, ccache, 0,
267 &mcreds, &forw_creds);
268 if (kerror) {
269 krb5_warn(kcontext, kerror,
270 "while retrieving V5 initial ticket for copy");
271 goto nuke_ccache;
272 }
273
274 have_forward = 1;
275
276 strlcpy(tkt_location, getenv("KRB5CCNAME"), sizeof(tkt_location));
277 krb5tkfile_env = tkt_location;
278 has_ccache = 1;
279 notickets = 0;
280
281 nuke_ccache:
282 krb5_cc_destroy(kcontext, ccache);
283 return(!have_forward);
284 }
285
286 int
287 k5_write_creds()
288 {
289 krb5_error_code kerror;
290 krb5_ccache ccache;
291
292 if (!have_forward)
293 return (1);
294
295 kerror = krb5_cc_default(kcontext, &ccache);
296 if (kerror) {
297 krb5_warn(kcontext, kerror, "while getting default ccache");
298 return (1);
299 }
300
301 kerror = krb5_cc_initialize(kcontext, ccache, me);
302 if (kerror) {
303 krb5_warn(kcontext, kerror,
304 "while re-initializing V5 ccache as user");
305 goto nuke_ccache_contents;
306 }
307
308 kerror = krb5_cc_store_cred(kcontext, ccache, &forw_creds);
309 if (kerror) {
310 krb5_warn(kcontext, kerror,
311 "while re-storing V5 ccache as user");
312 goto nuke_ccache_contents;
313 }
314
315 nuke_ccache_contents:
316 krb5_free_cred_contents(kcontext, &forw_creds);
317 return (kerror != 0);
318 }
319
320 /*
321 * Attempt to log the user in using Kerberos authentication
322 *
323 * return 0 on success (will be logged in)
324 * 1 if Kerberos failed (try local password in login)
325 */
326 int
327 k5login(pw, instance, localhost, password)
328 struct passwd *pw;
329 char *instance, *localhost, *password;
330 {
331 krb5_error_code kerror;
332 krb5_creds my_creds;
333 krb5_timestamp now;
334 krb5_ccache ccache = NULL;
335 long lifetime = KRB5_DEFAULT_LIFE;
336 int options = KRB5_DEFAULT_OPTIONS;
337 char *realm, *client_name;
338 char *principal;
339
340 krb5_configured = 1;
341
342 if (login_krb5_forwardable_tgt)
343 options |= KDC_OPT_FORWARDABLE;
344
345 /*
346 * Root logins don't use Kerberos.
347 * If we have a realm, try getting a ticket-granting ticket
348 * and using it to authenticate. Otherwise, return
349 * failure so that we can try the normal passwd file
350 * for a password. If that's ok, log the user in
351 * without issuing any tickets.
352 */
353 if (strcmp(pw->pw_name, "root") == 0 ||
354 krb5_get_default_realm(kcontext, &realm)) {
355 krb5_configured = 0;
356 return (1);
357 }
358
359 /*
360 * get TGT for local realm
361 * tickets are stored in a file named TKT_ROOT plus uid
362 * except for user.root tickets.
363 */
364
365 if (strcmp(instance, "root") != 0)
366 (void)snprintf(tkt_location, sizeof tkt_location,
367 "FILE:/tmp/krb5cc_%d.%s", pw->pw_uid, tty);
368 else
369 (void)snprintf(tkt_location, sizeof tkt_location,
370 "FILE:/tmp/krb5cc_root_%d.%s", pw->pw_uid, tty);
371 krb5tkfile_env = tkt_location;
372 has_ccache = 1;
373
374 principal = (char *)malloc(strlen(pw->pw_name)+strlen(instance)+2);
375 strcpy(principal, pw->pw_name); /* XXX strcpy is safe */
376 if (strlen(instance)) {
377 strcat(principal, "/"); /* XXX strcat is safe */
378 strcat(principal, instance); /* XXX strcat is safe */
379 }
380
381 if ((kerror = krb5_cc_resolve(kcontext, tkt_location, &ccache)) != 0) {
382 syslog(LOG_NOTICE, "warning: %s while getting default ccache",
383 krb5_get_err_text(kcontext, kerror));
384 return (1);
385 }
386
387 if ((kerror = krb5_parse_name(kcontext, principal, &me)) != 0) {
388 syslog(LOG_NOTICE, "warning: %s when parsing name %s",
389 krb5_get_err_text(kcontext, kerror), principal);
390 return (1);
391 }
392
393 if ((kerror = krb5_unparse_name(kcontext, me, &client_name)) != 0) {
394 syslog(LOG_NOTICE, "warning: %s when unparsing name %s",
395 krb5_get_err_text(kcontext, kerror), principal);
396 return (1);
397 }
398
399 kerror = krb5_cc_initialize(kcontext, ccache, me);
400 if (kerror != 0) {
401 syslog(LOG_NOTICE, "%s when initializing cache %s",
402 krb5_get_err_text(kcontext, kerror), tkt_location);
403 return (1);
404 }
405
406 memset((char *)&my_creds, 0, sizeof(my_creds));
407
408 my_creds.client = me;
409
410 if ((kerror = krb5_build_principal_ext(kcontext,
411 &server,
412 krb5_realm_length(*krb5_princ_realm(kcontext, me)),
413 krb5_realm_data(*krb5_princ_realm(kcontext, me)),
414 KRB5_TGS_NAME_SIZE,
415 KRB5_TGS_NAME,
416 krb5_realm_length(*krb5_princ_realm(kcontext, me)),
417 krb5_realm_data(*krb5_princ_realm(kcontext, me)),
418 0)) != 0) {
419 syslog(LOG_NOTICE, "%s while building server name",
420 krb5_get_err_text(kcontext, kerror));
421 return (1);
422 }
423
424 my_creds.server = server;
425
426 if ((kerror = krb5_timeofday(kcontext, &now)) != 0) {
427 syslog(LOG_NOTICE, "%s while getting time of day",
428 krb5_get_err_text(kcontext, kerror));
429 return (1);
430 }
431
432 my_creds.times.starttime = 0; /* start timer when request
433 gets to KDC */
434 my_creds.times.endtime = now + lifetime;
435 my_creds.times.renew_till = 0;
436
437 kerror = krb5_get_in_tkt_with_password(kcontext, options,
438 NULL,
439 NULL,
440 NULL,
441 password,
442 ccache,
443 &my_creds, 0);
444
445 if (my_creds.server != NULL)
446 krb5_free_principal(kcontext, my_creds.server);
447
448 if (chown(&tkt_location[5], pw->pw_uid, pw->pw_gid) < 0)
449 syslog(LOG_ERR, "chown tkfile (%s): %m", &tkt_location[5]);
450
451 if (kerror) {
452 if (kerror == KRB5KRB_AP_ERR_BAD_INTEGRITY)
453 printf("%s: Kerberos Password incorrect\n", principal);
454 else
455 krb5_warn(kcontext, kerror,
456 "while getting initial credentials");
457
458 return (1);
459 }
460
461 if (k5_verify_creds(kcontext, ccache) < 0)
462 return (1);
463
464
465 /* Success */
466 notickets = 0;
467 return (0);
468 }
469
470 /*
471 * Remove any credentials
472 */
473 void
474 k5destroy()
475 {
476 krb5_error_code kerror;
477 krb5_ccache ccache = NULL;
478
479 if (krb5tkfile_env == NULL)
480 return;
481
482 kerror = krb5_cc_resolve(kcontext, krb5tkfile_env, &ccache);
483 if (kerror == 0)
484 (void)krb5_cc_destroy(kcontext, ccache);
485 }
486 #endif
487