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