2 * Copyright (c) 2001,2002 Simon Wilkinson. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
37 #include <openssl/evp.h>
48 #include "monitor_wrap.h"
52 extern ServerOptions options;
53 extern u_char *session_id2;
54 extern int session_id2_len;
56 typedef struct ssh_gssapi_cred_cache {
61 } ssh_gssapi_cred_cache;
63 static struct ssh_gssapi_cred_cache gssapi_cred_store = {NULL,NULL,NULL};
66 * Environment variables pointing to delegated credentials
68 static char *delegation_env[] = {
69 "X509_USER_PROXY", /* GSSAPI/SSLeay */
70 "KRB5CCNAME", /* Krb5 and possibly SSLeay */
74 static void gssapi_unsetenv(const char *var);
81 #include <gssapi_krb5.h>
82 #define krb5_get_err_text(context,code) error_message(code)
85 static krb5_context krb_context = NULL;
87 /* Initialise the krb5 library, so we can use it for those bits that
90 int ssh_gssapi_krb5_init() {
91 krb5_error_code problem;
93 if (krb_context !=NULL)
96 problem = krb5_init_context(&krb_context);
98 log("Cannot initialize krb5 context");
101 krb5_init_ets(krb_context);
106 /* Check if this user is OK to login. This only works with krb5 - other
107 * GSSAPI mechanisms will need their own.
108 * Returns true if the user is OK to log in, otherwise returns 0
112 ssh_gssapi_krb5_userok(char *name) {
113 krb5_principal princ;
116 if (ssh_gssapi_krb5_init() == 0)
119 if ((retval=krb5_parse_name(krb_context, gssapi_client_name.value,
121 log("krb5_parse_name(): %.100s",
122 krb5_get_err_text(krb_context,retval));
125 if (krb5_kuserok(krb_context, princ, name)) {
127 log("Authorized to %s, krb5 principal %s (krb5_kuserok)",name,
128 (char *)gssapi_client_name.value);
133 krb5_free_principal(krb_context, princ);
138 ssh_gssapi_krb5_localname(char **user)
140 krb5_principal princ;
142 if (ssh_gssapi_krb5_init() == 0)
145 if (krb5_parse_name(krb_context, gssapi_client_name.value, &princ)) {
148 *user = (char *)xmalloc(256);
149 if (krb5_aname_to_localname(krb_context, princ, 256, *user)) {
157 /* Make sure that this is called _after_ we've setuid to the user */
159 /* This writes out any forwarded credentials. Its specific to the Kerberos
162 * We assume that our caller has made sure that the user has selected
163 * delegated credentials, and that the client_creds structure is correctly
168 ssh_gssapi_krb5_storecreds(gss_buffer_t export_buffer) {
170 krb5_error_code problem;
171 krb5_principal princ;
173 static char name[40];
175 OM_uint32 maj_status,min_status;
176 gss_cred_id_t krb5_cred_handle;
179 if (gssapi_client_creds==NULL) {
180 debug("No credentials stored");
181 return GSS_S_NO_CRED;
184 if (ssh_gssapi_krb5_init() == 0)
185 return GSS_S_FAILURE;
187 if (options.gss_use_session_ccache) {
188 snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d_XXXXXX",geteuid());
190 if ((tmpfd = mkstemp(ccname))==-1) {
191 log("mkstemp(): %.100s", strerror(errno));
192 return GSS_S_FAILURE;
194 if (fchmod(tmpfd, S_IRUSR | S_IWUSR) == -1) {
195 log("fchmod(): %.100s", strerror(errno));
197 return GSS_S_FAILURE;
200 snprintf(ccname,sizeof(ccname),"/tmp/krb5cc_%d",geteuid());
201 tmpfd = open(ccname, O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
203 log("open(): %.100s", strerror(errno));
204 return GSS_S_FAILURE;
209 snprintf(name, sizeof(name), "FILE:%s",ccname);
211 if ((problem = krb5_cc_resolve(krb_context, name, &ccache))) {
212 log("krb5_cc_default(): %.100s",
213 krb5_get_err_text(krb_context,problem));
214 return GSS_S_FAILURE;
217 if ((problem = krb5_parse_name(krb_context, gssapi_client_name.value,
219 log("krb5_parse_name(): %.100s",
220 krb5_get_err_text(krb_context,problem));
221 krb5_cc_destroy(krb_context,ccache);
222 return GSS_S_FAILURE;
225 if ((problem = krb5_cc_initialize(krb_context, ccache, princ))) {
226 log("krb5_cc_initialize(): %.100s",
227 krb5_get_err_text(krb_context,problem));
228 krb5_free_principal(krb_context,princ);
229 krb5_cc_destroy(krb_context,ccache);
230 return GSS_S_FAILURE;
233 krb5_free_principal(krb_context,princ);
237 __gss_get_mechanism_cred(gssapi_client_creds,
238 &(supported_mechs[GSS_KERBEROS].oid));
240 krb5_cred_handle = gssapi_client_creds;
243 if ((maj_status = gss_krb5_copy_ccache(&min_status,
246 log("gss_krb5_copy_ccache() failed");
247 ssh_gssapi_error(maj_status,min_status);
248 krb5_cc_destroy(krb_context,ccache);
249 return GSS_S_FAILURE;
252 krb5_cc_close(krb_context,ccache);
254 export_buffer->length = strlen("KRB5CCNAME")+strlen(name)+1;
255 export_buffer->value = xmalloc(export_buffer->length+1);
256 sprintf(export_buffer->value, "%s=%s", "KRB5CCNAME", name);
258 return GSS_S_COMPLETE;
264 #include <globus_gss_assist.h>
267 * Check if this user is OK to login under GSI. User has been authenticated
268 * as identity in global 'client_name.value' and is trying to log in as passed
269 * username in 'name'.
271 * Returns non-zero if user is authorized, 0 otherwise.
274 ssh_gssapi_gsi_userok(char *name)
278 /* This returns 0 on success */
279 authorized = (globus_gss_assist_userok(gssapi_client_name.value,
282 log("GSI user %s is%s authorized as target user %s",
283 (char *) gssapi_client_name.value, (authorized ? "" : " not"), name);
289 * Return the local username associated with the GSI credentials.
292 ssh_gssapi_gsi_localname(char **user)
294 return(globus_gss_assist_gridmap(gssapi_client_name.value, user) == 0);
298 * Handle setting up child environment for GSI.
300 * Make sure that this is called _after_ we've setuid to the user.
303 ssh_gssapi_gsi_storecreds(gss_buffer_t export_buffer)
305 OM_uint32 major_status;
306 OM_uint32 minor_status;
308 if (gssapi_client_creds != NULL)
310 char *creds_env = NULL;
313 * This is the current hack with the GSI gssapi library to
314 * export credentials to disk.
317 debug("Exporting delegated credentials");
319 minor_status = 0xdee0; /* Magic value */
321 gss_inquire_cred(&minor_status,
323 (gss_name_t *) &creds_env,
328 if ((major_status == GSS_S_COMPLETE) &&
329 (minor_status == 0xdee1) &&
335 * String is of the form:
336 * X509_USER_DELEG_PROXY=filename
337 * so we parse out the filename
338 * and then set X509_USER_PROXY
341 value = strchr(creds_env, '=');
347 export_buffer->length=
348 strlen("X509_USER_PROXY")+strlen(value)+1;
349 export_buffer->value =
350 xmalloc(export_buffer->length+1);
351 sprintf(export_buffer->value, "%s=%s",
352 "X509_USER_PROXY", value);
354 return GSS_S_COMPLETE;
358 log("Failed to parse delegated credentials string '%s'",
364 log("Failed to export delegated credentials (error %ld)",
374 ssh_gssapi_cleanup_creds(void *ignored)
376 /* OM_uint32 min_stat; */
378 if (gssapi_cred_store.filename!=NULL) {
379 /* Unlink probably isn't sufficient */
380 debug("removing gssapi cred file\"%s\"",gssapi_cred_store.filename);
381 unlink(gssapi_cred_store.filename);
384 if (gssapi_client_creds != GSS_C_NO_CREDENTIAL)
385 gss_release_cred(&min_stat, &gssapi_client_creds);
390 ssh_gssapi_export_cred(OM_uint32 * minor_status,
391 const gss_cred_id_t cred_handle,
392 const gss_OID desired_mech,
393 OM_uint32 option_req,
394 gss_buffer_t export_buffer)
396 OM_uint32 maj_stat = GSS_S_FAILURE;
398 if (option_req != 1) return GSS_S_UNAVAILABLE;
399 if (desired_mech != NULL) return GSS_S_BAD_MECH;
401 switch (gssapi_client_type) {
404 maj_stat = ssh_gssapi_krb5_storecreds(export_buffer);
409 maj_stat = ssh_gssapi_gsi_storecreds(export_buffer);
413 /* GSSAPI not used in this authentication */
414 debug("No GSSAPI credentials stored");
417 log("ssh_gssapi_do_child: Unknown mechanism");
421 if (GSS_ERROR(maj_stat)) {
422 *minor_status = GSS_S_FAILURE;
428 ssh_gssapi_storecreds()
430 OM_uint32 maj_stat, min_stat;
431 gss_buffer_desc export_cred = GSS_C_EMPTY_BUFFER;
434 if (gssapi_client_creds == GSS_C_NO_CREDENTIAL)
437 #ifdef HAVE_GSSAPI_EXT
438 maj_stat = gss_export_cred(&min_stat, gssapi_client_creds,
439 GSS_C_NO_OID, 1, &export_cred);
440 if (GSS_ERROR(maj_stat) && maj_stat != GSS_S_UNAVAILABLE) {
441 ssh_gssapi_error(maj_stat, min_stat);
446 /* If gss_export_cred() is not available, use old methods */
447 if (export_cred.length == 0) {
448 ssh_gssapi_export_cred(&min_stat, gssapi_client_creds,
449 GSS_C_NO_OID, 1, &export_cred);
450 if (GSS_ERROR(maj_stat)) {
451 ssh_gssapi_error(maj_stat, min_stat);
455 p = strchr((char *) export_cred.value, '=');
457 log("Failed to parse exported credentials string '%.100s'",
458 (char *)export_cred.value);
459 gss_release_buffer(&min_stat, &export_cred);
463 gssapi_cred_store.envvar = strdup((char *)export_cred.value);
464 gssapi_cred_store.envval = strdup(p);
466 do_pam_putenv(gssapi_cred_store.envvar, gssapi_cred_store.envval);
468 if (strncmp(p, "FILE:", 5) == 0) {
471 if (access(p, R_OK)) {
472 gssapi_cred_store.filename = strdup(p);
474 gss_release_buffer(&min_stat, &export_cred);
476 if (options.gss_cleanup_creds) {
477 fatal_add_cleanup(ssh_gssapi_cleanup_creds, NULL);
481 /* This allows GSSAPI methods to do things to the childs environment based
482 * on the passed authentication process and credentials.
484 * Question: If we didn't use userauth_external for some reason, should we
485 * still delegate credentials?
488 ssh_gssapi_do_child(char ***envp, u_int *envsizep)
491 if (gssapi_cred_store.envvar!=NULL &&
492 gssapi_cred_store.envval!=NULL) {
494 debug("Setting %s to %s", gssapi_cred_store.envvar,
495 gssapi_cred_store.envval);
496 child_set_env(envp, envsizep, gssapi_cred_store.envvar,
497 gssapi_cred_store.envval);
500 switch(gssapi_client_type) {
502 case GSS_KERBEROS: break;
508 debug("No GSSAPI credentials stored");
511 log("ssh_gssapi_do_child: Unknown mechanism");
516 ssh_gssapi_userok(char *user)
518 if (gssapi_client_name.length==0 ||
519 gssapi_client_name.value==NULL) {
520 debug("No suitable client data");
523 switch (gssapi_client_type) {
526 return(ssh_gssapi_krb5_userok(user));
527 break; /* Not reached */
531 return(ssh_gssapi_gsi_userok(user));
532 break; /* Not reached */
535 debug("Client not GSSAPI");
538 debug("Unknown client authentication type");
544 ssh_gssapi_localname(char **user)
547 if (gssapi_client_name.length==0 ||
548 gssapi_client_name.value==NULL) {
549 debug("No suitable client data");
552 switch (gssapi_client_type) {
555 return(ssh_gssapi_krb5_localname(user));
556 break; /* Not reached */
560 return(ssh_gssapi_gsi_localname(user));
561 break; /* Not reached */
564 debug("Client not GSSAPI");
567 debug("Unknown client authentication type");
573 * Clean our environment on startup. This means removing any environment
574 * strings that might inadvertantly been in root's environment and
575 * could cause serious security problems if we think we set them.
578 ssh_gssapi_clean_env(void)
584 for (envstr_index = 0;
585 (envstr = delegation_env[envstr_index]) != NULL;
588 if (getenv(envstr)) {
589 debug("Clearing environment variable %s", envstr);
590 gssapi_unsetenv(envstr);
596 * Wrapper around unsetenv.
599 gssapi_unsetenv(const char *var)
604 #else /* !HAVE_UNSETENV */
605 extern char **environ;
606 char **p1 = environ; /* New array list */
607 char **p2 = environ; /* Current array list */
608 int len = strlen(var);
611 * Walk through current environ array (p2) copying each pointer
612 * to new environ array (p1) unless the pointer is to the item
613 * we want to delete. Copy happens in place.
616 if ((strncmp(*p2, var, len) == 0) &&
617 ((*p2)[len] == '=')) {
619 * *p2 points at item to be deleted, just skip over it
624 * *p2 points at item we want to save, so copy it
632 /* And make sure new array is NULL terminated */
634 #endif /* HAVE_UNSETENV */