key.o dispatch.o kex.o mac.o uuencode.o misc.o \
rijndael.o ssh-dss.o ssh-rsa.o dh.o kexdh.o kexgex.o \
kexdhc.o kexgexc.o scard.o msg.o progressmeter.o \
- entropy.o
+ entropy.o kexgssc.o gss-genr.o
SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \
sshconnect.o sshconnect1.o sshconnect2.o
auth-skey.o auth-bsdauth.o auth2-hostbased.o auth2-kbdint.o \
auth2-none.o auth2-passwd.o auth2-pubkey.o \
monitor_mm.o monitor.o monitor_wrap.o monitor_fdpass.o \
- kexdhs.o kexgexs.o \
- auth-krb5.o auth-krb4.o \
+ kexdhs.o kexgexs.o kexgsss.o \
+ auth-krb5.o auth-krb4.o auth2-gss.o \
+ gss-serv.o gss-serv-krb5.o gss-serv-gsi.o \
loginrec.o auth-pam.o auth2-pam.o auth-sia.o md5crypt.o
MANPAGES = scp.1.out ssh-add.1.out ssh-agent.1.out ssh-keygen.1.out ssh-keyscan.1.out ssh.1.out sshd.8.out sftp-server.8.out sftp.1.out ssh-rand-helper.8.out ssh-keysign.8.out sshd_config.5.out ssh_config.5.out
/* Define if compiler implements __func__ */
#undef HAVE___func__
+/* Define this is you want GSSAPI support in the version 2 protocol */
+#undef GSSAPI
+
/* Define if you want Kerberos 5 support */
#undef KRB5
/* Define if you want AFS support */
#undef AFS
+/* Define if you want GSI/Globus authentication support */
+#undef GSI
+
/* Define if you want S/Key support */
#undef SKEY
}
}
+/* Set a PAM environment string. We need to do this so that the session
+ * modules can handle things like Kerberos/GSI credentials that appear
+ * during the ssh authentication process.
+ */
+
+int do_pam_putenv(char *name, char *value) {
+ char *compound;
+ int ret=1;
+
+#ifdef HAVE_PAM_PUTENV
+ compound=xmalloc(strlen(name)+strlen(value)+2);
+ if (compound) {
+ sprintf(compound,"%s=%s",name,value);
+ ret=pam_putenv(__pamh,compound);
+ xfree(compound);
+ }
+#endif
+ return(ret);
+}
+
/* Print any messages that have been generated during authentication */
/* or account checking to stderr */
void print_pam_messages(void)
int is_pam_password_change_required(void);
void do_pam_chauthtok(void);
void do_pam_set_conv(struct pam_conv *);
+int do_pam_putenv(char *, char *);
void message_cat(char **p, const char *a);
#endif /* USE_PAM */
authmsg,
method,
authctxt->valid ? "" : "illegal user ",
- authctxt->user,
+ (authctxt->user[0]) ? authctxt->user : "<implicit>",
get_remote_ipaddr(),
get_remote_port(),
info);
krb5_principal krb5_user;
char *krb5_ticket_file;
#endif
+ void *methoddata;
};
struct Authmethod {
#include "pathnames.h"
#include "monitor_wrap.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
/* import */
extern ServerOptions options;
extern u_char *session_id2;
extern Authmethod method_passwd;
extern Authmethod method_kbdint;
extern Authmethod method_hostbased;
+extern Authmethod method_external;
+extern Authmethod method_gssapi;
Authmethod *authmethods[] = {
&method_none,
+#ifdef GSSAPI
+ &method_external,
+ &method_gssapi,
+#endif
&method_pubkey,
&method_passwd,
&method_kbdint,
}
/* reset state */
auth2_challenge_stop(authctxt);
+
+#ifdef GSSAPI
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN, NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE, NULL);
+#endif
+
authctxt->postponed = 0;
/* try to authenticate user */
{ "OpenSSH_2.5.0p1*,"
"OpenSSH_2.5.1p1*",
SSH_BUG_BIGENDIANAES|SSH_OLD_DHGEX|
- SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
+ SSH_BUG_NOREKEY|SSH_BUG_EXTEOF|
+ SSH_OLD_GSSAPI},
{ "OpenSSH_2.5.0*,"
"OpenSSH_2.5.1*,"
"OpenSSH_2.5.2*", SSH_OLD_DHGEX|SSH_BUG_NOREKEY|
SSH_BUG_EXTEOF},
{ "OpenSSH_2.5.3*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
+ { "OpenSSH_2.9p*", SSH_BUG_EXTEOF|SSH_OLD_GSSAPI},
{ "OpenSSH_2.*,"
"OpenSSH_3.0*,"
- "OpenSSH_3.1*", SSH_BUG_EXTEOF},
+ "OpenSSH_3.1*", SSH_BUG_EXTEOF|SSH_BUG_GSSAPI_BER},
+ { "OpenSSH_3.2*,"
+ "OpenSSH_3.3*,"
+ "OpenSSH_3.4*,"
+ "OpenSSH_3.5*", SSH_BUG_GSSAPI_BER},
{ "Sun_SSH_1.0*", SSH_BUG_NOREKEY|SSH_BUG_EXTEOF},
{ "OpenSSH*", 0 },
{ "*MindTerm*", 0 },
#define SSH_BUG_K5USER 0x00400000
#define SSH_BUG_PROBE 0x00800000
#define SSH_BUG_FIRSTKEX 0x01000000
+#define SSH_OLD_GSSAPI 0x02000000
+#define SSH_BUG_GSSAPI_BER 0x04000000
void enable_compat13(void);
void enable_compat20(void);
AC_CHECK_LIB(dl, dlopen, , )
AC_CHECK_LIB(pam, pam_set_item, , AC_MSG_ERROR([*** libpam missing]))
AC_CHECK_FUNCS(pam_getenvlist)
+ AC_CHECK_FUNCS(pam_putenv)
disable_shadow=yes
PAM_MSG="yes"
else
LIBPAM="-lpam"
fi
+
AC_SUBST(LIBPAM)
fi
]
AC_CHECK_LIB(crypt, crypt)
fi
+# Start of GSI/Globus 2.0 mods
+# Check whether the user wants GSI (Globus 2.0) support
+# if we are using GSI, we will also use the
+# OPenSSL that is built by Globus. This is called
+# -lcrypto_FLAVOR
+# and it will be in the GSI path.
+# The includes will be in the include/FLAVOR/openssl
+# therfore we will not process the -with-ssl parameter.
+
+gsi_path="no"
+AC_ARG_WITH(gsi,
+ [ --with-gsi=PATH Enable GSI/Globus GSSAPI support],
+ [
+ gsi_path="$withval"
+ ]
+)
+
+gsi_flavor=gcc32dbg
+AC_ARG_WITH(gsi-flavor,
+ [ --with-gsi-flavor=FLAVOR Globus build flavor ],
+ [
+ gsi_flavor="$withval"
+ ]
+)
+
+if test "x$gsi_path" != "xno" ; then
+ # Globus GSSAPI configuration
+ AC_DEFINE(GSSAPI)
+ AC_DEFINE(GSI)
+ AC_DEFINE(HAVE_OPENSSL)
+ LDFLAGS="$LDFLAGS -L${gsi_path}/lib"
+ if test ! -z "$need_dash_r" ; then
+ LDFLAGS="$LDFLAGS -R${gsi_path}/lib"
+ fi
+ if test ! -z "$blibpath" ; then
+ blibpath="$blibpath:${gsi_path}/lib"
+ fi
+ LIBS="$LIBS -lcrypto_${gsi_flavor} -lglobus_gss_assist_${gsi_flavor} -lglobus_gssapi_gsi_${gsi_flavor}"
+ CPPFLAGS="$CPPFLAGS -I${gsi_path}/include -I${gsi_path}/include/${gsi_flavor}"
+else
+
# Search for OpenSSL
saved_CPPFLAGS="$CPPFLAGS"
saved_LDFLAGS="$LDFLAGS"
AC_CHECK_LIB(crypt, crypt, LIBS="$LIBS -lcrypt")
fi
+fi
+#end of GSI/Globus 2.0 mods
### Configure cryptographic random number support
fi
AC_CHECK_LIB(resolv, dn_expand, , )
+ AC_CHECK_LIB(gssapi,gss_init_sec_context,
+ [ AC_DEFINE(GSSAPI)
+ K5LIBS="-lgssapi $K5LIBS" ],
+ [ AC_CHECK_LIB(gssapi_krb5,gss_init_sec_context,
+ [ AC_DEFINE(GSSAPI)
+ K5LIBS="-lgssapi_krb5 $K5LIBS" ],
+ AC_MSG_WARN([Cannot find any suitable gss-api library - build may fail]),
+ $K5LIBS)
+ ],
+ $K5LIBS)
+
+ AC_CHECK_HEADER(gssapi.h, ,
+ [ unset ac_cv_header_gssapi_h
+ CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi"
+ AC_CHECK_HEADERS(gssapi.h, ,
+ AC_MSG_WARN([Cannot find any suitable gss-api header - build may fail])
+ )
+ ]
+ )
+
+ oldCPP="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS -I${KRB5ROOT}/include/gssapi"
+ AC_CHECK_HEADER(gssapi_krb5.h, ,
+ [ CPPFLAGS="$oldCPP" ])
+
KRB5=yes
fi
]
#include "dispatch.h"
#include "monitor.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
#define KEX_COOKIE_LEN 16
/* prototype */
k->kex_type = KEX_DH_GRP1_SHA1;
} else if (strcmp(k->name, KEX_DHGEX) == 0) {
k->kex_type = KEX_DH_GEX_SHA1;
+#ifdef GSSAPI
+ } else if (strncmp(k->name, KEX_GSS_SHA1, sizeof(KEX_GSS_SHA1)-1) == 0) {
+ k->kex_type = KEX_GSS_GRP1_SHA1;
+#endif
} else
fatal("bad kex alg %s", k->name);
}
enum kex_exchange {
KEX_DH_GRP1_SHA1,
KEX_DH_GEX_SHA1,
+ KEX_GSS_GRP1_SHA1,
KEX_MAX
};
Mac mac;
Comp comp;
};
+
+struct KexOptions {
+ int gss_deleg_creds;
+};
+
struct Kex {
u_char *session_id;
u_int session_id_len;
Buffer peer;
int done;
int flags;
+ char *host;
char *client_version_string;
char *server_version_string;
+ struct KexOptions options;
int (*verify_host_key)(Key *);
Key *(*load_host_key)(int);
int (*host_key_index)(Key *);
void kexdh_server(Kex *);
void kexgex_client(Kex *);
void kexgex_server(Kex *);
+#ifdef GSSAPI
+void kexgss_client(Kex *);
+void kexgss_server(Kex *);
+#endif
u_char *
kex_dh_hash(char *, char *, char *, int, char *, int, u_char *, int,
return KEY_RSA;
} else if (strcmp(name, "ssh-dss") == 0) {
return KEY_DSA;
+ } else if (strcmp(name, "null") == 0){
+ return KEY_NULL;
}
debug2("key_type_from_name: unknown key type '%s'", name);
return KEY_UNSPEC;
KEY_RSA1,
KEY_RSA,
KEY_DSA,
+ KEY_NULL,
KEY_UNSPEC
};
enum fp_type {
#include "ssh2.h"
#include "mpaux.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+static Gssctxt *gsscontext = NULL;
+#endif
+
/* Imports */
extern ServerOptions options;
extern u_int utmp_len;
int mm_answer_krb5(int, Buffer *);
#endif
+#ifdef GSSAPI
+int mm_answer_gss_setup_ctx(int, Buffer *);
+int mm_answer_gss_accept_ctx(int, Buffer *);
+int mm_answer_gss_userok(int, Buffer *);
+int mm_answer_gss_sign(int, Buffer *);
+int mm_answer_gss_error(int, Buffer *);
+#endif
+
static Authctxt *authctxt;
static BIGNUM *ssh1_challenge = NULL; /* used for ssh1 rsa auth */
#ifdef SKEY
{MONITOR_REQ_SKEYQUERY, MON_ISAUTH, mm_answer_skeyquery},
{MONITOR_REQ_SKEYRESPOND, MON_AUTH, mm_answer_skeyrespond},
+#endif
+#ifdef GSSAPI
+ {MONITOR_REQ_GSSSETUP, MON_ISAUTH, mm_answer_gss_setup_ctx},
+ {MONITOR_REQ_GSSSTEP, MON_ISAUTH, mm_answer_gss_accept_ctx},
+ {MONITOR_REQ_GSSSIGN, MON_ONCE, mm_answer_gss_sign},
+ {MONITOR_REQ_GSSUSEROK, MON_AUTH, mm_answer_gss_userok},
+ {MONITOR_REQ_GSSERR, MON_ISAUTH | MON_ONCE, mm_answer_gss_error},
#endif
{MONITOR_REQ_KEYALLOWED, MON_ISAUTH, mm_answer_keyallowed},
{MONITOR_REQ_KEYVERIFY, MON_AUTH, mm_answer_keyverify},
};
struct mon_table mon_dispatch_postauth20[] = {
+#ifdef GSSAPI
+ {MONITOR_REQ_GSSSETUP, 0, mm_answer_gss_setup_ctx},
+ {MONITOR_REQ_GSSSTEP, 0, mm_answer_gss_accept_ctx},
+ {MONITOR_REQ_GSSSIGN, 0, mm_answer_gss_sign},
+ {MONITOR_REQ_GSSERR, 0, mm_answer_gss_error},
+#endif
{MONITOR_REQ_MODULI, 0, mm_answer_moduli},
{MONITOR_REQ_SIGN, 0, mm_answer_sign},
{MONITOR_REQ_PTY, 0, mm_answer_pty},
/* Permit requests for moduli and signatures */
monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1);
monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1);
+#ifdef GSSAPI
+ /* and for the GSSAPI key exchange */
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSETUP, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSTEP, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSSIGN, 1);
+ monitor_permit(mon_dispatch, MONITOR_REQ_GSSERR, 1);
+#endif
} else {
mon_dispatch = mon_dispatch_proto15;
kex->we_need = buffer_get_int(m);
kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server;
kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+#ifdef GSSAPI
+ kex->kex[KEX_GSS_GRP1_SHA1] =kexgss_server;
+#endif
kex->server = 1;
kex->hostkey_type = buffer_get_int(m);
kex->kex_type = buffer_get_int(m);
mon->m_recvfd = pair[0];
mon->m_sendfd = pair[1];
}
+
+#ifdef GSSAPI
+
+int
+mm_answer_gss_setup_ctx(int socket, Buffer *m) {
+ gss_OID_desc oid;
+ OM_uint32 major;
+
+ oid.elements=buffer_get_string(m,&oid.length);
+
+ major=ssh_gssapi_server_ctx(&gsscontext,&oid);
+
+ xfree(oid.elements);
+
+ buffer_clear(m);
+ buffer_put_int(m,major);
+
+ mm_request_send(socket,MONITOR_ANS_GSSSETUP,m);
+
+ return(0);
+}
+
+int
+mm_answer_gss_accept_ctx(int socket, Buffer *m) {
+ gss_buffer_desc in,out;
+ OM_uint32 major,minor;
+ OM_uint32 flags = 0; /* GSI needs this */
+
+ in.value = buffer_get_string(m,&in.length);
+ major=ssh_gssapi_accept_ctx(gsscontext,&in,&out,&flags);
+ xfree(in.value);
+
+ buffer_clear(m);
+ buffer_put_int(m, major);
+ buffer_put_string(m, out.value, out.length);
+ buffer_put_int(m, flags);
+ mm_request_send(socket,MONITOR_ANS_GSSSTEP,m);
+
+ gss_release_buffer(&minor, &out);
+
+ return(0);
+}
+int
+mm_answer_gss_userok(int socket, Buffer *m) {
+ int authenticated;
+
+ authenticated = authctxt->valid && ssh_gssapi_userok(authctxt->user);
+
+ buffer_clear(m);
+ buffer_put_int(m, authenticated);
+
+ debug3("%s: sending result %d", __func__, authenticated);
+ mm_request_send(socket, MONITOR_ANS_GSSUSEROK, m);
+
+ /* XXX - auth method could also be 'external' */
+ auth_method="gssapi";
+
+ /* Monitor loop will terminate if authenticated */
+ return(authenticated);
+}
+
+int
+mm_answer_gss_sign(int socket, Buffer *m) {
+ gss_buffer_desc data,hash;
+ OM_uint32 major,minor;
+
+ data.value = buffer_get_string(m,&data.length);
+ if (data.length != 20)
+ fatal("%s: data length incorrect: %d", __func__, data.length);
+
+ /* Save the session ID - only first time round */
+ if (session_id2_len == 0) {
+ session_id2_len=data.length;
+ session_id2 = xmalloc(session_id2_len);
+ memcpy(session_id2, data.value, session_id2_len);
+ }
+ major=ssh_gssapi_sign(gsscontext, &data, &hash);
+
+ xfree(data.value);
+
+ buffer_clear(m);
+ buffer_put_int(m, major);
+ buffer_put_string(m, hash.value, hash.length);
+
+ mm_request_send(socket,MONITOR_ANS_GSSSIGN,m);
+
+ gss_release_buffer(&minor,&hash);
+
+ /* Turn on permissions for getpwnam */
+ monitor_permit(mon_dispatch, MONITOR_REQ_PWNAM, 1);
+
+ return(0);
+}
+
+int
+mm_answer_gss_error(int socket, Buffer *m) {
+ OM_uint32 major,minor;
+ char *msg;
+
+ msg=ssh_gssapi_last_error(gsscontext,&major,&minor);
+ buffer_clear(m);
+ buffer_put_int(m,major);
+ buffer_put_int(m,minor);
+ buffer_put_cstring(m,msg);
+
+ mm_request_send(socket,MONITOR_ANS_GSSERR,m);
+
+ xfree(msg);
+
+ return(0);
+}
+
+
+#endif /* GSSAPI */
MONITOR_REQ_BSDAUTHRESPOND, MONITOR_ANS_BSDAUTHRESPOND,
MONITOR_REQ_SKEYQUERY, MONITOR_ANS_SKEYQUERY,
MONITOR_REQ_SKEYRESPOND, MONITOR_ANS_SKEYRESPOND,
+#ifdef GSSAPI
+ MONITOR_REQ_GSSSETUP,MONITOR_ANS_GSSSETUP,
+ MONITOR_REQ_GSSSTEP,MONITOR_ANS_GSSSTEP,
+ MONITOR_REQ_GSSSIGN,MONITOR_ANS_GSSSIGN,
+ MONITOR_REQ_GSSUSEROK,MONITOR_ANS_GSSUSEROK,
+ MONITOR_REQ_GSSERR,MONITOR_ANS_GSSERR,
+#endif
MONITOR_REQ_KEYALLOWED, MONITOR_ANS_KEYALLOWED,
MONITOR_REQ_KEYVERIFY, MONITOR_ANS_KEYVERIFY,
MONITOR_REQ_KEYEXPORT,
#include "channels.h"
#include "session.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
/* Imports */
extern int compat20;
extern Newkeys *newkeys[];
return (success);
}
#endif
+#ifdef GSSAPI
+OM_uint32
+mm_ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid) {
+ Buffer m;
+ OM_uint32 major;
+
+ /* Client doesn't get to see the context */
+ *ctx=NULL;
+
+ buffer_init(&m);
+ buffer_put_string(&m,oid->elements,oid->length);
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSETUP, &m);
+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSETUP, &m);
+
+ major=buffer_get_int(&m);
+
+ buffer_free(&m);
+ return(major);
+}
+
+OM_uint32
+mm_ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_desc *in,
+ gss_buffer_desc *out, OM_uint32 *flags) {
+
+ Buffer m;
+ OM_uint32 major;
+
+ buffer_init(&m);
+ buffer_put_string(&m, in->value, in->length);
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSTEP, &m);
+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSTEP, &m);
+
+ major=buffer_get_int(&m);
+ out->value=buffer_get_string(&m,&out->length);
+ if (flags) *flags=buffer_get_int(&m);
+
+ buffer_free(&m);
+
+ return(major);
+}
+
+int
+mm_ssh_gssapi_userok(char *user) {
+ Buffer m;
+ int authenticated = 0;
+
+ buffer_init(&m);
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSUSEROK, &m);
+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSUSEROK,
+ &m);
+
+ authenticated = buffer_get_int(&m);
+
+ buffer_free(&m);
+ debug3("%s: user %sauthenticated",__func__, authenticated ? "" : "not ");
+ return(authenticated);
+}
+
+OM_uint32
+mm_ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_desc *data, gss_buffer_desc *hash) {
+ Buffer m;
+ OM_uint32 major;
+
+ buffer_init(&m);
+ buffer_put_string(&m, data->value, data->length);
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSSIGN, &m);
+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSSIGN, &m);
+
+ major=buffer_get_int(&m);
+ hash->value = buffer_get_string(&m, &hash->length);
+
+ buffer_free(&m);
+
+ return(major);
+}
+
+char *
+mm_ssh_gssapi_last_error(Gssctxt *ctx, OM_uint32 *major, OM_uint32 *minor) {
+ Buffer m;
+ OM_uint32 maj,min;
+ char *errstr;
+
+ buffer_init(&m);
+
+ mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_GSSERR, &m);
+ mm_request_receive_expect(pmonitor->m_recvfd, MONITOR_ANS_GSSERR, &m);
+
+ maj = buffer_get_int(&m);
+ min = buffer_get_int(&m);
+
+ if (major) *major=maj;
+ if (minor) *minor=min;
+
+ errstr=buffer_get_string(&m,NULL);
+
+ buffer_free(&m);
+
+ return(errstr);
+}
+
+#endif /* GSSAPI */
void mm_start_pam(char *);
#endif
+#ifdef GSSAPI
+#include "ssh-gss.h"
+OM_uint32 mm_ssh_gssapi_server_ctx(Gssctxt **ctxt, gss_OID oid);
+OM_uint32 mm_ssh_gssapi_accept_ctx(Gssctxt *ctxt, gss_buffer_desc *recv,
+ gss_buffer_desc *send, OM_uint32 *flags);
+OM_uint32 mm_ssh_gssapi_sign(Gssctxt *ctxt, gss_buffer_desc *buffer,
+ gss_buffer_desc *hash);
+int mm_ssh_gssapi_userok(char *user);
+char *mm_ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *maj, OM_uint32 *min);
+#endif
+
void mm_terminate(void);
int mm_pty_allocate(int *, int *, char *, int);
void mm_session_pty_cleanup2(void *);
#if defined(KRB4) || defined(KRB5)
oKerberosAuthentication,
#endif
+#ifdef GSSAPI
+ oGssAuthentication, oGssDelegateCreds,
+#ifdef GSI
+ oGssGlobusDelegateLimitedCreds,
+#endif /* GSI */
+#endif /* GSSAPI */
#if defined(AFS) || defined(KRB5)
oKerberosTgtPassing,
#endif
#if defined(KRB4) || defined(KRB5)
{ "kerberosauthentication", oKerberosAuthentication },
#endif
+#ifdef GSSAPI
+ { "gssapiauthentication", oGssAuthentication },
+ { "gssapidelegatecredentials", oGssDelegateCreds },
+#ifdef GSI
+ /* For backwards compatability with old 1.2.27 client code */
+ { "forwardgssapiglobusproxy", oGssDelegateCreds }, /* alias */
+ { "forwardgssapiglobuslimitedproxy", oGssGlobusDelegateLimitedCreds },
+#endif /* GSI */
+#endif /* GSSAPI */
#if defined(AFS) || defined(KRB5)
{ "kerberostgtpassing", oKerberosTgtPassing },
#endif
intptr = &options->kerberos_authentication;
goto parse_flag;
#endif
+#ifdef GSSAPI
+ case oGssAuthentication:
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+
+ case oGssDelegateCreds:
+ intptr = &options->gss_deleg_creds;
+ goto parse_flag;
+
+#ifdef GSI
+ case oGssGlobusDelegateLimitedCreds:
+ intptr = &options->gss_globus_deleg_limited_proxy;
+ goto parse_flag;
+#endif /* GSI */
+
+#endif /* GSSAPI */
+
#if defined(AFS) || defined(KRB5)
case oKerberosTgtPassing:
intptr = &options->kerberos_tgt_passing;
options->rsa_authentication = -1;
options->pubkey_authentication = -1;
options->challenge_response_authentication = -1;
+#ifdef GSSAPI
+ options->gss_authentication = -1;
+ options->gss_deleg_creds = -1;
+#ifdef GSI
+ options->gss_globus_deleg_limited_proxy = -1;
+#endif /* GSI */
+#endif /* GSSAPI */
+
#if defined(KRB4) || defined(KRB5)
options->kerberos_authentication = -1;
#endif
options->pubkey_authentication = 1;
if (options->challenge_response_authentication == -1)
options->challenge_response_authentication = 1;
+#ifdef GSSAPI
+ if (options->gss_authentication == -1)
+ options->gss_authentication = 1;
+ if (options->gss_deleg_creds == -1)
+ options->gss_deleg_creds = 1;
+#ifdef GSI
+ if (options->gss_globus_deleg_limited_proxy == -1)
+ options->gss_globus_deleg_limited_proxy = 0;
+#endif /* GSI */
+#endif /* GSSAPI */
#if defined(KRB4) || defined(KRB5)
if (options->kerberos_authentication == -1)
options->kerberos_authentication = 1;
#if defined(AFS) || defined(KRB5)
int kerberos_tgt_passing; /* Try Kerberos TGT passing. */
#endif
+
+#ifdef GSSAPI
+ int gss_authentication;
+ int gss_deleg_creds;
+#ifdef GSI
+ int gss_globus_deleg_limited_proxy;
+#endif /* GSI */
+#endif /* GSSAPI */
+
#ifdef AFS
int afs_token_passing; /* Try AFS token passing. */
#endif
options->hostbased_uses_name_from_packet_only = -1;
options->rsa_authentication = -1;
options->pubkey_authentication = -1;
+#ifdef GSSAPI
+ options->gss_authentication=-1;
+ options->gss_keyex=-1;
+ options->gss_use_session_ccache = -1;
+ options->gss_cleanup_creds = -1;
+#endif
#if defined(KRB4) || defined(KRB5)
options->kerberos_authentication = -1;
options->kerberos_or_local_passwd = -1;
options->rsa_authentication = 1;
if (options->pubkey_authentication == -1)
options->pubkey_authentication = 1;
+#ifdef GSSAPI
+ if (options->gss_authentication == -1)
+ options->gss_authentication = 1;
+ if (options->gss_keyex == -1)
+ options->gss_keyex =1;
+ if (options->gss_use_session_ccache == -1)
+ options->gss_use_session_ccache = 1;
+ if (options->gss_cleanup_creds == -1)
+ options->gss_cleanup_creds = 1;
+#endif
#if defined(KRB4) || defined(KRB5)
if (options->kerberos_authentication == -1)
options->kerberos_authentication = 0;
sPort, sHostKeyFile, sServerKeyBits, sLoginGraceTime, sKeyRegenerationTime,
sPermitRootLogin, sLogFacility, sLogLevel,
sRhostsAuthentication, sRhostsRSAAuthentication, sRSAAuthentication,
+#ifdef GSSAPI
+ sGssAuthentication, sGssKeyEx, sGssUseSessionCredCache, sGssCleanupCreds,
+#endif
#if defined(KRB4) || defined(KRB5)
sKerberosAuthentication, sKerberosOrLocalPasswd, sKerberosTicketCleanup,
#endif
{ "rsaauthentication", sRSAAuthentication },
{ "pubkeyauthentication", sPubkeyAuthentication },
{ "dsaauthentication", sPubkeyAuthentication }, /* alias */
+#ifdef GSSAPI
+ { "gssapiauthentication", sGssAuthentication },
+ { "gssapikeyexchange", sGssKeyEx },
+ { "gssusesessionccache", sGssUseSessionCredCache },
+ { "gssapiusesessioncredcache", sGssUseSessionCredCache },
+ { "gssapicleanupcreds", sGssCleanupCreds },
+#endif
#if defined(KRB4) || defined(KRB5)
{ "kerberosauthentication", sKerberosAuthentication },
{ "kerberosorlocalpasswd", sKerberosOrLocalPasswd },
case sPubkeyAuthentication:
intptr = &options->pubkey_authentication;
goto parse_flag;
+#ifdef GSSAPI
+ case sGssAuthentication:
+ intptr = &options->gss_authentication;
+ goto parse_flag;
+ case sGssKeyEx:
+ intptr = &options->gss_keyex;
+ goto parse_flag;
+ case sGssUseSessionCredCache:
+ intptr = &options->gss_use_session_ccache;
+ goto parse_flag;
+ case sGssCleanupCreds:
+ intptr = &options->gss_cleanup_creds;
+ goto parse_flag;
+#endif
#if defined(KRB4) || defined(KRB5)
case sKerberosAuthentication:
intptr = &options->kerberos_authentication;
int hostbased_uses_name_from_packet_only; /* experimental */
int rsa_authentication; /* If true, permit RSA authentication. */
int pubkey_authentication; /* If true, permit ssh2 pubkey authentication. */
+ #ifdef GSSAPI
+ int gss_authentication;
+ int gss_keyex;
+ int gss_use_session_ccache; /* If true, delegated credentials are
+ * stored in a session specific cache */
+ int gss_cleanup_creds; /* If true, destroy cred cache on logout */
+#endif
#if defined(KRB4) || defined(KRB5)
int kerberos_authentication; /* If true, permit Kerberos
* authentication. */
#include "session.h"
#include "monitor_wrap.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
#ifdef HAVE_CYGWIN
#include <windows.h>
#include <sys/cygwin.h>
session_proctitle(s);
+#if defined(GSSAPI)
+ temporarily_use_uid(s->pw);
+ ssh_gssapi_storecreds();
+ restore_uid();
+#endif
+
#if defined(USE_PAM)
do_pam_session(s->pw->pw_name, NULL);
do_pam_setcred(1);
ptyfd = s->ptyfd;
ttyfd = s->ttyfd;
+#if defined(GSSAPI)
+ temporarily_use_uid(s->pw);
+ ssh_gssapi_storecreds();
+ restore_uid();
+#endif
+
#if defined(USE_PAM)
do_pam_session(s->pw->pw_name, s->tty);
do_pam_setcred(1);
* Sets the value of the given variable in the environment. If the variable
* already exists, its value is overriden.
*/
-static void
+void
child_set_env(char ***envp, u_int *envsizep, const char *name,
const char *value)
{
copy_environment(environ, &env, &envsize);
#endif
+#ifdef GSSAPI
+ /* Allow any GSSAPI methods that we've used to alter
+ * the childs environment as they see fit
+ */
+ ssh_gssapi_do_child(&env,&envsize);
+#endif
+
if (!options.use_login) {
/* Set basic environment. */
child_set_env(&env, &envsize, "USER", pw->pw_name);
do_authenticated2(Authctxt *authctxt)
{
server_loop2(authctxt);
+#if defined(GSSAPI)
+ ssh_gssapi_cleanup_creds(NULL);
+#endif
}
Session *session_by_tty(char *);
void session_close(Session *);
void do_setusercontext(struct passwd *);
+
+void child_set_env(char ***envp, u_int *envsizep, const char *name,
+ const char *value);
#endif
#include "msg.h"
#include "pathnames.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
/* import */
extern char *client_version_string;
extern char *server_version_string;
ssh_kex2(char *host, struct sockaddr *hostaddr)
{
Kex *kex;
+#ifdef GSSAPI
+ char *orig, *gss;
+ int len;
+#endif
xxx_host = host;
xxx_hostaddr = hostaddr;
+#ifdef GSSAPI
+ /* Add the GSSAPI mechanisms currently supported on this client to
+ * the key exchange algorithm proposal */
+ orig = myproposal[PROPOSAL_KEX_ALGS];
+ gss = ssh_gssapi_client_mechanisms(host);
+ if (gss) {
+ len = strlen(orig)+strlen(gss)+2;
+ myproposal[PROPOSAL_KEX_ALGS]=xmalloc(len);
+ snprintf(myproposal[PROPOSAL_KEX_ALGS],len,"%s,%s",gss,orig);
+ }
+#endif
+
if (options.ciphers == (char *)-1) {
log("No valid ciphers for protocol version 2 given, using defaults.");
options.ciphers = NULL;
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] =
options.hostkeyalgorithms;
+#ifdef GSSAPI
+ /* If we've got GSSAPI algorithms, then we also support the
+ * 'null' hostkey, as a last resort */
+ if (gss) {
+ orig=myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS];
+ len = strlen(orig)+sizeof(",null");
+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]=xmalloc(len);
+ snprintf(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS],len,"%s,null",orig);
+ }
+#endif
/* start key exchange */
kex = kex_setup(myproposal);
kex->kex[KEX_DH_GRP1_SHA1] = kexdh_client;
kex->kex[KEX_DH_GEX_SHA1] = kexgex_client;
+#ifdef GSSAPI
+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_client;
+#endif
kex->client_version_string=client_version_string;
kex->server_version_string=server_version_string;
kex->verify_host_key=&verify_host_key_callback;
-
+ kex->host=host;
+#ifdef GSSAPI
+ kex->options.gss_deleg_creds=options.gss_deleg_creds;
+#endif
xxx_kex = kex;
dispatch_run(DISPATCH_BLOCK, &kex->done, kex);
Sensitive *sensitive;
/* kbd-interactive */
int info_req_seen;
+ /* generic */
+ void *methoddata;
};
struct Authmethod {
char *name; /* string to compare against server's list */
int userauth_kbdint(Authctxt *);
int userauth_hostbased(Authctxt *);
+#ifdef GSSAPI
+int userauth_external(Authctxt *authctxt);
+int userauth_gssapi(Authctxt *authctxt);
+void input_gssapi_response(int type, u_int32_t plen, void *ctxt);
+void input_gssapi_token(int type, u_int32_t plen, void *ctxt);
+void input_gssapi_hash(int type, u_int32_t plen, void *ctxt);
+void input_gssapi_error(int, u_int32_t, void *);
+#endif
+
void userauth(Authctxt *, char *);
static int sign_and_send_pubkey(Authctxt *, Key *, sign_cb_fn *);
static char *authmethods_get(void);
Authmethod authmethods[] = {
+#ifdef GSSAPI
+ {"external-keyx",
+ userauth_external,
+ &options.gss_authentication,
+ NULL},
+ {"gssapi",
+ userauth_gssapi,
+ &options.gss_authentication,
+ NULL},
+#endif
{"hostbased",
userauth_hostbased,
&options.hostbased_authentication,
authctxt.success = 0;
authctxt.method = authmethod_lookup("none");
authctxt.authlist = NULL;
+ authctxt.methoddata = NULL;
authctxt.sensitive = sensitive;
authctxt.info_req_seen = 0;
if (authctxt.method == NULL)
void
userauth(Authctxt *authctxt, char *authlist)
{
+ if (authctxt->methoddata!=NULL) {
+ xfree(authctxt->methoddata);
+ authctxt->methoddata=NULL;
+ }
+
if (authlist == NULL) {
authlist = authctxt->authlist;
} else {
fatal("input_userauth_success: no authentication context");
if (authctxt->authlist)
xfree(authctxt->authlist);
+ if (authctxt->methoddata)
+ xfree(authctxt->methoddata);
clear_auth_state(authctxt);
authctxt->success = 1; /* break out */
}
}
+#ifdef GSSAPI
+int
+userauth_gssapi(Authctxt *authctxt)
+{
+ Gssctxt *gssctxt = NULL;
+ static gss_OID_set supported = NULL;
+ static int mech=0;
+ OM_uint32 min;
+ int ok=0;
+
+ /* Things work better if we send one mechanism at a time, rather
+ * than them all at once. This means that if we fail at some point
+ * in the middle of a negotiation, we can come back and try something
+ * different. */
+
+ if (datafellows & SSH_OLD_GSSAPI) return 0;
+
+ /* Before we offer a mechanism, check that we can support it. Don't
+ * bother trying to get credentials - as the standard fallback will
+ * deal with that kind of failure.
+ */
+
+ if (supported==NULL) gss_indicate_mechs(&min, &supported);
+
+ while (mech<supported->count && !ok) {
+ if (gssctxt) ssh_gssapi_delete_ctx(&gssctxt);
+ ssh_gssapi_build_ctx(&gssctxt);
+ ssh_gssapi_set_oid(gssctxt,&supported->elements[mech]);
+
+ /* The DER encoding below only works for lengths<128,
+ * so check this here
+ */
+ if (supported->elements[mech].length<128 &&
+ !GSS_ERROR(ssh_gssapi_import_name(gssctxt,
+ authctxt->host))) {
+ ok = 1; /* Mechanism works */
+ } else {
+ mech++;
+ }
+ }
+
+ if (!ok) return 0;
+
+ authctxt->methoddata=(void *)gssctxt;
+
+ packet_start(SSH2_MSG_USERAUTH_REQUEST);
+ packet_put_cstring(authctxt->server_user);
+ packet_put_cstring(authctxt->service);
+ packet_put_cstring(authctxt->method->name);
+
+ packet_put_int(1);
+
+ /* The newest gsskeyex draft stipulates that OIDs should
+ * be DER encoded, so we need to add the object type and
+ * length information back on */
+ if (datafellows & SSH_BUG_GSSAPI_BER) {
+ packet_put_string(supported->elements[mech].elements,
+ supported->elements[mech].length);
+ } else {
+ packet_put_int((supported->elements[mech].length)+2);
+ packet_put_char(0x06);
+ packet_put_char(supported->elements[mech].length);
+ packet_put_raw(supported->elements[mech].elements,
+ supported->elements[mech].length);
+ }
+
+ packet_send();
+ packet_write_wait();
+
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,&input_gssapi_response);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,&input_gssapi_token);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR,&input_gssapi_error);
+
+ mech++; /* Move along to next candidate */
+
+ return 1;
+}
+
+void
+input_gssapi_response(int type, u_int32_t plen, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ Gssctxt *gssctxt;
+ OM_uint32 status,ms;
+ int oidlen;
+ char *oidv;
+ gss_buffer_desc send_tok;
+
+ if (authctxt == NULL)
+ fatal("input_gssapi_response: no authentication context");
+ gssctxt = authctxt->methoddata;
+
+ /* Setup our OID */
+ oidv=packet_get_string(&oidlen);
+
+ if (datafellows & SSH_BUG_GSSAPI_BER) {
+ if (!ssh_gssapi_check_oid(gssctxt,oidv,oidlen)) {
+ fatal("Server returned different OID than expected");
+ }
+ ssh_gssapi_set_oid_data(gssctxt,oidv,oidlen);
+ } else {
+ if(oidv[0]!=0x06 || oidv[1]!=oidlen-2) {
+ debug("Badly encoded mechanism OID received");
+ clear_auth_state(authctxt);
+ userauth(authctxt,NULL);
+ return;
+ }
+ if (!ssh_gssapi_check_oid(gssctxt,oidv+2,oidlen-2)) {
+ fatal("Server returned different OID than expected");
+ }
+ ssh_gssapi_set_oid_data(gssctxt,oidv+2,oidlen-2);
+ }
+
+ packet_check_eom();
+
+ status = ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds,
+ GSS_C_NO_BUFFER, &send_tok,
+ NULL);
+ if (GSS_ERROR(status)) {
+ /* Start again with next method on list */
+ debug("Trying to start again");
+ clear_auth_state(authctxt);
+ userauth(authctxt,NULL);
+ return;
+ }
+
+ /* We must have data to send */
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ packet_put_string(send_tok.value,send_tok.length);
+ packet_send();
+ packet_write_wait();
+ gss_release_buffer(&ms, &send_tok);
+}
+
+void
+input_gssapi_token(int type, u_int32_t plen, void *ctxt)
+{
+ Authctxt *authctxt = ctxt;
+ Gssctxt *gssctxt;
+ gss_buffer_desc send_tok,recv_tok;
+ OM_uint32 status;
+
+ if (authctxt == NULL)
+ fatal("input_gssapi_response: no authentication context");
+ gssctxt = authctxt->methoddata;
+
+ recv_tok.value=packet_get_string(&recv_tok.length);
+
+ status=ssh_gssapi_init_ctx(gssctxt, options.gss_deleg_creds,
+ &recv_tok, &send_tok, NULL);
+
+ packet_check_eom();
+
+ if (GSS_ERROR(status)) {
+ /* Start again with the next method in the list */
+ clear_auth_state(authctxt);
+ userauth(authctxt,NULL);
+ return;
+ }
+
+ if (send_tok.length>0) {
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_TOKEN);
+ packet_put_string(send_tok.value,send_tok.length);
+ packet_send();
+ packet_write_wait();
+ }
+
+ if (status == GSS_S_COMPLETE) {
+ /* If that succeeded, send a exchange complete message */
+ packet_start(SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE);
+ packet_send();
+ packet_write_wait();
+ }
+}
+
+void
+input_gssapi_error(int type, u_int32_t plen, void *ctxt)
+{
+ OM_uint32 maj,min;
+ char *msg;
+ char *lang;
+
+ maj=packet_get_int();
+ min=packet_get_int();
+ msg=packet_get_string(NULL);
+ lang=packet_get_string(NULL);
+
+ packet_check_eom();
+
+ fprintf(stderr, "Server GSSAPI Error:\n%s\n", msg);
+ xfree(msg);
+ xfree(lang);
+}
+
+int
+userauth_external(Authctxt *authctxt)
+{
+ static int attempt =0;
+
+ if (attempt++ >= 1)
+ return 0;
+
+ debug2("userauth_external");
+ packet_start(SSH2_MSG_USERAUTH_REQUEST);
+ packet_put_cstring(authctxt->server_user);
+ packet_put_cstring(authctxt->service);
+ packet_put_cstring(authctxt->method->name);
+ packet_send();
+ packet_write_wait();
+ return 1;
+}
+#endif /* GSSAPI */
+
int
userauth_none(Authctxt *authctxt)
{
packet_put_cstring(authctxt->method->name);
packet_send();
return 1;
+
}
int
{
/* XXX clear authentication state */
dispatch_set(SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ, NULL);
-
+#ifdef GSSAPI
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,NULL);
+ dispatch_set(SSH2_MSG_USERAUTH_GSSAPI_ERROR,NULL);
+#endif
+
if (authctxt->last_key != NULL && authctxt->last_key_hint == -1) {
debug3("clear_auth_state: key_free %p", authctxt->last_key);
key_free(authctxt->last_key);
#include "monitor_wrap.h"
#include "monitor_fdpass.h"
+#ifdef GSSAPI
+#include "ssh-gss.h"
+#endif
+
#ifdef LIBWRAP
#include <tcpd.h>
#include <syslog.h>
log("Disabling protocol version 1. Could not load host key");
options.protocol &= ~SSH_PROTO_1;
}
+#ifndef GSSAPI
+ /* The GSSAPI key exchange can run without a host key */
if ((options.protocol & SSH_PROTO_2) && !sensitive_data.have_ssh2_key) {
log("Disabling protocol version 2. Could not load host key");
options.protocol &= ~SSH_PROTO_2;
}
+#endif
if (!(options.protocol & (SSH_PROTO_1|SSH_PROTO_2))) {
log("sshd: no hostkeys available -- exiting.");
exit(1);
}
myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS] = list_hostkey_types();
+#ifdef GSSAPI
+ {
+ char *orig;
+ char *gss = NULL;
+ char *newstr = NULL;
+ orig = myproposal[PROPOSAL_KEX_ALGS];
+
+ /* If we don't have a host key, then all of the algorithms
+ * currently in myproposal are useless */
+ if (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])==0)
+ orig= NULL;
+
+ if (options.gss_keyex)
+ gss = ssh_gssapi_server_mechanisms();
+ else
+ gss = NULL;
+
+ if (gss && orig) {
+ int len = strlen(orig) + strlen(gss) +2;
+ newstr=xmalloc(len);
+ snprintf(newstr,len,"%s,%s",gss,orig);
+ } else if (gss) {
+ newstr=gss;
+ } else if (orig) {
+ newstr=orig;
+ }
+ /* If we've got GSSAPI mechanisms, then we've also got the 'null'
+ host key algorithm, but we're not allowed to advertise it, unless
+ its the only host key algorithm we're supporting */
+ if (gss && (strlen(myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS])) == 0) {
+ myproposal[PROPOSAL_SERVER_HOST_KEY_ALGS]="null";
+ }
+ if (newstr)
+ myproposal[PROPOSAL_KEX_ALGS]=newstr;
+ else
+ fatal("No supported key exchange algorithms");
+ }
+#endif
+
/* start key exchange */
kex = kex_setup(myproposal);
kex->kex[KEX_DH_GRP1_SHA1] = kexdh_server;
kex->kex[KEX_DH_GEX_SHA1] = kexgex_server;
+#ifdef GSSAPI
+ kex->kex[KEX_GSS_GRP1_SHA1] = kexgss_server;
+#endif
kex->server = 1;
kex->client_version_string=client_version_string;
kex->server_version_string=server_version_string;
or
.Dq rsa
are used for version 2 of the SSH protocol.
+.It Cm GssapiAuthentication
+Specifies whether authentication based on GSSAPI may be used, either using
+the result of a successful key exchange, or using GSSAPI user
+authentication.
+The default is
+.Dq yes .
+Note that this option applies to protocol version 2 only.
+.It Cm GssapiKeyExchange
+Specifies whether key exchange based on GSSAPI may be used. When using
+GSSAPI key exchange the server need not have a host key.
+The default is
+.Dq yes .
+Note that this option applies to protocol version 2 only.
+.It Cm GssapiUseSessionCredCache
+Specifies whether a unique credentials cache name should be generated per
+session for storing delegated credentials.
+The default is
+.Dq yes .
+Note that this option applies to protocol version 2 only.
+
.It Cm IgnoreRhosts
Specifies that
.Pa .rhosts