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