1 /* 2 * Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 */ 24 /* 25 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 26 * Use is subject to license terms. 27 */ 28 29 #include "includes.h" 30 31 #ifdef GSSAPI 32 33 #pragma ident "%Z%%M% %I% %E% SMI" 34 35 #include "includes.h" 36 #include "ssh.h" 37 #include "ssh2.h" 38 #include "xmalloc.h" 39 #include "buffer.h" 40 #include "bufaux.h" 41 #include "packet.h" 42 #include "compat.h" 43 #include <openssl/evp.h> 44 #include "cipher.h" 45 #include "kex.h" 46 #include "auth.h" 47 #include "log.h" 48 #include "channels.h" 49 #include "session.h" 50 #include "dispatch.h" 51 #include "servconf.h" 52 #include "uidswap.h" 53 #include "compat.h" 54 #include <pwd.h> 55 56 #include "ssh-gss.h" 57 58 extern char **environ; 59 60 extern ServerOptions options; 61 extern uchar_t *session_id2; 62 extern int session_id2_len; 63 64 Gssctxt *xxx_gssctxt; 65 66 void 67 ssh_gssapi_server_kex_hook(Kex *kex, char **proposal) 68 { 69 gss_OID_set mechs = GSS_C_NULL_OID_SET; 70 71 if (kex == NULL || !kex->server) 72 fatal("INTERNAL ERROR (%s)", __func__); 73 74 ssh_gssapi_server_mechs(&mechs); 75 ssh_gssapi_modify_kex(kex, mechs, proposal); 76 } 77 78 void 79 ssh_gssapi_server_mechs(gss_OID_set *mechs) 80 { 81 static gss_OID_set supported = GSS_C_NULL_OID_SET; 82 gss_OID_set s, acquired, indicated = GSS_C_NULL_OID_SET; 83 gss_cred_id_t creds; 84 OM_uint32 maj, min; 85 int i; 86 87 if (!mechs) { 88 (void) gss_release_oid_set(&min, &supported); 89 return; 90 } 91 92 if (supported != GSS_C_NULL_OID_SET) { 93 *mechs = supported; 94 return; 95 } 96 97 *mechs = GSS_C_NULL_OID_SET; 98 99 maj = gss_create_empty_oid_set(&min, &s); 100 if (GSS_ERROR(maj)) { 101 debug("Could not allocate GSS-API resources (%s)", 102 ssh_gssapi_last_error(NULL, &maj, &min)); 103 return; 104 } 105 106 maj = gss_indicate_mechs(&min, &indicated); 107 if (GSS_ERROR(maj)) { 108 debug("No GSS-API mechanisms are installed"); 109 return; 110 } 111 112 maj = gss_acquire_cred(&min, GSS_C_NO_NAME, 0, indicated, 113 GSS_C_ACCEPT, &creds, &acquired, NULL); 114 115 if (GSS_ERROR(maj)) 116 debug("Failed to acquire GSS-API credentials for any " 117 "mechanisms (%s)", ssh_gssapi_last_error(NULL, &maj, &min)); 118 119 (void) gss_release_oid_set(&min, &indicated); 120 (void) gss_release_cred(&min, &creds); 121 122 if (acquired == GSS_C_NULL_OID_SET || acquired->count == 0) 123 return; 124 125 for (i = 0; i < acquired->count; i++) { 126 if (ssh_gssapi_is_spnego(&acquired->elements[i])) 127 continue; 128 129 maj = gss_add_oid_set_member(&min, &acquired->elements[i], &s); 130 if (GSS_ERROR(maj)) { 131 debug("Could not allocate GSS-API resources (%s)", 132 ssh_gssapi_last_error(NULL, &maj, &min)); 133 return; 134 } 135 } 136 (void) gss_release_oid_set(&min, &acquired); 137 138 if (s->count) { 139 supported = s; 140 *mechs = s; 141 } 142 } 143 144 /* 145 * Wrapper around accept_sec_context. Requires that the context contains: 146 * 147 * oid 148 * credentials (from ssh_gssapi_acquire_cred) 149 */ 150 /* Priviledged */ 151 OM_uint32 152 ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_t recv_tok, 153 gss_buffer_t send_tok) 154 { 155 /* 156 * Acquiring a cred for the ctx->desired_mech for GSS_C_NO_NAME 157 * may well be probably better than using GSS_C_NO_CREDENTIAL 158 * and then checking that ctx->desired_mech agrees with 159 * ctx->actual_mech... 160 */ 161 ctx->major = gss_accept_sec_context(&ctx->minor, &ctx->context, 162 GSS_C_NO_CREDENTIAL, recv_tok, GSS_C_NO_CHANNEL_BINDINGS, 163 &ctx->src_name, &ctx->actual_mech, send_tok, &ctx->flags, 164 NULL, &ctx->deleg_creds); 165 166 if (GSS_ERROR(ctx->major)) 167 ssh_gssapi_error(ctx, "accepting security context"); 168 169 if (ctx->major == GSS_S_CONTINUE_NEEDED && send_tok->length == 0) 170 fatal("Zero length GSS context token output when " 171 "continue needed"); 172 else if (GSS_ERROR(ctx->major) && send_tok->length == 0) 173 debug2("Zero length GSS context error token output"); 174 175 if (ctx->major == GSS_S_COMPLETE && 176 ctx->desired_mech != GSS_C_NULL_OID && 177 (ctx->desired_mech->length != ctx->actual_mech->length || 178 memcmp(ctx->desired_mech->elements, ctx->actual_mech->elements, 179 ctx->desired_mech->length) != 0)) { 180 181 gss_OID_set supported; 182 OM_uint32 min; 183 int present = 0; 184 185 debug("The client did not use the GSS-API mechanism it " 186 "asked for"); 187 188 /* Let it slide as long as the mech is supported */ 189 ssh_gssapi_server_mechs(&supported); 190 if (supported != GSS_C_NULL_OID_SET) { 191 (void) gss_test_oid_set_member(&min, ctx->actual_mech, 192 supported, &present); 193 } 194 if (!present) 195 ctx->major = GSS_S_BAD_MECH; 196 } 197 198 if (ctx->deleg_creds) 199 debug("Received delegated GSS credentials"); 200 201 if (ctx->major == GSS_S_COMPLETE) { 202 ctx->major = gss_inquire_context(&ctx->minor, ctx->context, 203 NULL, &ctx->dst_name, NULL, NULL, NULL, NULL, 204 &ctx->established); 205 206 if (GSS_ERROR(ctx->major)) { 207 ssh_gssapi_error(ctx, 208 "inquiring established sec context"); 209 return (ctx->major); 210 } 211 212 xxx_gssctxt = ctx; 213 } 214 215 return (ctx->major); 216 } 217 218 219 /* As user - called through fatal cleanup hook */ 220 void 221 ssh_gssapi_cleanup_creds(Gssctxt *ctx) 222 { 223 #ifdef HAVE_GSS_STORE_CRED 224 /* pam_setcred() will take care of this */ 225 return; 226 #else 227 return; 228 /* #error "Portability broken in cleanup of stored creds" */ 229 #endif /* HAVE_GSS_STORE_CRED */ 230 } 231 232 void 233 ssh_gssapi_storecreds(Gssctxt *ctx, Authctxt *authctxt) 234 { 235 #ifdef USE_PAM 236 char **penv, **tmp_env; 237 #endif /* USE_PAM */ 238 239 if (authctxt == NULL) { 240 error("Missing context while storing GSS-API credentials"); 241 return; 242 } 243 244 if (ctx == NULL && xxx_gssctxt == NULL) 245 return; 246 247 if (ctx == NULL) 248 ctx = xxx_gssctxt; 249 250 if (!options.gss_cleanup_creds || 251 ctx->deleg_creds == GSS_C_NO_CREDENTIAL) { 252 debug3("Not storing delegated GSS credentials" 253 " (none delegated)"); 254 return; 255 } 256 257 if (!authctxt->valid || authctxt->pw == NULL) { 258 debug3("Not storing delegated GSS credentials" 259 " for invalid user"); 260 return; 261 } 262 263 debug("Storing delegated GSS-API credentials"); 264 265 /* 266 * The GSS-API has a flaw in that it does not provide a 267 * mechanism by which delegated credentials can be made 268 * available for acquisition by GSS_Acquire_cred() et. al.; 269 * gss_store_cred() is the proposed GSS-API extension for 270 * generically storing delegated credentials. 271 * 272 * gss_store_cred() does not speak to how credential stores are 273 * referenced. Generically this may be done by switching to the 274 * user context of the user in whose default credential store we 275 * wish to place delegated credentials. But environment 276 * variables could conceivably affect the choice of credential 277 * store as well, and perhaps in a mechanism-specific manner. 278 * 279 * SUNW -- On Solaris the euid selects the current credential 280 * store, but PAM modules could select alternate stores by 281 * setting, for example, KRB5CCNAME, so we also use the PAM 282 * environment temporarily. 283 */ 284 285 #ifdef HAVE_GSS_STORE_CRED 286 #ifdef USE_PAM 287 /* 288 * PAM may have set mechanism-specific variables (e.g., 289 * KRB5CCNAME). fetch_pam_environment() protects against LD_* 290 * and other environment variables. 291 */ 292 penv = fetch_pam_environment(authctxt); 293 tmp_env = environ; 294 environ = penv; 295 #endif /* USE_PAM */ 296 if (authctxt->pw->pw_uid != geteuid()) { 297 temporarily_use_uid(authctxt->pw); 298 ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds, 299 GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds, 300 NULL, NULL); 301 restore_uid(); 302 } else { 303 /* only when logging in as the privileged user used by sshd */ 304 ctx->major = gss_store_cred(&ctx->minor, ctx->deleg_creds, 305 GSS_C_INITIATE, GSS_C_NULL_OID, 0, ctx->default_creds, 306 NULL, NULL); 307 } 308 #ifdef USE_PAM 309 environ = tmp_env; 310 free_pam_environment(penv); 311 #endif /* USE_PAM */ 312 if (GSS_ERROR(ctx->major)) 313 ssh_gssapi_error(ctx, "storing delegated credentials"); 314 315 #else 316 #ifdef KRB5_GSS 317 #error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()" 318 if (ssh_gssapi_is_krb5(ctx->mech)) 319 ssh_gssapi_krb5_storecreds(ctx); 320 #endif /* KRB5_GSS */ 321 #ifdef GSI_GSS 322 #error "GSI krb5-specific code missing in ssh_gssapi_storecreds()" 323 if (ssh_gssapi_is_gsi(ctx->mech)) 324 ssh_gssapi_krb5_storecreds(ctx); 325 #endif /* GSI_GSS */ 326 /* #error "Mechanism-specific code missing in ssh_gssapi_storecreds()" */ 327 return; 328 #endif /* HAVE_GSS_STORE_CRED */ 329 } 330 331 void 332 ssh_gssapi_do_child(Gssctxt *ctx, char ***envp, uint_t *envsizep) 333 { 334 /* 335 * MIT/Heimdal/GSI specific code goes here. 336 * 337 * On Solaris there's nothing to do here as the GSS store and 338 * related environment variables are to be set by PAM, if at all 339 * (no environment variables are needed to address the default 340 * credential store -- the euid does that). 341 */ 342 #ifdef KRB5_GSS 343 #error "MIT/Heimdal krb5-specific code missing in ssh_gssapi_storecreds()" 344 #endif /* KRB5_GSS */ 345 #ifdef GSI_GSS 346 #error "GSI krb5-specific code missing in ssh_gssapi_storecreds()" 347 #endif /* GSI_GSS */ 348 } 349 350 int 351 ssh_gssapi_userok(Gssctxt *ctx, char *user) 352 { 353 if (ctx == NULL) { 354 debug3("INTERNAL ERROR: %s", __func__); 355 return (0); 356 } 357 358 if (user == NULL || *user == '\0') 359 return (0); 360 361 #ifdef HAVE___GSS_USEROK 362 { 363 int user_ok = 0; 364 365 ctx->major = __gss_userok(&ctx->minor, ctx->src_name, user, 366 &user_ok); 367 if (GSS_ERROR(ctx->major)) { 368 debug2("__GSS_userok() failed"); 369 return (0); 370 } 371 372 if (user_ok) 373 return (1); 374 375 /* fall through */ 376 } 377 #else 378 #ifdef GSSAPI_SIMPLE_USEROK 379 { 380 /* Mechanism-generic */ 381 OM_uint32 min; 382 gss_buffer_desc buf, ename1, ename2; 383 gss_name_t iname, cname; 384 int eql; 385 386 buf.value = user; 387 buf.length = strlen(user); 388 ctx->major = gss_import_name(&ctx->minor, &buf, 389 GSS_C_NULL_OID, &iname); 390 if (GSS_ERROR(ctx->major)) { 391 ssh_gssapi_error(ctx, 392 "importing name for authorizing initiator"); 393 goto failed_simple_userok; 394 } 395 396 ctx->major = gss_canonicalize_name(&ctx->minor, iname, 397 ctx->actual_mech, &cname); 398 (void) gss_release_name(&min, &iname); 399 if (GSS_ERROR(ctx->major)) { 400 ssh_gssapi_error(ctx, "canonicalizing name"); 401 goto failed_simple_userok; 402 } 403 404 ctx->major = gss_export_name(&ctx->minor, cname, &ename1); 405 (void) gss_release_name(&min, &cname); 406 if (GSS_ERROR(ctx->major)) { 407 ssh_gssapi_error(ctx, "exporting name"); 408 goto failed_simple_userok; 409 } 410 411 ctx->major = gss_export_name(&ctx->minor, ctx->src_name, 412 &ename2); 413 if (GSS_ERROR(ctx->major)) { 414 ssh_gssapi_error(ctx, 415 "exporting client principal name"); 416 (void) gss_release_buffer(&min, &ename1); 417 goto failed_simple_userok; 418 } 419 420 eql = (ename1.length == ename2.length && 421 memcmp(ename1.value, ename2.value, ename1.length) == 0); 422 423 (void) gss_release_buffer(&min, &ename1); 424 (void) gss_release_buffer(&min, &ename2); 425 426 if (eql) 427 return (1); 428 /* fall through */ 429 } 430 failed_simple_userok: 431 #endif /* GSSAPI_SIMPLE_USEROK */ 432 #ifdef HAVE_GSSCRED_API 433 { 434 /* Mechanism-generic, Solaris-specific */ 435 OM_uint32 maj; 436 uid_t uid; 437 struct passwd *pw; 438 439 maj = gsscred_name_to_unix_cred(ctx->src_name, 440 ctx->actual_mech, &uid, NULL, NULL, NULL); 441 442 if (GSS_ERROR(maj)) 443 goto failed_simple_gsscred_userok; 444 445 if ((pw = getpwnam(user)) == NULL) 446 goto failed_simple_gsscred_userok; 447 448 if (pw->pw_uid == uid) 449 return (1); 450 /* fall through */ 451 } 452 453 failed_simple_gsscred_userok: 454 #endif /* HAVE_GSSCRED_API */ 455 #ifdef KRB5_GSS 456 if (ssh_gssapi_is_krb5(ctx->mech)) 457 if (ssh_gssapi_krb5_userok(ctx->src_name, user)) 458 return (1); 459 #endif /* KRB5_GSS */ 460 #ifdef GSI_GSS 461 if (ssh_gssapi_is_gsi(ctx->mech)) 462 if (ssh_gssapi_gsi_userok(ctx->src_name, user)) 463 return (1); 464 #endif /* GSI_GSS */ 465 #endif /* HAVE___GSS_USEROK */ 466 467 /* default to not authorized */ 468 return (0); 469 } 470 471 char * 472 ssh_gssapi_localname(Gssctxt *ctx) 473 { 474 if (ctx == NULL) { 475 debug3("INTERNAL ERROR: %s", __func__); 476 return (NULL); 477 } 478 479 debug2("Mapping initiator GSS-API principal to local username"); 480 #ifdef HAVE_GSSCRED_API 481 { 482 /* Mechanism-generic, Solaris-specific */ 483 OM_uint32 maj; 484 uid_t uid; 485 struct passwd *pw; 486 487 if (ctx->src_name == GSS_C_NO_NAME) 488 goto failed_gsscred_localname; 489 490 maj = gsscred_name_to_unix_cred(ctx->src_name, 491 ctx->actual_mech, &uid, NULL, NULL, NULL); 492 493 if (GSS_ERROR(maj)) 494 goto failed_gsscred_localname; 495 496 if ((pw = getpwuid(uid)) == NULL) 497 goto failed_gsscred_localname; 498 499 debug2("Mapped the initiator to: %s", pw->pw_name); 500 return (xstrdup(pw->pw_name)); 501 } 502 failed_gsscred_localname: 503 #endif /* HAVE_GSSCRED_API */ 504 #ifdef KRB5_GSS 505 #error "ssh_gssapi_krb5_localname() not implemented" 506 if (ssh_gssapi_is_krb5(ctx->mech)) 507 return (ssh_gssapi_krb5_localname(ctx->src_name)); 508 #endif /* KRB5_GSS */ 509 #ifdef GSI_GSS 510 #error "ssh_gssapi_gsi_localname() not implemented" 511 if (ssh_gssapi_is_gsi(ctx->mech)) 512 return (ssh_gssapi_gsi_localname(ctx->src_name)); 513 #endif /* GSI_GSS */ 514 return (NULL); 515 } 516 #endif /* GSSAPI */