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