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
ssh_gssapi_server_kex_hook(Kex * kex,char ** proposal)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
ssh_gssapi_server_mechs(gss_OID_set * mechs)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
ssh_gssapi_accept_ctx(Gssctxt * ctx,gss_buffer_t recv_tok,gss_buffer_t send_tok)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
ssh_gssapi_cleanup_creds(Gssctxt * ctx)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
ssh_gssapi_storecreds(Gssctxt * ctx,Authctxt * authctxt)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
ssh_gssapi_do_child(Gssctxt * ctx,char *** envp,uint_t * envsizep)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
ssh_gssapi_userok(Gssctxt * ctx,char * user)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 *
ssh_gssapi_localname(Gssctxt * ctx)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 */
517