Index: configure.ac =================================================================== RCS file: /cvsroot/lsh/lsh/configure.ac,v retrieving revision 1.45 diff -u -p -r1.45 configure.ac --- configure.ac 19 Sep 2003 14:58:32 -0000 1.45 +++ configure.ac 19 Sep 2003 21:40:25 -0000 @@ -102,6 +102,10 @@ AC_ARG_ENABLE(srp, AC_HELP_STRING([--disable-srp], [Disable the (experimental) support for SRP]),, [enable_srp=yes]) +AC_ARG_ENABLE(gss, + AC_HELP_STRING([--disable-gss], [Disable the (experimental) support for GSS]),, + [enable_gss=yes]) + AC_ARG_ENABLE(kerberos, AC_HELP_STRING([--disable-kerberos], [Don't support kerberos]),, [enable_kerberos=yes]) @@ -343,7 +347,35 @@ if test x$have_utmpx_h = xyes; then ]) fi - +AH_TEMPLATE([WITH_GSS_K5], [Whether to use gss K5 authorization (Heimdal/MIT)]) +if test x$enable_gss != xno; then + if test x$enable_gss = xk5; then + AC_CHECK_PROG(KRB5CONFIG, krb5-config, krb5-config, no) + if test x$KRB5CONFIG != xno; then + CPPFLAGS="$CPPFLAGS `$KRB5CONFIG --cflags gssapi`" + LIBS="`$KRB5CONFIG --libs gssapi`" + AC_CHECK_HEADERS([gssapi.h gssapi/gssapi.h gssapi/gssapi_generic.h]) + AC_DEFINE(WITH_GSS_K5) + AC_CHECK_DECL(GSS_C_NT_HOSTBASED_SERVICE,, [ + AC_DEFINE(GSS_C_NT_HOSTBASED_SERVICE, + gss_nt_service_name, + [Work around buggy MIT library])], [ +#ifdef HAVE_GSSAPI_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +#include +#endif +]) + fi + else + AC_CHECK_HEADERS(gss.h,, [enable_gss=no]) + AC_CHECK_LIB(gss, gss_check_version,, [enable_gss=no]) + fi +fi if test x$enable_kerberos = xyes; then AC_CHECK_HEADERS(krb5.h,, [enable_kerberos=no]) @@ -457,6 +489,12 @@ AH_TEMPLATE([WITH_ZLIB], [Define if zlib # Should we use zlib? if test x$with_zlib = xyes ; then AC_DEFINE(WITH_ZLIB) +fi + +AH_TEMPLATE([WITH_GSS], [Define if gss should be used]) +# Should we use gss? +if test x$enable_gss != xno ; then + AC_DEFINE(WITH_GSS) fi # The kerberos libraries are needed only to support the Index: src/.dist_classes =================================================================== RCS file: /cvsroot/lsh/lsh/src/.dist_classes,v retrieving revision 1.10 diff -u -p -r1.10 .dist_classes --- src/.dist_classes 5 Jun 2003 14:48:28 -0000 1.10 +++ src/.dist_classes 19 Sep 2003 21:40:25 -0000 @@ -72,6 +72,7 @@ dist_classes = \ rsa.c.x \ server.c.x \ server_authorization.c.x \ + server_gssapi.c.x \ server_keyexchange.c.x \ server_password.c.x \ server_pty.h.x \ Index: src/Makefile.am =================================================================== RCS file: /cvsroot/lsh/lsh/src/Makefile.am,v retrieving revision 1.14 diff -u -p -r1.14 Makefile.am --- src/Makefile.am 6 Jun 2003 18:25:02 -0000 1.14 +++ src/Makefile.am 19 Sep 2003 21:40:25 -0000 @@ -82,7 +82,7 @@ liblsh_a_SOURCES = abstract_io.c abstrac reaper.c resource.c \ rsa.c rsa_keygen.c \ server.c server_authorization.c server_keyexchange.c \ - server_password.c server_publickey.c \ + server_password.c server_publickey.c server_gssapi.c \ server_pty.c server_session.c server_userauth.c \ server_x11.c sexp.c \ spki.c srp_exchange.c ssh1_fallback.c \ Index: src/atoms.in =================================================================== RCS file: /cvsroot/lsh/lsh/src/atoms.in,v retrieving revision 1.30 diff -u -p -r1.30 atoms.in --- src/atoms.in 22 Apr 2003 11:57:11 -0000 1.30 +++ src/atoms.in 19 Sep 2003 21:40:25 -0000 @@ -76,6 +76,7 @@ ssh-connection publickey password hostbased +gssapi # Channel related Index: src/client_userauth.c =================================================================== RCS file: /cvsroot/lsh/lsh/src/client_userauth.c,v retrieving revision 1.54 diff -u -p -r1.54 client_userauth.c --- src/client_userauth.c 19 Sep 2003 14:20:22 -0000 1.54 +++ src/client_userauth.c 19 Sep 2003 21:40:26 -0000 @@ -27,6 +27,23 @@ #include +#if WITH_GSS +#if WITH_GSS_K5 +#include /* for guserok */ +#ifdef HAVE_GSSAPI_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +#include +#endif +#else /* !WITH_GSS_K5 */ +#include +#endif /* WITH_GSS_K5 */ +#endif /* WITH_GSS */ + #include "client_userauth.h" #include "charset.h" @@ -964,3 +981,463 @@ make_client_publickey_auth(struct object return &self->super; } + +/* GSSAPI authentication. */ + +#if WITH_GSS + +static struct lsh_string * +get_status_1 (OM_uint32 code, int type) +{ + OM_uint32 maj_stat, min_stat; + struct lsh_string *str = NULL; + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + + do + { + maj_stat = gss_display_status (&min_stat, code, type, GSS_C_NULL_OID, + &msg_ctx, &msg); + if (!GSS_ERROR(maj_stat)) + { + if (str) + str = ssh_format("%flS\n%ls", str, msg.length, (char *) msg.value); + else + str = ssh_format("%ls", msg.length, (char *) msg.value); + } + gss_release_buffer (&min_stat, &msg); + } + while (msg_ctx); + + return str; +} + +static struct lsh_string * +get_status (OM_uint32 maj_stat, OM_uint32 min_stat) +{ + return ssh_format ("%flS\n%flS", get_status_1 (maj_stat, GSS_C_GSS_CODE), + get_status_1 (min_stat, GSS_C_MECH_CODE)); +} + +/* GABA: + (class + (name client_gssapi_method) + (condition "WITH_GSS") + (super client_userauth_method) + (vars + (hostname . "const char *"))) +*/ + +/* GABA: + (class + (name client_gssapi_state) + (condition "WITH_GSS") + (super client_userauth_failure) + (vars + (userauth object client_userauth) + (connection object ssh_connection) + (e object exception_handler) + (name . gss_name_t) + (ctx . gss_ctx_id_t))) +*/ + +static void +do_gssapi_failure(struct client_userauth_failure *s, int again UNUSED) +{ + CAST(client_gssapi_state, self, s); + static const struct exception gssapi_not_useful = + STATIC_EXCEPTION(EXC_USERAUTH, "gssapi authentication not useful."); + + EXCEPTION_RAISE(self->e, &gssapi_not_useful); +} + +static void +do_gc_gssapi (struct client_gssapi_state *gssapi) +{ + OM_uint32 maj_stat, min_stat; + + if (gssapi->ctx) + { + maj_stat = gss_delete_sec_context (&min_stat, &gssapi->ctx, + GSS_C_NO_BUFFER); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error deleting security context: %fS\n", + get_status(maj_stat, min_stat)); + gssapi->ctx = NULL; + } + + if (gssapi->name) + { + maj_stat = gss_release_name (&min_stat, &gssapi->name); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error deleting security context: %fS\n", + get_status(maj_stat, min_stat)); + gssapi->name = NULL; + } +} + +static struct client_gssapi_state * +make_client_gssapi_state(struct client_userauth *userauth, + struct ssh_connection *connection, + struct exception_handler *e) +{ + NEW(client_gssapi_state, self); + self->super.failure = do_gssapi_failure; + self->userauth = userauth; + self->connection = connection; + self->e = e; + self->name = NULL; + self->ctx = GSS_C_NO_CONTEXT; + + return self; +} + +/* GABA: + (class + (name userauth_gssapi_token_handler) + (condition "WITH_GSS") + (super packet_handler) + (vars + (state object client_gssapi_state))) */ + +static struct lsh_string * +format_userauth_gssapi_token(uint32_t len, char *data) +{ + return ssh_format("%c%s", SSH_MSG_USERAUTH_GSSAPI_TOKEN, len, data); +} + +static void +do_gssapi_token(struct packet_handler *s, + struct ssh_connection *connection, + struct lsh_string *packet UNUSED) +{ + CAST(userauth_gssapi_token_handler, self, s); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc inbuf, bufdesc2; + + /* 1 for the SSH message code and 4 for the uint32_t length */ +#define GSS_TOKEN_OFFSET (1 + 4) + inbuf.value = packet->data + GSS_TOKEN_OFFSET; + inbuf.length = packet->length - GSS_TOKEN_OFFSET; + bufdesc2.value = NULL; + bufdesc2.length = 0; + + maj_stat = gss_init_sec_context (&min_stat, + GSS_C_NO_CREDENTIAL, + &self->state->ctx, + self->state->name, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + &inbuf, + NULL, + &bufdesc2, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + static const struct exception gssapi_init_sec_context = + STATIC_EXCEPTION(EXC_USERAUTH, "GSS authentication failed."); + verbose("GSS authentication failed: %fS\n", + get_status(maj_stat, min_stat)); + EXCEPTION_RAISE(self->state->e, &gssapi_init_sec_context); + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + &connection_unimplemented_handler; + return; + } + + verbose("Sending GSS token.\n"); + + if (bufdesc2.length > 0) + C_WRITE(connection, + format_userauth_gssapi_token(bufdesc2.length, bufdesc2.value)); + + if (maj_stat == GSS_S_COMPLETE) + { + verbose("GSS authentication done.\n"); + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + &connection_unimplemented_handler; + C_WRITE(connection, + ssh_format("%c", SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE)); + } +} + +static struct packet_handler * +make_gssapi_token_handler(struct client_gssapi_state *state) +{ + NEW(userauth_gssapi_token_handler, self); + + self->super.handler = do_gssapi_token; + self->state = state; + + return &self->super; +} + +/* GABA: + (class + (name userauth_gssapi_response_handler) + (condition "WITH_GSS") + (super packet_handler) + (vars + (state object client_gssapi_state))) */ + +static void +do_gssapi_response(struct packet_handler *s, + struct ssh_connection *connection, + struct lsh_string *packet UNUSED) +{ + CAST(userauth_gssapi_response_handler, self, s); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc bufdesc; + + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_RESPONSE] = + &connection_unimplemented_handler; + + bufdesc.value = NULL; + bufdesc.length = 0; + + maj_stat = gss_init_sec_context (&min_stat, + GSS_C_NO_CREDENTIAL, + &self->state->ctx, + self->state->name, + GSS_C_NO_OID, + GSS_C_MUTUAL_FLAG | + GSS_C_REPLAY_FLAG | + GSS_C_SEQUENCE_FLAG, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + NULL, + NULL, + &bufdesc, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + static const struct exception gssapi_init_sec_context = + STATIC_EXCEPTION(EXC_USERAUTH, "GSS authentication failed."); + verbose("GSS authentication failed: %fS\n", + get_status(maj_stat, min_stat)); + EXCEPTION_RAISE(self->state->e, &gssapi_init_sec_context); + return; + } + + verbose("Sending initial GSS token.\n"); + + if (bufdesc.length > 0) + C_WRITE(connection, + format_userauth_gssapi_token(bufdesc.length, bufdesc.value)); + + if (maj_stat == GSS_S_COMPLETE) + { + verbose("GSS authentication done.\n"); + C_WRITE(connection, + ssh_format("%c", SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE)); + } + else + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + make_gssapi_token_handler(self->state); +} + +static struct packet_handler * +make_gssapi_response_handler(struct client_gssapi_state *state) +{ + NEW(userauth_gssapi_response_handler, self); + + self->super.handler = do_gssapi_response; + self->state = state; + + return &self->super; +} + +static struct lsh_string * +format_userauth_gssapi_request(struct lsh_string *name, + uint32_t service, + uint32_t n, + struct lsh_string *oids) +{ + return ssh_format("%c%S%a%a%i%S", SSH_MSG_USERAUTH_REQUEST, + name, service, ATOM_GSSAPI, n, oids); +} + +#if !WITH_GSS_K5 +static struct lsh_string * +format_supported_oids (gss_OID_set mechs) +{ + size_t i; + gss_OID mech; + struct lsh_string *out = NULL; + + for (i = 0, mech = mechs->elements; i < mechs->count; i++, mech++) + { + if (mech->length > 255) + { + verbose("Skipping too long GSS OID %S\n", + ssh_format("%lxs", mech->length, mech->elements)); + continue; + } + + if (out) + out = ssh_format("%fS\x06%c%ls", out, mech->length, mech->length, + mech->elements); + else + out = ssh_format("\x06%c%ls", mech->length, mech->length, + mech->elements); + } + + return out; +} +#endif + +static void +maybe_cleanup (struct ssh_connection *connection) +{ + + if (connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_RESPONSE] != + &connection_fail_handler) + { + verbose("Cleaning up attempted GSS authentication.\n"); + CAST(userauth_gssapi_response_handler, response_handler, + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_RESPONSE]); + do_gc_gssapi(response_handler->state); + KILL(response_handler); + } + + if (connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] != + &connection_unimplemented_handler) + { + verbose("Cleaning up GSS authentication attempt.\n"); + CAST(userauth_gssapi_token_handler, token_handler, + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN]); + do_gc_gssapi(token_handler->state); + KILL(token_handler); + } +} + +static void +send_gssapi(struct client_userauth_method *s, + struct client_userauth *userauth, + struct ssh_connection *connection, + struct exception_handler *e UNUSED, + struct client_gssapi_state *state) +{ + CAST(client_gssapi_method, self, s); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc bufdesc; + struct lsh_string *oids = NULL; + size_t noids; +#if !WITH_GSS_K5 + gss_OID_set mechs; +#endif + + maybe_cleanup (connection); + + bufdesc.value = + (char*) lsh_get_cstring (ssh_format ("host@%lz", self->hostname)); + bufdesc.length = strlen(bufdesc.value); + + maj_stat = gss_import_name (&min_stat, &bufdesc, GSS_C_NT_HOSTBASED_SERVICE, + &state->name); + if (GSS_ERROR(maj_stat)) + { + static const struct exception gssapi_import_name = + STATIC_EXCEPTION(EXC_USERAUTH, + "GSS error importing hostname."); + verbose("GSSAPI error importing name: %fS\n", + get_status(maj_stat, min_stat)); + EXCEPTION_RAISE(state->e, &gssapi_import_name); + return; + } + +#if WITH_GSS_K5 + /* MIT Kerberos 5 and Heimdal do not implement GSSAPI version 2, + so we hard code the Kerberos 5 OID. */ + noids = 1; + oids = ssh_format("\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"); +#else + /* XXX we probably shouldn't do this, but I refuse to implement + english with four negations; draft-ietf-secsh-gsskeyex-06.txt: + The client SHOULD NOT send more then one gssapi mechanism OID + unless there are no non-GSSAPI authentication methods between the + GSSAPI mechanisms in the order of preference, otherwise, + authentication methods may be executed out of order. + */ + maj_stat = gss_inquire_mechs_for_name (&min_stat, state->name, &mechs); + if (GSS_ERROR(maj_stat)) + { + static const struct exception gssapi_inquire_mechs = + STATIC_EXCEPTION(EXC_USERAUTH, + "GSS error inquiring mechanisms."); + verbose("GSSAPI error inquiring mechanisms: %fS\n", + get_status(maj_stat, min_stat)); + EXCEPTION_RAISE(state->e, &gssapi_inquire_mechs); + return; + } + + if (mechs->count > 0) + { + noids = mechs->count; + oids = format_supported_oids(mechs); + } + + maj_stat = gss_release_oid_set (&min_stat, &mechs); + if (GSS_ERROR(maj_stat)) + { + static const struct exception gssapi_release_oids = + STATIC_EXCEPTION(EXC_USERAUTH, + "GSS error releasing OID set."); + verbose("GSSAPI error releasing OID set: %fS\n", + get_status(maj_stat, min_stat)); + EXCEPTION_RAISE(state->e, &gssapi_release_oids); + return; + } + + if (!oids) + { + static const struct exception no_gssapi = + STATIC_EXCEPTION(EXC_USERAUTH, + "GSSAPI do not support hostbased authentication."); + verbose("GSSAPI do not support hostbased authentication\n"); + EXCEPTION_RAISE(state->e, &no_gssapi); + return; + } +#endif + + verbose("Sending list of GSS mechanisms we support.\n"); + + C_WRITE(connection, + format_userauth_gssapi_request(userauth->username, + userauth->service_name, + noids, oids)); + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_RESPONSE] = + make_gssapi_response_handler(state); +} + +static struct client_userauth_failure * +do_gssapi_login(struct client_userauth_method *s, + struct client_userauth *userauth, + struct ssh_connection *connection, + struct exception_handler *e) +{ + struct client_gssapi_state *state = + make_client_gssapi_state(userauth, connection, e); + + send_gssapi(s, userauth, connection, e, state); + + return &state->super; +} + +struct client_userauth_method * +make_client_gssapi_auth(const char *hostname) +{ + NEW(client_gssapi_method, self); + + self->super.type = ATOM_GSSAPI; + self->super.login = do_gssapi_login; + self->hostname = hostname; + + return &self->super; +} +#endif /* WITH_GSS */ Index: src/client_userauth.h =================================================================== RCS file: /cvsroot/lsh/lsh/src/client_userauth.h,v retrieving revision 1.7 diff -u -p -r1.7 client_userauth.h --- src/client_userauth.h 25 Aug 2002 08:14:44 -0000 1.7 +++ src/client_userauth.h 19 Sep 2003 21:40:26 -0000 @@ -51,6 +51,9 @@ struct client_userauth_method * make_client_publickey_auth(struct object_list *); struct client_userauth_method * +make_client_gssapi_auth(const char *); + +struct client_userauth_method * make_client_none_auth(void); #endif /* LSH_CLIENT_USERAUTH_H_INCLUDED */ Index: src/lsh.c =================================================================== RCS file: /cvsroot/lsh/lsh/src/lsh.c,v retrieving revision 1.190 diff -u -p -r1.190 lsh.c --- src/lsh.c 15 Sep 2003 15:17:55 -0000 1.190 +++ src/lsh.c 19 Sep 2003 21:40:26 -0000 @@ -104,6 +104,7 @@ STATIC_REQUEST_SERVICE(ATOM_SSH_USERAUTH (identity . "char *") (with_publickey . int) + (with_gssapi . int) (with_srp_keyexchange . int) (with_dh_keyexchange . int) @@ -147,6 +148,9 @@ make_options(struct exception_handler *h self->start_gateway = 0; self->with_publickey = 1; +#if WITH_GSS + self->with_gssapi = 1; +#endif self->with_srp_keyexchange = 0; @@ -576,11 +580,27 @@ make_lsh_login(struct lsh_options *optio struct client_userauth_method *password = make_client_password_auth(options->super.tty); +#if WITH_GSS + struct client_userauth_method *gssapi = + make_client_gssapi_auth(options->super.target); +#endif + /* FIXME: Perhaps we should try "none" only when using SRP. */ struct client_userauth_method *none = make_client_none_auth(); struct object_list *methods; +#if WITH_GSS + if (LIST_LENGTH(keys)) + methods = make_object_list(4, + none, + make_client_publickey_auth(keys), + gssapi, + password, + -1); + else + methods = make_object_list(3, none, gssapi, password, -1); +#else if (LIST_LENGTH(keys)) methods = make_object_list(3, none, @@ -589,7 +609,8 @@ make_lsh_login(struct lsh_options *optio -1); else methods = make_object_list(2, none, password, -1); - +#endif + return make_client_userauth (ssh_format("%lz", options->super.user), ATOM_SSH_CONNECTION, methods); @@ -645,6 +666,8 @@ const char *argp_program_bug_address = B #define OPT_FORK_STDIO 0x213 +#define OPT_GSSAPI 0x214 + static const struct argp_option main_options[] = { @@ -654,6 +677,12 @@ main_options[] = "Try publickey user authentication (default).", 0 }, { "no-publickey", OPT_PUBLICKEY | ARG_NOT, NULL, 0, "Don't try publickey user authentication.", 0 }, +#if WITH_GSS + { "gssapi", OPT_GSSAPI, NULL, 0, + "Try GSSAPI user authentication (default).", 0 }, + { "no-gssapi", OPT_GSSAPI | ARG_NOT, NULL, 0, + "Don't try GSSAPI user authentication.", 0 }, +#endif { "host-db", OPT_HOST_DB, "Filename", 0, "By default, ~/.lsh/host-acls", 0}, { "sloppy-host-authentication", OPT_SLOPPY, NULL, 0, "Allow untrusted hostkeys.", 0 }, @@ -864,6 +893,10 @@ main_argp_parser(int key, char *arg, str break; CASE_FLAG(OPT_PUBLICKEY, with_publickey); + +#if WITH_GSS + CASE_FLAG(OPT_GSSAPI, with_gssapi); +#endif case OPT_HOST_DB: self->known_hosts = arg; Index: src/lshd.c =================================================================== RCS file: /cvsroot/lsh/lsh/src/lshd.c,v retrieving revision 1.159 diff -u -p -r1.159 lshd.c --- src/lshd.c 15 Sep 2003 15:18:05 -0000 1.159 +++ src/lshd.c 19 Sep 2003 21:40:26 -0000 @@ -123,6 +123,8 @@ const char *argp_program_bug_address = B #define OPT_NO_PUBLICKEY (OPT_PUBLICKEY | OPT_NO) #define OPT_PASSWORD 0x221 #define OPT_NO_PASSWORD (OPT_PASSWORD | OPT_NO) +#define OPT_GSSAPI 0x229 +#define OPT_NO_GSSAPI (OPT_GSSAPI | OPT_NO) #define OPT_ROOT_LOGIN 0x222 #define OPT_NO_ROOT_LOGIN (OPT_ROOT_LOGIN | OPT_NO) @@ -170,6 +172,7 @@ const char *argp_program_bug_address = B (with_loginauthmode . int) (with_publickey . int) (with_password . int) + (with_gssapi . int) (allow_root . int) (pw_helper . "const char *") (login_shell . "const char *") @@ -225,6 +228,7 @@ make_lshd_options(void) self->with_loginauthmode = 0; self->with_publickey = 1; self->with_password = 1; + self->with_gssapi = 1; self->with_tcpip_forward = 1; /* Experimental, so disabled by default. */ self->with_x11_forward = 0; @@ -379,6 +383,13 @@ main_options[] = { "no-publickey", OPT_NO_PUBLICKEY, NULL, 0, "Disable publickey user authentication.", 0}, +#if WITH_GSS + { "gssapi", OPT_GSSAPI, NULL, 0, + "Enable gssapi user authentication (default).", 0}, + { "no-gssapi", OPT_NO_GSSAPI, NULL, 0, + "Disable gssapi user authentication.", 0}, +#endif + { "root-login", OPT_ROOT_LOGIN, NULL, 0, "Allow root to login.", 0 }, { "no-root-login", OPT_NO_ROOT_LOGIN, NULL, 0, @@ -554,7 +565,7 @@ main_argp_parser(int key, char *arg, str if (!self->random) argp_failure( state, EXIT_FAILURE, 0, "No randomness generator available."); - if (self->with_password || self->with_publickey || self->with_srp_keyexchange || self->with_loginauthmode) + if (self->with_password || self->with_publickey || self->with_srp_keyexchange || self->with_gssapi || self->with_loginauthmode) user_db = make_unix_user_db(self->reaper, self->pw_helper, self->login_shell, self->allow_root); @@ -611,8 +622,17 @@ main_argp_parser(int key, char *arg, str { int i = 0; +#if WITH_GSS + self->userauth_methods + = alloc_int_list(self->with_password + + self->with_gssapi + + self->with_publickey); +#else self->userauth_methods - = alloc_int_list(self->with_password + self->with_publickey); + = alloc_int_list(self->with_password + + self->with_publickey); +#endif + self->userauth_algorithms = make_alist(0, -1); if (self->with_password) { @@ -639,6 +659,15 @@ main_argp_parser(int key, char *arg, str -1)) ->super); } +#if WITH_GSS + if (self->with_gssapi) + { + LIST(self->userauth_methods)[i++] = ATOM_GSSAPI; + ALIST_SET(self->userauth_algorithms, + ATOM_GSSAPI, + &make_userauth_gssapi(user_db)->super); + } +#endif } @@ -743,6 +772,14 @@ main_argp_parser(int key, char *arg, str case OPT_NO_PUBLICKEY: self->with_publickey = 0; + break; + + case OPT_GSSAPI: + self->with_gssapi = 1; + break; + + case OPT_NO_GSSAPI: + self->with_gssapi = 0; break; case OPT_LOGIN_AUTH_MODE: Index: src/server_gssapi.c =================================================================== RCS file: src/server_gssapi.c diff -N src/server_gssapi.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/server_gssapi.c 19 Sep 2003 21:40:26 -0000 @@ -0,0 +1,493 @@ +/* server_gssapi.c + * + * GSSAPI authentication method + */ + +/* lsh, an implementation of the ssh protocol + * + * Copyright (C) 2003 Simon Josefsson + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "charset.h" +#include "format.h" +#include "parse.h" +#include "ssh.h" +#include "server_userauth.h" +#include "werror.h" +#include "xalloc.h" + +#if WITH_GSS + +#if WITH_GSS_K5 +#include /* for guserok */ +#ifdef HAVE_GSSAPI_H +#include +#endif +#ifdef HAVE_GSSAPI_GSSAPI_H +#include +#endif +#else /* !WITH_GSS_K5 */ +#include +#endif /* WITH_GSS_K5 */ + +#include "server_gssapi.c.x" + +static struct lsh_string * +get_status_1 (OM_uint32 code, int type) +{ + OM_uint32 maj_stat, min_stat; + struct lsh_string *str = NULL; + gss_buffer_desc msg; + OM_uint32 msg_ctx = 0; + + do + { + maj_stat = gss_display_status (&min_stat, code, type, GSS_C_NULL_OID, + &msg_ctx, &msg); + if (!GSS_ERROR(maj_stat)) + { + if (str) + str = ssh_format("%flS\n%ls", str, msg.length, (char *) msg.value); + else + str = ssh_format("%ls", msg.length, (char *) msg.value); + } + gss_release_buffer (&min_stat, &msg); + } + while (msg_ctx); + + return str; +} + +static struct lsh_string * +get_status (OM_uint32 maj_stat, OM_uint32 min_stat) +{ + return ssh_format ("%flS\n%flS", get_status_1 (maj_stat, GSS_C_GSS_CODE), + get_status_1 (min_stat, GSS_C_MECH_CODE)); +} + +static struct packet_handler * +make_gssapi_token_handler(struct gssapi_server_instance *gssapi); + +/* GABA: + (class + (name userauth_gssapi) + (super userauth) + (vars + (db object user_db))) +*/ + +/* GABA: + (class + (name gssapi_server_instance) + (super userauth_gssapi) + (vars + (user object lsh_string) + (cont object command_continuation) + (db object user_db) + (e object exception_handler) + (cred . gss_cred_id_t) + (ctx . gss_ctx_id_t) + (mech . gss_OID_desc) + (client . gss_name_t))) +*/ + + +static void +do_gc_gssapi (struct gssapi_server_instance *gssapi) +{ + OM_uint32 maj_stat, min_stat; + + if (gssapi->ctx) + { + maj_stat = gss_delete_sec_context (&min_stat, &gssapi->ctx, + GSS_C_NO_BUFFER); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error deleting security context: %fS\n", + get_status(maj_stat, min_stat)); + } + if (gssapi->cred) + { + maj_stat = gss_release_cred (&min_stat, &gssapi->cred); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error deleting security context: %fS\n", + get_status(maj_stat, min_stat)); + } + + if (gssapi->client) + { + maj_stat = gss_release_name(&min_stat, &gssapi->client); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error releasing client name: %fS\n", + get_status(maj_stat, min_stat)); + } +} + +static void +do_authenticate(struct userauth *s, + struct ssh_connection *connection, + struct lsh_string *username, + uint32_t service UNUSED, + struct simple_buffer *args, + struct command_continuation *c, + struct exception_handler *e) +{ + CAST(userauth_gssapi, self, s); + NEW(gssapi_server_instance, gssapi); + int number_of_mechanisms; + OM_uint32 maj_stat, min_stat; + static const struct exception gssapi_acquire_cred + = STATIC_EXCEPTION(EXC_USERAUTH, + "Cannot acquire credential for any mechanism."); + + gssapi->user = username; + gssapi->db = self->db; + gssapi->cont = c; + gssapi->e = e; + gssapi->ctx = GSS_C_NO_CONTEXT; + gssapi->cred = GSS_C_NO_CREDENTIAL; + + if (parse_uint32(args, &number_of_mechanisms)) + { + int i; + + verbose("Client requests %i GSS mechanism(s).\n", number_of_mechanisms); + + for (i = 0; i < number_of_mechanisms; i++) + { + gss_OID_set desired_mechs = GSS_C_NO_OID_SET; + gss_OID_desc tmp; + OM_uint32 junk; + + if (!parse_string(args, &gssapi->mech.length, + (const uint8_t**)&gssapi->mech.elements)) + goto fail; + + if (gssapi->mech.length < 2 || + ((uint8_t*)gssapi->mech.elements)[0] != 0x06) + goto fail; + + { + uint8_t oidlen = (((uint8_t*)gssapi->mech.elements)[1] & 128) ? + ((uint8_t*)gssapi->mech.elements)[1] : 1; + if (gssapi->mech.length <= 1 + oidlen) + goto fail; + /* XXX for oids longer than 255 char, we don't check if + asn.1 der length is correct. */ + if (oidlen == 1 && + ((uint8_t*)gssapi->mech.elements)[1] != + gssapi->mech.length - 2) + goto fail; + tmp.length = gssapi->mech.length - 1 - oidlen; + tmp.elements = (char*)gssapi->mech.elements + 1 + oidlen; + } + + verbose("Acquiring GSS credentials for GSS mechanism %S (%i).\n", + ssh_format("%lxs", tmp.length, (uint8_t*)tmp.elements), i); + + maj_stat = gss_create_empty_oid_set (&min_stat, &desired_mechs); + if (GSS_ERROR(maj_stat)) + { + verbose("GSSAPI error creating OID set: %fS\n", + get_status(maj_stat, min_stat)); + continue; + } + + maj_stat = gss_add_oid_set_member (&min_stat, &tmp, &desired_mechs); + if (GSS_ERROR(maj_stat)) + { + verbose("GSSAPI error adding OID to set: %fS\n", + get_status(maj_stat, min_stat)); + gss_release_oid_set (&junk, &desired_mechs); + continue; + } + + maj_stat = gss_acquire_cred (&min_stat, GSS_C_NO_NAME, 0, + desired_mechs, GSS_C_ACCEPT, + &gssapi->cred, NULL, NULL); + gss_release_oid_set (&junk, &desired_mechs); + if (GSS_ERROR(maj_stat)) + { + verbose("GSSAPI error acquiring credential: %fS\n", + get_status(maj_stat, min_stat)); + continue; + } + + verbose("Ready to continue with mechanism %S (%i).\n", + ssh_format("%lxs", gssapi->mech.length, + (uint8_t*)gssapi->mech.elements), i); + + if (connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] != + &connection_unimplemented_handler) + { + verbose("Cleaning up attempted GSS authentication.\n"); + CAST(gssapi_token_handler, token_handler, + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN]); + do_gc_gssapi(token_handler->gssapi); + KILL(token_handler); + } + + if (connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE] + != &connection_unimplemented_handler) + { + verbose("Cleaning up finished GSS authentication.\n"); + CAST(gssapi_finish_handler, finish_handler, + connection->dispatch + [SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE]); + do_gc_gssapi(finish_handler->gssapi); + KILL(finish_handler); + } + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + make_gssapi_token_handler (gssapi); + EXCEPTION_RAISE(e, make_userauth_special_exception + (ssh_format("%c%s", SSH_MSG_USERAUTH_GSSAPI_RESPONSE, + gssapi->mech.length, + (uint8_t*)gssapi->mech.elements), + NULL)); + return; + } + } + + EXCEPTION_RAISE(e, &gssapi_acquire_cred); + return; + + fail: + PROTOCOL_ERROR(e, "Invalid gssapi USERAUTH message."); +} + +struct userauth * +make_userauth_gssapi(struct user_db *db) +{ + NEW(userauth_gssapi, self); + self->super.authenticate = do_authenticate; + self->db = db; + + return &self->super; +} + +/* GABA: + (class + (name gssapi_finish_handler) + (super packet_handler) + (vars + (gssapi object gssapi_server_instance))) +*/ + +static int +gss_userok (gss_buffer_t client_name, const char *name) +{ +#if WITH_GSS_K5 + int rc = -1; + krb5_principal p; + krb5_context kcontext; + + krb5_init_context (&kcontext); + + if (krb5_parse_name (kcontext, client_name->value, &p) != 0) + return -1; + if (krb5_kuserok (kcontext, p, name)) + rc = 0; + else + rc = 1; + krb5_free_principal (kcontext, p); + return rc; +#else + return (strlen(name) == client_name->length && + memcmp(name, client_name->value, client_name->length) == 0) ? 0 : 1; +#endif +} + +static void +do_handle_gssapi_finish(struct packet_handler *s, + struct ssh_connection *connection, + struct lsh_string *packet UNUSED) +{ + CAST(gssapi_finish_handler, self, s); + OM_uint32 maj_stat, min_stat; + gss_buffer_desc client_name; + + verbose("Finishing GSS.\n"); + + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE] = + &connection_unimplemented_handler; + + /* Serialize handling of userauth requests */ + connection_lock(connection); + + /* Find authenticated user. */ + maj_stat = gss_display_name (&min_stat, self->gssapi->client, + &client_name, NULL); + if (GSS_ERROR(maj_stat)) + { + static const struct exception gssapi_display_name + = STATIC_EXCEPTION(EXC_USERAUTH, + "Cannot extract username from GSS."); + + verbose("GSSAPI error getting client name: %fS\n", + get_status(maj_stat, min_stat)); + + EXCEPTION_RAISE(self->gssapi->e, &gssapi_display_name); + goto done; + } + + /* Check authorization: is GSS user authorized to log on as user? */ + if (gss_userok (&client_name, lsh_get_cstring(self->gssapi->user))) + { + static const struct exception gssapi_not_auth + = STATIC_EXCEPTION(EXC_USERAUTH, + "GSS user not authorized to log on."); + + verbose("GSS user %s not authorized to log on as %s.\n", + client_name.length, (char*)client_name.value, + self->gssapi->user); + + EXCEPTION_RAISE(self->gssapi->e, &gssapi_not_auth); + goto done; + } + + connection->user = USER_LOOKUP(self->gssapi->db, self->gssapi->user, 1); + if (connection->user) + verbose("GSS user %s authorized to log on as %S.\n", client_name.length, + (char*)client_name.value, self->gssapi->user); + else + verbose("GSS user %s requested unknown user %S.\n", client_name.length, + (char*)client_name.value, self->gssapi->user); + + maj_stat = gss_release_buffer(&min_stat, &client_name); + if (GSS_ERROR(maj_stat)) + verbose("GSSAPI error releasing client name: %fS\n", + get_status(maj_stat, min_stat)); + + if (!connection->user) + { + static const struct exception no_such_user + = STATIC_EXCEPTION(EXC_USERAUTH, "No such user"); + EXCEPTION_RAISE(self->gssapi->e, &no_such_user); + goto done; + } + + COMMAND_RETURN(self->gssapi->cont, connection->user); + + done: + do_gc_gssapi (self->gssapi); +} + +static struct packet_handler * +make_gssapi_finish_handler(struct gssapi_server_instance *gssapi) +{ + NEW(gssapi_finish_handler, self); + self->super.handler = do_handle_gssapi_finish; + self->gssapi = gssapi; + + return &self->super; +} + +/* GABA: + (class + (name gssapi_token_handler) + (super packet_handler) + (vars + (gssapi object gssapi_server_instance))) +*/ + +static void +do_handle_gssapi_token(struct packet_handler *s, + struct ssh_connection *connection, + struct lsh_string *packet) +{ + CAST(gssapi_token_handler, self, s); + OM_uint32 maj_stat, min_stat; + OM_uint32 retflags; + gss_buffer_desc inbuf, outbuf; + + verbose("Received GSS token.\n"); + + /* Serialize handling of userauth requests */ + connection_lock(connection); + + /* 1 for the SSH message code and 4 for the uint32_t length */ +#define GSS_TOKEN_OFFSET (1 + 4) + inbuf.value = packet->data + GSS_TOKEN_OFFSET; + inbuf.length = packet->length - GSS_TOKEN_OFFSET; + maj_stat = gss_accept_sec_context (&min_stat, + &self->gssapi->ctx, + self->gssapi->cred, + &inbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &self->gssapi->client, + NULL, + &outbuf, + &retflags, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + static const struct exception gssapi_accept_sec_context + = STATIC_EXCEPTION(EXC_USERAUTH, "Authentication failed."); + + verbose("GSSAPI error accept_sec_context: %fS\n", + get_status(maj_stat, min_stat)); + + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + &connection_unimplemented_handler; + + EXCEPTION_RAISE(self->gssapi->e, make_userauth_special_exception + (ssh_format("%c%li%li%S%S", + SSH_MSG_USERAUTH_GSSAPI_ERROR, + maj_stat, min_stat, + get_status(maj_stat, min_stat), + /* XXX GSS is multilingual, but how + do we get the RFC 1766 tag it uses? */ + ssh_format("en")), + NULL)); + /* XXX ERRTOK */ + /* Serialize handling of userauth requests */ + connection_lock(connection); + EXCEPTION_RAISE(self->gssapi->e, &gssapi_accept_sec_context); + do_gc_gssapi (self->gssapi); + return; + } + + if (maj_stat == GSS_S_COMPLETE) + { + verbose("Preparing to finish GSS authentication.\n"); + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_TOKEN] = + &connection_unimplemented_handler; + connection->dispatch[SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE] = + make_gssapi_finish_handler(self->gssapi); + } + + verbose("Sending GSS token.\n"); + EXCEPTION_RAISE(self->gssapi->e, make_userauth_special_exception + (ssh_format("%c%s", SSH_MSG_USERAUTH_GSSAPI_TOKEN, + outbuf.length, outbuf.value), + NULL)); +} + +static struct packet_handler * +make_gssapi_token_handler(struct gssapi_server_instance *gssapi) +{ + NEW(gssapi_token_handler, self); + self->super.handler = do_handle_gssapi_token; + self->gssapi = gssapi; + + return &self->super; +} +#endif /* WITH_GSS */ Index: src/server_userauth.h =================================================================== RCS file: /cvsroot/lsh/lsh/src/server_userauth.h,v retrieving revision 1.22 diff -u -p -r1.22 server_userauth.h --- src/server_userauth.h 15 Sep 2003 15:18:16 -0000 1.22 +++ src/server_userauth.h 19 Sep 2003 21:40:26 -0000 @@ -104,6 +104,9 @@ make_userauth_publickey(struct user_db * struct alist *verifiers); struct userauth * +make_userauth_gssapi(struct user_db *db); + +struct userauth * make_userauth_none(int ignore_user, struct lsh_user *user); Index: src/ssh.h =================================================================== RCS file: /cvsroot/lsh/lsh/src/ssh.h,v retrieving revision 1.9 diff -u -p -r1.9 ssh.h --- src/ssh.h 13 Mar 2002 16:01:59 -0000 1.9 +++ src/ssh.h 19 Sep 2003 21:40:26 -0000 @@ -55,6 +55,14 @@ #define SSH_MSG_USERAUTH_PK_OK 60 #define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +/* The following message numbers have been defined for use with the + * 'gssapi' user authentication method: */ + +#define SSH_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH_MSG_USERAUTH_GSSAPI_TOKEN 61 +#define SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 +#define SSH_MSG_USERAUTH_GSSAPI_ERROR 64 + /* 80-89 Connection protocol generic */ #define SSH_FIRST_CONNECTION_GENERIC 80