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 */