? src/auth/mech-gssapi.c ? src/login-common/integrity-proxy-gssapi.c ? src/login-common/integrity-proxy.c ? src/login-common/integrity-proxy.h Index: configure.in =================================================================== RCS file: /home/cvs/dovecot/configure.in,v retrieving revision 1.172 diff -u -r1.172 configure.in --- configure.in 21 Jun 2004 14:51:04 -0000 1.172 +++ configure.in 12 Jul 2004 14:13:55 -0000 @@ -88,6 +88,15 @@ fi, want_pam=yes) +AC_ARG_WITH(gssapi_krb5, +[ --with-gssapi-krb5 Build with GSSAPI Kerberos 5 support (default)], + if test x$withval = xno; then + want_gssapi_krb5=no + else + want_gssapi_krb5=yes + fi, + want_gssapi_krb5=yes) + AC_ARG_WITH(checkpassword, [ --with-checkpassword Build with checkpassword support (default)], if test x$withval = xno; then @@ -994,6 +1003,17 @@ ]) fi +if test $want_gssapi_krb5 = yes; then + AC_CHECK_LIB(gssapi_krb5, gss_acquire_cred, [ + AC_CHECK_HEADER(gssapi/gssapi.h, [ + AC_DEFINE(USE_GSSAPI,, + Define if you want to use GSSAPI) + AUTH_LIBS="$AUTH_LIBS -lgssapi_krb5" + have_gssapi_krb5=yes + ]) + ]) +fi + if test $want_checkpassword = yes; then AC_DEFINE(USERDB_CHECKPASSWORD,, Build with checkpassword userdb support) AC_DEFINE(PASSDB_CHECKPASSWORD,, Build with checkpassword passdb support) @@ -1264,6 +1284,7 @@ echo "Install prefix ...................... : $prefix" echo "File offsets ........................ : ${offt_bits}bit" echo "Building with SSL support ........... : $have_ssl" +echo "Building with GSSAPI (Kerberos 5) ... : $have_gssapi_krb5" echo "Building with IPv6 support .......... : $want_ipv6" echo "Building with pop3 server ........... : $want_pop3d" echo "Building with user database modules . :$userdb" Index: src/auth/Makefile.am =================================================================== RCS file: /home/cvs/dovecot/src/auth/Makefile.am,v retrieving revision 1.28 diff -u -r1.28 Makefile.am --- src/auth/Makefile.am 2 Jul 2004 22:03:37 -0000 1.28 +++ src/auth/Makefile.am 12 Jul 2004 14:13:55 -0000 @@ -31,6 +31,7 @@ mech-plain.c \ mech-cram-md5.c \ mech-digest-md5.c \ + mech-gssapi.c \ mech-apop.c \ mycrypt.c \ passdb.c \ Index: src/auth/auth-client-interface.h =================================================================== RCS file: /home/cvs/dovecot/src/auth/auth-client-interface.h,v retrieving revision 1.7 diff -u -r1.7 auth-client-interface.h --- src/auth/auth-client-interface.h 2 Jul 2004 22:03:37 -0000 1.7 +++ src/auth/auth-client-interface.h 12 Jul 2004 14:13:55 -0000 @@ -81,6 +81,8 @@ /* variable width data, indexes into data[]. Ignore if it points outside data_size. */ uint32_t username_idx; /* NUL-terminated */ + uint32_t integrity_mech_name_idx; /* Mechanism integrity data */ + uint32_t integrity_mech_data_idx; /* Mechanism integrity data */ uint32_t reply_idx; /* last, non-NUL terminated */ uint32_t data_size; Index: src/auth/mech.c =================================================================== RCS file: /home/cvs/dovecot/src/auth/mech.c,v retrieving revision 1.27 diff -u -r1.27 mech.c --- src/auth/mech.c 2 Jul 2004 22:03:37 -0000 1.27 +++ src/auth/mech.c 12 Jul 2004 14:13:55 -0000 @@ -31,6 +31,10 @@ list = i_new(struct mech_module_list, 1); list->module = *module; + if (module->auth_factory_new) + list->factory = module->auth_factory_new (); + else + list->factory = NULL; list->next = mech_modules; mech_modules = list; @@ -44,6 +48,8 @@ if (strcmp((*pos)->module.mech_name, module->mech_name) == 0) { list = *pos; *pos = (*pos)->next; + if (list->factory) + list->module.auth_factory_free (list->factory); i_free(list); break; } @@ -62,15 +68,21 @@ return str; } -static struct mech_module *mech_module_find(const char *name) +static void mech_module_find(const char *name, + struct mech_module **module, + struct auth_request_factory **factory) { struct mech_module_list *list; for (list = mech_modules; list != NULL; list = list->next) { - if (strcasecmp(list->module.mech_name, name) == 0) - return &list->module; + if (strcasecmp(list->module.mech_name, name) == 0) { + *module = &list->module; + *factory = list->factory; + return; + } } - return NULL; + *module = NULL; + *factory = NULL; } void mech_request_new(struct auth_client_connection *conn, @@ -78,7 +90,8 @@ const unsigned char *data, mech_callback_t *callback) { - struct mech_module *mech; + struct mech_module *mech; + struct auth_request_factory *factory; struct auth_request *auth_request; size_t ip_size = 1; @@ -102,7 +115,8 @@ return; } - mech = mech_module_find((const char *)data + request->mech_idx); + mech_module_find((const char *)data + request->mech_idx, + &mech, &factory); if (mech == NULL) { /* unsupported mechanism */ i_error("BUG: Auth client %u requested unsupported " @@ -113,6 +127,9 @@ return; } + if (factory) + auth_request = factory->auth_new (factory); + else #ifdef USE_CYRUS_SASL2 if (set_use_cyrus_sasl) auth_request = mech_cyrus_sasl_new(conn, request, callback); @@ -198,9 +215,11 @@ reply->reply_idx = (size_t)-1; } -void *mech_auth_success(struct auth_client_request_reply *reply, - struct auth_request *auth_request, - const void *data, size_t data_size) +void *mech_auth_success_extended(struct auth_client_request_reply *reply, + struct auth_request *auth_request, + const char *mech_name, + const void *data, size_t data_size, + const void *context, size_t ctx_len) { buffer_t *buf; @@ -209,6 +228,16 @@ reply->username_idx = 0; buffer_append(buf, auth_request->user, strlen(auth_request->user)+1); + if (mech_name && ctx_len > 0) { + reply->integrity_mech_name_idx = buffer_get_used_size(buf); + buffer_append(buf, mech_name, strlen(mech_name)+1); + reply->integrity_mech_data_idx = buffer_get_used_size(buf); + buffer_append(buf, context, ctx_len); + } else { + reply->integrity_mech_name_idx = 0; + reply->integrity_mech_data_idx = 0; + } + if (data_size == 0) reply->reply_idx = (size_t)-1; else { @@ -221,8 +250,21 @@ return buffer_get_modifyable_data(buf, NULL); } -void mech_auth_finish(struct auth_request *auth_request, - const void *data, size_t data_size, int success) +void *mech_auth_success(struct auth_client_request_reply *reply, + struct auth_request *auth_request, + const void *data, size_t data_size) +{ + return mech_auth_success_extended(reply, auth_request, + NULL, + data, data_size, + NULL, 0); +} + +void mech_auth_finish_extended(struct auth_request *auth_request, + const char *mech_name, + const void *data, size_t data_size, + const void *context, size_t ctx_size, + int success) { struct auth_client_request_reply reply; void *reply_data; @@ -239,7 +281,9 @@ reply.id = auth_request->id; reply.result = AUTH_CLIENT_RESULT_SUCCESS; - reply_data = mech_auth_success(&reply, auth_request, data, data_size); + reply_data = mech_auth_success_extended(&reply, auth_request, mech_name, + data, data_size, context, + ctx_size); auth_request->callback(&reply, reply_data, auth_request->conn); if (AUTH_MASTER_IS_DUMMY(auth_request->conn->master)) { @@ -249,6 +293,14 @@ } } +void mech_auth_finish(struct auth_request *auth_request, + const void *data, size_t data_size, int success) +{ + mech_auth_finish_extended(auth_request, NULL, + data, data_size, NULL, 0, + success); +} + int mech_is_valid_username(const char *username) { const unsigned char *p; @@ -387,6 +439,9 @@ extern struct mech_module mech_apop; extern struct mech_module mech_cram_md5; extern struct mech_module mech_digest_md5; +#ifdef USE_GSSAPI +extern struct mech_module mech_gssapi; +#endif extern struct mech_module mech_anonymous; void mech_init(void) @@ -418,6 +473,10 @@ mech_register_module(&mech_cram_md5); else if (strcasecmp(*mechanisms, "DIGEST-MD5") == 0) mech_register_module(&mech_digest_md5); +#ifdef USE_GSSAPI + else if (strcasecmp(*mechanisms, "GSSAPI") == 0) + mech_register_module(&mech_gssapi); +#endif else if (strcasecmp(*mechanisms, "ANONYMOUS") == 0) { if (anonymous_username == NULL) { i_fatal("ANONYMOUS listed in mechanisms, " @@ -477,5 +536,8 @@ mech_unregister_module(&mech_apop); mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); +#ifdef USE_GSSAPI + mech_unregister_module(&mech_gssapi); +#endif mech_unregister_module(&mech_anonymous); } Index: src/auth/mech.h =================================================================== RCS file: /home/cvs/dovecot/src/auth/mech.h,v retrieving revision 1.19 diff -u -r1.19 mech.h --- src/auth/mech.h 31 May 2004 20:10:02 -0000 1.19 +++ src/auth/mech.h 12 Jul 2004 14:13:55 -0000 @@ -15,6 +15,7 @@ pool_t pool; char *user; + char *authenticating_mech; struct auth_client_connection *conn; unsigned int id; @@ -35,6 +36,10 @@ /* ... mechanism specific data ... */ }; +struct auth_request_factory { + struct auth_request *(*auth_new)(struct auth_request_factory *factory); +}; + struct mech_module { const char *mech_name; @@ -44,12 +49,15 @@ unsigned int passdb_need_credentials:1; struct auth_request *(*auth_new)(void); + struct auth_request_factory *(*auth_factory_new)(void); + void (*auth_factory_free)(struct auth_request_factory *auth_factory); }; struct mech_module_list { struct mech_module_list *next; struct mech_module module; + struct auth_request_factory *factory; }; extern struct mech_module_list *mech_modules; @@ -78,8 +86,19 @@ void *mech_auth_success(struct auth_client_request_reply *reply, struct auth_request *auth_request, const void *data, size_t data_size); +void *mech_auth_success_extended(struct auth_client_request_reply *reply, + struct auth_request *auth_request, + const char *mech_name, + const void *data, size_t data_size, + const void *context, size_t ctx_len); void mech_auth_finish(struct auth_request *auth_request, const void *data, size_t data_size, int success); +void mech_auth_finish_extended(struct auth_request *auth_request, + const char *mech_name, + const void *data, size_t data_size, + const void *context, size_t ctx_size, + int success); + int mech_is_valid_username(const char *username); Index: src/imap-login/client-authenticate.c =================================================================== RCS file: /home/cvs/dovecot/src/imap-login/client-authenticate.c,v retrieving revision 1.22 diff -u -r1.22 client-authenticate.c --- src/imap-login/client-authenticate.c 6 Jul 2004 05:55:46 -0000 1.22 +++ src/imap-login/client-authenticate.c 12 Jul 2004 14:13:56 -0000 @@ -48,6 +48,7 @@ if (client->common.auth_request != NULL) { auth_client_request_abort(client->common.auth_request); client->common.auth_request = NULL; + return; } client_send_tagline(client, msg != NULL ? @@ -221,8 +222,8 @@ client_send_auth_data(client, data, reply->data_size); break; default: - /* success, we should be able to log in. if we fail, just - disconnect the client. */ + /* success, we should be able to log in. If we fail, + just disconnect the client. */ client->authenticating = FALSE; client_send_tagline(client, "OK Logged in."); client_unref(client); Index: src/imap-login/client.c =================================================================== RCS file: /home/cvs/dovecot/src/imap-login/client.c,v retrieving revision 1.26 diff -u -r1.26 client.c --- src/imap-login/client.c 31 May 2004 18:04:47 -0000 1.26 +++ src/imap-login/client.c 12 Jul 2004 14:13:56 -0000 @@ -16,7 +16,7 @@ #include "ssl-proxy.h" /* max. size of one parameter in line */ -#define MAX_INBUF_SIZE 512 +#define MAX_INBUF_SIZE 1024 #define MAX_OUTBUF_SIZE 1024 Index: src/lib/mempool.h =================================================================== RCS file: /home/cvs/dovecot/src/lib/mempool.h,v retrieving revision 1.15 diff -u -r1.15 mempool.h --- src/lib/mempool.h 6 Jan 2004 06:09:26 -0000 1.15 +++ src/lib/mempool.h 12 Jul 2004 14:13:56 -0000 @@ -2,6 +2,7 @@ #define __MEMPOOL_H #include "macros.h" +#include /* Memory allocated and reallocated (the new data in it) in pools is always zeroed, it will cost only a few CPU cycles and may well save some debug Index: src/login-common/Makefile.am =================================================================== RCS file: /home/cvs/dovecot/src/login-common/Makefile.am,v retrieving revision 1.5 diff -u -r1.5 Makefile.am --- src/login-common/Makefile.am 22 Aug 2003 02:42:13 -0000 1.5 +++ src/login-common/Makefile.am 12 Jul 2004 14:13:56 -0000 @@ -8,6 +8,8 @@ liblogin_common_a_SOURCES = \ auth-common.c \ + integrity-proxy.c \ + integrity-proxy-gssapi.c \ main.c \ master.c \ ssl-proxy.c \ @@ -18,5 +20,6 @@ auth-common.h \ client-common.h \ common.h \ + integrity-proxy.h \ master.h \ ssl-proxy.h Index: src/login-common/auth-common.c =================================================================== RCS file: /home/cvs/dovecot/src/login-common/auth-common.c,v retrieving revision 1.5 diff -u -r1.5 auth-common.c --- src/login-common/auth-common.c 22 Aug 2003 02:42:13 -0000 1.5 +++ src/login-common/auth-common.c 12 Jul 2004 14:13:56 -0000 @@ -2,6 +2,7 @@ #include "common.h" #include "ioloop.h" +#include "str.h" #include "client-common.h" #include "auth-client.h" #include "auth-common.h" @@ -20,6 +21,56 @@ return t_strndup(data + idx, stop); } +static int setup_integrity_mech(struct client *client, + struct auth_client_request_reply *reply, + const unsigned char *data, + const char **error_r) +{ + const char *mech; + size_t mech_data_len; + + /* Sanity checks on the indices */ + if (!(reply->integrity_mech_name_idx > reply->username_idx + && reply->integrity_mech_data_idx > reply->integrity_mech_name_idx + && reply->reply_idx > reply->integrity_mech_data_idx + && reply->data_size > reply->integrity_mech_data_idx)) + return 0; + + i_info("Setting up integrity protection"); + + mech = t_strndup(data + reply->integrity_mech_name_idx, + reply->integrity_mech_data_idx - reply->integrity_mech_name_idx); + if (!mech) { + *error_r = "Couldn't determine integrity proxy."; + i_warning("%s", *error_r); + return -1; + } + + if (reply->reply_idx == (size_t)-1) + mech_data_len = reply->data_size - reply->integrity_mech_data_idx; + else + mech_data_len = reply->reply_idx - reply->integrity_mech_data_idx; + + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + client->integrity_proxy = integrity_proxy_new(mech, + data + reply->integrity_mech_data_idx, + mech_data_len, + client->fd); + if (!client->integrity_proxy) { + *error_r = "Integrity proxy initialization failed."; + i_warning("%s", *error_r); + return -1; + } + + client->fd = client->integrity_proxy->fd; + + return 0; +} + int auth_callback(struct auth_request *request, struct auth_client_request_reply *reply, const unsigned char *data, struct client *client, @@ -55,6 +106,11 @@ i_free(client->virtual_user); client->virtual_user = i_strdup(user); + if (setup_integrity_mech(client, reply, + data, + error_r) < 0) + return -1; + master_request_login(client, master_callback, auth_client_request_get_server_pid(request), auth_client_request_get_id(request)); @@ -64,6 +120,7 @@ io_remove(client->io); client->io = NULL; } + return 1; case AUTH_CLIENT_RESULT_FAILURE: Index: src/login-common/client-common.h =================================================================== RCS file: /home/cvs/dovecot/src/login-common/client-common.h,v retrieving revision 1.6 diff -u -r1.6 client-common.h --- src/login-common/client-common.h 31 May 2004 18:04:47 -0000 1.6 +++ src/login-common/client-common.h 12 Jul 2004 14:13:56 -0000 @@ -3,12 +3,15 @@ #include "network.h" #include "master.h" +#include "integrity-proxy.h" struct client { struct ip_addr local_ip; struct ip_addr ip; struct ssl_proxy *proxy; + struct integrity_proxy *integrity_proxy; + int fd; struct io *io; Index: src/login-common/main.c =================================================================== RCS file: /home/cvs/dovecot/src/login-common/main.c,v retrieving revision 1.18 diff -u -r1.18 main.c --- src/login-common/main.c 23 Jun 2004 17:48:35 -0000 1.18 +++ src/login-common/main.c 12 Jul 2004 14:13:56 -0000 @@ -209,6 +209,7 @@ if (io_listen != NULL) io_remove(io_listen); if (io_ssl_listen != NULL) io_remove(io_ssl_listen); + integrity_proxy_deinit(); ssl_proxy_deinit(); auth_client_free(auth_client); @@ -253,6 +254,8 @@ master_fd = master_connect(group_name); } + integrity_proxy_init(); + name = strrchr(argv[0], '/'); drop_privileges(); Index: src/master/login-process.c =================================================================== RCS file: /home/cvs/dovecot/src/master/login-process.c,v retrieving revision 1.56 diff -u -r1.56 login-process.c --- src/master/login-process.c 16 Jun 2004 02:04:02 -0000 1.56 +++ src/master/login-process.c 12 Jul 2004 14:13:57 -0000 @@ -421,6 +421,8 @@ set->login_max_logging_users)); } + env_put(t_strconcat("MECHANISMS=", set->login_integrity_mechanisms, NULL)); + env_put(t_strdup_printf("PROCESS_UID=%s", dec2str(pid))); } Index: src/master/master-settings.c =================================================================== RCS file: /home/cvs/dovecot/src/master/master-settings.c,v retrieving revision 1.58 diff -u -r1.58 master-settings.c --- src/master/master-settings.c 10 Jul 2004 17:24:09 -0000 1.58 +++ src/master/master-settings.c 12 Jul 2004 14:13:57 -0000 @@ -227,6 +227,7 @@ MEMBER(login_processes_count) 3, MEMBER(login_max_processes_count) 128, MEMBER(login_max_logging_users) 256, + MEMBER(login_integrity_mechanisms) "gssapi", /* mail */ MEMBER(valid_chroot_dirs) NULL, Index: src/master/master-settings.h =================================================================== RCS file: /home/cvs/dovecot/src/master/master-settings.h,v retrieving revision 1.38 diff -u -r1.38 master-settings.h --- src/master/master-settings.h 10 Jul 2004 17:24:09 -0000 1.38 +++ src/master/master-settings.h 12 Jul 2004 14:13:57 -0000 @@ -45,6 +45,7 @@ unsigned int login_processes_count; unsigned int login_max_processes_count; unsigned int login_max_logging_users; + const char *login_integrity_mechanisms; /* mail */ const char *valid_chroot_dirs; --- /dev/null 2004-06-24 13:58:40.000000000 -0400 +++ src/auth/mech-gssapi.c 2004-07-11 19:57:29.000000000 -0400 @@ -0,0 +1,404 @@ +/* Copyright (C) 2004 Colin Walters */ + +/* GSSAPI SASL authentication, see RFC-2743 */ + +#include "common.h" +#include "buffer.h" +#include "hex-binary.h" +#include "str.h" +#include "mech.h" +#include "hostpid.h" + +#ifdef USE_GSSAPI + +#include +#include + +#if 0 +static const char *hexstring(const unsigned char *data, size_t len) +{ + string_t *str; + size_t i; + + str = str_new(default_pool, len*2); + for (i = 0; i < len; i++) { + str_printfa(str, "%02X", data[i]); + } + return str_data(str); +} +#endif + +enum gssapi_sasl_state { + SASL_GSSAPI_STATE_CTX_LOOP, + SASL_GSSAPI_STATE_FINAL_TOKEN, + SASL_GSSAPI_STATE_FINAL_NEGOTIATE, + SASL_GSSAPI_STATE_COMPLETE +}; + +struct gssapi_auth_request { + struct auth_request auth_request; + + pool_t pool; + + enum gssapi_sasl_state state; + gss_name_t client; + gss_ctx_id_t context; + + struct gssapi_auth_request_factory *factory; +}; + +struct gssapi_auth_request_factory { + struct auth_request_factory auth_factory; + + gss_cred_id_t cred; +}; + +static void log_gssapi_error(gss_ctx_id_t context, + const char *func_name, + OM_uint32 maj_stat, + OM_uint32 min_stat) +{ + OM_uint32 tmp_min_stat, tmp_maj_stat; + gss_buffer_desc gssbuf; + string_t *logbuf; + OM_uint32 msg_context; + + logbuf = str_new(default_pool, 128); + + msg_context = 0; + /* The GSSAPI error api is way overkill. */ + tmp_maj_stat = gss_display_status(&tmp_min_stat, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NULL_OID, + &msg_context, + &gssbuf); + if (GSS_ERROR(tmp_maj_stat)) { + str_append(logbuf, "UNKNOWN ("); + } else { + str_append_n(logbuf, gssbuf.value, gssbuf.length); + str_append(logbuf, " ("); + maj_stat = gss_release_buffer(&min_stat, &gssbuf); + } + + msg_context = 0; + tmp_maj_stat = gss_display_status(&tmp_min_stat, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &msg_context, + &gssbuf); + if (GSS_ERROR(tmp_maj_stat)) { + str_append(logbuf, "UNKNOWN)"); + } else { + str_append_n(logbuf, gssbuf.value, gssbuf.length); + maj_stat = gss_release_buffer(&min_stat, &gssbuf); + str_append_c(logbuf, ')'); + } + + i_warning("%s failed: %s", func_name, str_data(logbuf)); + str_free(logbuf); +} + +static int +mech_gssapi_auth_continue(struct auth_request *auth_request, + const unsigned char *data, size_t data_size, + mech_callback_t *callback) +{ + struct gssapi_auth_request *auth = + (struct gssapi_auth_request *)auth_request; + struct auth_client_request_reply reply; + const char *reply_data = NULL; + OM_uint32 min_stat, maj_stat; + gss_buffer_desc inbuf, outbuf; + + /* initialize reply */ + mech_init_auth_client_reply(&reply); + reply.id = auth_request->id; + + auth_request->callback = callback; + + switch (auth->state) { + case SASL_GSSAPI_STATE_CTX_LOOP: + { + inbuf.value = (char*) data; + inbuf.length = data_size; + i_info("gssapi: in ctx loop"); + maj_stat = gss_accept_sec_context(&min_stat, + &(auth->context), + auth->factory->cred, + &inbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &auth->client, + NULL, + &outbuf, NULL, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(auth->context, "gss_accept_sec_context", + maj_stat, min_stat); + reply_data = "Couldn't accept initial context"; + reply.data_size = strlen(reply_data)+1; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } else { + if (maj_stat == GSS_S_COMPLETE) { + auth->state = SASL_GSSAPI_STATE_FINAL_TOKEN; + i_info("gssapi: initial context accept completed"); + } else + i_info("gssapi: continuing initial context acceptance"); + + /* If not complete, then keep looping */ + reply.data_size = outbuf.length; + reply_data = outbuf.value; + reply.result = AUTH_CLIENT_RESULT_CONTINUE; + callback(&reply, reply_data, auth_request->conn); + maj_stat = gss_release_buffer(&min_stat, &outbuf); + if (maj_stat != GSS_S_COMPLETE) + return FALSE; + return TRUE; + } + break; + } + case SASL_GSSAPI_STATE_FINAL_TOKEN: + { + char ret[4]; + ret[0] = 0x07; + ret[1] = 0xFF; + ret[2] = 0xFF; + ret[3] = 0xFF; + inbuf.length = 4; + inbuf.value = ret; + i_info("gssapi: wrapping final token"); + maj_stat = gss_wrap(&min_stat, + auth->context, 0, + GSS_C_QOP_DEFAULT, + &inbuf, NULL, &outbuf); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(auth->context, "gss_wrap", + maj_stat, min_stat); + reply_data = "Couldn't complete layer negotiation: gss_wrap failure"; + reply.data_size = strlen(reply_data)+1; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } else { + auth->state = SASL_GSSAPI_STATE_FINAL_NEGOTIATE; + reply.result = AUTH_CLIENT_RESULT_CONTINUE; + reply.data_size = outbuf.length; + callback(&reply, outbuf.value, auth_request->conn); + maj_stat = gss_release_buffer(&min_stat, &outbuf); + return TRUE; + } + } + break; + case SASL_GSSAPI_STATE_FINAL_NEGOTIATE: + inbuf.value = (char*) data; + inbuf.length = data_size; + i_info("gssapi: completing final negotiation"); + maj_stat = gss_unwrap(&min_stat, + auth->context, + &inbuf, &outbuf, NULL, NULL); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(auth->context, "gss_unwrap", + maj_stat, min_stat); + reply_data = "Couldn't complete final negotiation: gss_unwrap failure"; + reply.data_size = strlen(reply_data)+1; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } else if (outbuf.length <= 4) { + reply_data = "Invalid response length"; + reply.data_size = strlen(reply_data)+1; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } else { + gss_buffer_desc exportbuf; + buffer_t *buf; + size_t buflen; + const char *bufdata; + + auth->state = SASL_GSSAPI_STATE_COMPLETE; + + auth_request->user = p_strndup(auth->pool, + ((unsigned char*) outbuf.value)+4, + outbuf.length-4); + i_info("gssapi: got user %s", auth_request->user); + + if (((unsigned char *) outbuf.value)[0] == 0x1) { + /* The client only supports authentication */ + maj_stat = gss_delete_sec_context(&min_stat, + &auth->context, + &exportbuf); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(auth->context, "gss_delete_sec_context", + maj_stat, min_stat); + } + maj_stat = gss_release_buffer(&min_stat, &outbuf); + mech_auth_finish(auth_request, NULL, 0, TRUE); + return TRUE; + } + + /* Prepare to transfer security context to + * login process, along with QOP flags + * and max transmit packet size. + */ + maj_stat = gss_export_sec_context(&min_stat, + &auth->context, + &exportbuf); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(auth->context, "gss_export_sec_context", + maj_stat, min_stat); + reply_data = "Couldn't export security context"; + reply.data_size = strlen(reply_data)+1; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } +#if 0 + i_info("exporting context: %s", hexstring(exportbuf.value, exportbuf.length)); +#endif + + buf = buffer_create_dynamic(pool_datastack_create(), + 4 + exportbuf.length, + (size_t)-1); + buffer_append(buf, outbuf.value, 4); + buffer_append(buf, exportbuf.value, exportbuf.length); + + bufdata = buffer_get_data(buf, &buflen); + mech_auth_finish_extended(auth_request, "GSSAPI", + NULL, 0, + bufdata, buflen, TRUE); + + maj_stat = gss_release_buffer(&min_stat, &outbuf); + maj_stat = gss_release_buffer(&min_stat, &exportbuf); + return TRUE; + } + break; + default: + /* failed */ + i_info("gssapi: internal error"); + reply_data = "Internal error"; + reply.result = AUTH_CLIENT_RESULT_FAILURE; + reply.data_size = strlen(reply_data)+1; + callback(&reply, reply_data, auth_request->conn); + return FALSE; + } +} + +static int +mech_gssapi_auth_initial(struct auth_request *auth_request, + struct auth_client_request_new *request, + const unsigned char *data, + mech_callback_t *callback) +{ +/* struct gssapi_auth_request *auth = */ +/* (struct gssapi_auth_request *)auth_request; */ + struct auth_client_request_reply reply; + size_t data_size; + + if (AUTH_CLIENT_REQUEST_HAVE_INITIAL_RESPONSE(request)) { + /* FIXME: support subsequent authentication? */ + data += request->initial_resp_idx; + data_size = request->data_size - request->initial_resp_idx; + + return auth_request->auth_continue(auth_request, data, + data_size, callback); + } + + /* initialize reply */ + mech_init_auth_client_reply(&reply); + reply.id = request->id; + reply.result = AUTH_CLIENT_RESULT_CONTINUE; + /* Empty reply */ + callback(&reply, NULL, auth_request->conn); + return TRUE; +} + +static void mech_gssapi_auth_free(struct auth_request *auth_request) +{ + pool_unref(auth_request->pool); +} + +static struct auth_request * +mech_gssapi_auth_new_from_factory(struct auth_request_factory *factory) +{ + struct gssapi_auth_request *auth; + pool_t pool; + + pool = pool_alloconly_create("gssapi_auth_request", 2048); + auth = p_new(pool, struct gssapi_auth_request, 1); + auth->pool = pool; + auth->factory = (struct gssapi_auth_request_factory *) factory; + + auth->context = GSS_C_NO_CONTEXT; + auth->client = NULL; + + auth->auth_request.refcount = 1; + auth->auth_request.pool = pool; + auth->auth_request.auth_initial = mech_gssapi_auth_initial; + auth->auth_request.auth_continue = mech_gssapi_auth_continue; + auth->auth_request.auth_free = mech_gssapi_auth_free; + return &auth->auth_request; +} + +static struct auth_request_factory * +mech_gssapi_auth_factory_new(void) +{ + struct gssapi_auth_request_factory *factory; + OM_uint32 min_stat, maj_stat; + gss_name_t server; + gss_buffer_desc buf; + string_t *service; + + factory = i_malloc(sizeof (struct gssapi_auth_request_factory)); + + service = t_str_new(25); + str_append(service, "imap@"); + str_append(service, my_hostname); + buf.length = str_len(service); + buf.value = (void*) str_c(service); + + maj_stat = gss_import_name(&min_stat, &buf, + GSS_C_NT_HOSTBASED_SERVICE, + &server); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(GSS_C_NO_CONTEXT, "gss_import_name", + maj_stat, min_stat); + i_info("gssapi: Couldn't import name \"%s\"", str_c(service)); + str_free(service); + return NULL; + } + i_info("gssapi: Imported name \"%s\"", str_c(service)); + str_free(service); + + maj_stat = gss_acquire_cred(&min_stat, server, 0, + GSS_C_NULL_OID_SET, GSS_C_ACCEPT, + &factory->cred, NULL, NULL); + gss_release_name(&min_stat, &server); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(GSS_C_NO_CONTEXT, "gss_acquire_cred", + maj_stat, min_stat); + i_info("gssapi: Couldn't acquire credentials"); + return NULL; + } + i_info("gssapi: Acquired credentials"); + + factory->auth_factory.auth_new = mech_gssapi_auth_new_from_factory; + return &factory->auth_factory; +} + +struct mech_module mech_gssapi = { + "GSSAPI", + + MEMBER(plaintext) FALSE, + MEMBER(advertise) TRUE, + + MEMBER(passdb_need_plain) FALSE, + MEMBER(passdb_need_credentials) FALSE, + + NULL, + mech_gssapi_auth_factory_new +}; + +#endif --- /dev/null 2004-06-24 13:58:40.000000000 -0400 +++ src/login-common/integrity-proxy.h 2004-07-10 20:38:07.000000000 -0400 @@ -0,0 +1,33 @@ +/* Copyright (C) 2004 Colin Walters + */ + +#include "mempool.h" + +#ifndef __INTEGRITY_PROXY_H +#define __INTEGRITY_PROXY_H + +struct integrity_proxy_factory +{ + const char *proxy_name; + void (*init)(struct integrity_proxy_factory *factory); + struct integrity_proxy *(*new)(struct integrity_proxy_factory *factory, + const unsigned char *context, size_t len, int fd); + void (*free)(struct integrity_proxy *proxy); + void (*destroy)(struct integrity_proxy_factory *factory); +}; + +struct integrity_proxy +{ + pool_t pool; + int fd; +}; + +struct integrity_proxy * integrity_proxy_new (const char *type, + const unsigned char *context, + size_t len, + int fd); + +void integrity_proxy_init(void); +void integrity_proxy_deinit(void); + +#endif --- /dev/null 2004-06-24 13:58:40.000000000 -0400 +++ src/login-common/integrity-proxy.c 2004-07-10 20:38:41.000000000 -0400 @@ -0,0 +1,110 @@ +/* Copyright (C) 2004 Colin Walters + * Based partially on mech.c, Copyright (C) 2004 Timo Sirainen + */ + +#include "common.h" +#include "integrity-proxy.h" +#include "str.h" +#include "var-expand.h" + +#include + +struct integrity_proxy_factory_list { + struct integrity_proxy_factory_list *next; + + struct integrity_proxy_factory *factory; +}; + +struct integrity_proxy_factory_list *integrity_proxy_factories; + +static void integrity_proxy_factory_register(struct integrity_proxy_factory *factory) +{ + struct integrity_proxy_factory_list *list; + + list = i_new(struct integrity_proxy_factory_list, 1); + list->factory = factory; + factory->init(factory); + list->next = integrity_proxy_factories; + integrity_proxy_factories = list; +} + +static void integrity_proxy_factory_unregister(struct integrity_proxy_factory *factory) +{ + struct integrity_proxy_factory_list **pos, *list; + + for (pos = &integrity_proxy_factories; *pos != NULL; pos = &(*pos)->next) { + if (strcmp((*pos)->factory->proxy_name, factory->proxy_name) == 0) { + list = *pos; + *pos = (*pos)->next; + list->factory->destroy(list->factory); + i_free(list); + break; + } + } +} + +static struct integrity_proxy_factory *integrity_proxy_factory_find(const char *name) +{ + struct integrity_proxy_factory_list *list; + + for (list = integrity_proxy_factories; list != NULL; list = list->next) { + if (strcasecmp(list->factory->proxy_name, name) == 0) { + return list->factory; + } + } + return NULL; +} + +struct integrity_proxy * integrity_proxy_new(const char *mech, + const unsigned char *ctx, + size_t len, int fd) +{ + struct integrity_proxy_factory *factory; + + factory = integrity_proxy_factory_find(mech); + if (factory == NULL) { + return NULL; + } + + return factory->new(factory, ctx, len, fd); +} + +#ifdef USE_GSSAPI +extern struct integrity_proxy_factory integrity_proxy_factory_gssapi; +#endif + +void integrity_proxy_init(void) +{ + const char *const *proxies; + const char *env; + + integrity_proxy_factories = NULL; + + /* register proxies */ + env = getenv("MECHANISMS"); + if (env == NULL || *env == '\0') + i_fatal("MECHANISMS environment is unset"); + + proxies = t_strsplit_spaces(env, " "); + while (*proxies != NULL) { +#ifdef USE_GSSAPI + if (strcasecmp(*proxies, "GSSAPI") == 0) { + integrity_proxy_factory_register(&integrity_proxy_factory_gssapi); +#else + if (0) { +#endif + } else { + i_fatal("Unknown integrity proxy '%s'", + *proxies); + } + + proxies++; + } +} + +void integrity_proxy_deinit(void) +{ +#ifdef USE_GSSAPI + integrity_proxy_factory_unregister(&integrity_proxy_factory_gssapi); +#endif +} --- /dev/null 2004-06-24 13:58:40.000000000 -0400 +++ src/login-common/integrity-proxy-gssapi.c 2004-07-11 20:35:09.000000000 -0400 @@ -0,0 +1,576 @@ +/* Copyright (C) 2004 Colin Walters + * Influenced by ssl-proxy-openssl.c, Copyright (C) 2002 Timo Sirainen + */ + +#include "common.h" +#include "ioloop.h" +#include "network.h" +#include "hash.h" +#include "buffer.h" +#include "str.h" +#include "integrity-proxy.h" +#include + +#ifdef USE_GSSAPI + +#include + +struct integrity_proxy_gssapi { + struct integrity_proxy proxy; + + int refcount; + + int fd_secure, fd_plain; + struct io *io_secure_read, *io_secure_write, *io_plain_read, *io_plain_write; + + OM_uint32 max_plain_input_size; + + unsigned int secure_size_bytes_to_read; + unsigned char size_bytes[4]; + unsigned int secure_msg_bytes_to_read; + buffer_t *secure_input_buf; + + buffer_t *plain_output_buf; + + buffer_t *secure_output_buf; + + gss_ctx_id_t context; + + int destroyed:1; +}; + +static int integrity_proxy_gssapi_initialized = 0; +static struct hash_table *gssapi_integrity_proxies; + +static void integrity_proxy_gssapi_factory_init(struct integrity_proxy_factory *factory); +static void integrity_proxy_gssapi_factory_destroy(struct integrity_proxy_factory *factory); +static struct integrity_proxy *integrity_proxy_gssapi_new(struct integrity_proxy_factory *factory, + const unsigned char *context, + size_t len, + int fd); +static void integrity_proxy_gssapi_free(struct integrity_proxy *proxy); +static void plain_read(void *context); +static void plain_write(void *context); +static void secure_write(void *context); +static void secure_read(void *context); +static void integrity_proxy_gssapi_destroy(struct integrity_proxy_gssapi *proxy); +static int integrity_proxy_gssapi_unref(struct integrity_proxy_gssapi *proxy); +static void secure_block_input(struct integrity_proxy_gssapi *proxy, int block); +static void plain_block_input(struct integrity_proxy_gssapi *proxy, int block); + +static void integrity_proxy_gssapi_factory_init(struct integrity_proxy_factory *factory + __attr_unused__) +{ + integrity_proxy_gssapi_initialized = 1; + gssapi_integrity_proxies = hash_create(default_pool, default_pool, 0, NULL, NULL); +} + +static void integrity_proxy_gssapi_factory_destroy(struct integrity_proxy_factory *factory + __attr_unused__) +{ + struct hash_iterate_context *iter; + void *key, *value; + + if (!integrity_proxy_gssapi_initialized) + return; + + iter = hash_iterate_init(gssapi_integrity_proxies); + while (hash_iterate(iter, &key, &value)) + integrity_proxy_gssapi_destroy(value); + hash_iterate_deinit(iter); + hash_destroy(gssapi_integrity_proxies); +} + +static void log_gssapi_error(gss_ctx_id_t context, + const char *func_name, + OM_uint32 maj_stat, + OM_uint32 min_stat) +{ + OM_uint32 tmp_min_stat, tmp_maj_stat; + gss_buffer_desc gssbuf; + string_t *logbuf; + OM_uint32 msg_context; + + logbuf = str_new(default_pool, 128); + + msg_context = 0; + /* The GSSAPI error api is way overkill. */ + tmp_maj_stat = gss_display_status(&tmp_min_stat, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NULL_OID, + &msg_context, + &gssbuf); + if (GSS_ERROR(tmp_maj_stat)) { + str_append(logbuf, "UNKNOWN ("); + } else { + str_append_n(logbuf, gssbuf.value, gssbuf.length); + str_append(logbuf, " ("); + maj_stat = gss_release_buffer(&min_stat, &gssbuf); + } + + msg_context = 0; + tmp_maj_stat = gss_display_status(&tmp_min_stat, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NULL_OID, + &msg_context, + &gssbuf); + if (GSS_ERROR(tmp_maj_stat)) { + str_append(logbuf, "UNKNOWN)"); + } else { + str_append_n(logbuf, gssbuf.value, gssbuf.length); + maj_stat = gss_release_buffer(&min_stat, &gssbuf); + str_append_c(logbuf, ')'); + } + + i_warning("%s failed: %s", func_name, str_data(logbuf)); + str_free(logbuf); +} + +static struct integrity_proxy *integrity_proxy_gssapi_new(struct integrity_proxy_factory *factory __attr_unused__, + const unsigned char *context, + size_t len, + int fd) +{ + struct integrity_proxy_gssapi *gss; + pool_t pool; + int sfd[2]; + gss_buffer_desc buf; + OM_uint32 min_stat, maj_stat; + gss_qop_t qop; + uint32_t max_client_bufsize; + + pool = pool_alloconly_create("integrity_proxy_gssapi", 2048); + + gss = p_new(pool, struct integrity_proxy_gssapi, 1); + gss->proxy.pool = pool; + gss->refcount = 2; + + if (len <= 4) { + i_error("too short context in integrity_proxy_gssapi_new"); + return NULL; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) { + i_error("socketpair() failed: %m"); + return NULL; + } + + net_set_nonblock(sfd[0], TRUE); + net_set_nonblock(sfd[1], TRUE); + net_set_nonblock(fd, TRUE); + + gss->fd_secure = fd; + gss->fd_plain = sfd[0]; + gss->proxy.fd = sfd[1]; + + qop = context[0]; + max_client_bufsize = (context[1] << 16) + (context[2] << 8) + context[3]; + i_info("qop %d max_client_bufsize %d", qop, max_client_bufsize); + /* Sanity. */ + if (max_client_bufsize == 0) + max_client_bufsize = 1024; + + buf.value = (void *) (context+4); + buf.length = len-4; + maj_stat = gss_import_sec_context(&min_stat, + &buf, + &gss->context); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(gss->context, + "gss_import_sec_context", + maj_stat, min_stat); + close(sfd[0]); + close(sfd[1]); + pool_unref(pool); + return NULL; + } + + maj_stat = gss_wrap_size_limit(&min_stat, + gss->context, + 1, + GSS_C_QOP_DEFAULT, + max_client_bufsize, + &gss->max_plain_input_size); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(gss->context, + "gss_wrap_size_limit", + maj_stat, min_stat); + gss->max_plain_input_size = 1024; + } + + i_info("created new integrity proxy"); + + hash_insert(gssapi_integrity_proxies, gss, gss); + + gss->secure_size_bytes_to_read = 4; + + gss->secure_input_buf = buffer_create_dynamic(default_pool, 4096, (size_t)-1); + gss->secure_output_buf = buffer_create_dynamic(default_pool, 4096, (size_t)-1); + gss->plain_output_buf = buffer_create_dynamic(default_pool, 4096, (size_t)-1); + secure_block_input(gss, FALSE); + plain_block_input(gss, FALSE); + main_ref(); + + return &gss->proxy; +} + +static void integrity_proxy_gssapi_free(struct integrity_proxy *proxy) +{ + struct integrity_proxy_gssapi *gss = (struct integrity_proxy_gssapi *) proxy; + if (!gss->destroyed) + integrity_proxy_gssapi_destroy(gss); + + integrity_proxy_gssapi_unref(gss); +} + +static void secure_block_input(struct integrity_proxy_gssapi *proxy, + int block) +{ + if (block) { + if (proxy->io_secure_read != NULL) { + io_remove(proxy->io_secure_read); + proxy->io_secure_read = NULL; + } + } else { + if (proxy->io_secure_read == NULL) { + proxy->io_secure_read = io_add(proxy->fd_secure, IO_READ, + secure_read, proxy); + } + } +} + +static void secure_block_output(struct integrity_proxy_gssapi *proxy, + int block) +{ + if (block) { + if (proxy->io_secure_write != NULL) { + io_remove(proxy->io_secure_write); + proxy->io_secure_write = NULL; + } + } else { + if (proxy->io_secure_write == NULL) { + proxy->io_secure_write = io_add(proxy->fd_secure, IO_WRITE, + secure_write, proxy); + } + } +} + +static void plain_block_input(struct integrity_proxy_gssapi *proxy, int block) +{ + if (block) { + if (proxy->io_plain_read != NULL) { + io_remove(proxy->io_plain_read); + proxy->io_plain_read = NULL; + } + } else { + if (proxy->io_plain_read == NULL) { + proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ, + plain_read, proxy); + } + } +} + +static void plain_read(void *context) +{ + struct integrity_proxy_gssapi *proxy = context; + buffer_t *tmp_buf; + size_t buflen; + ssize_t ret; + OM_uint32 min_stat, maj_stat; + gss_buffer_desc inbuf, outbuf; + + i_info("plain read"); + + proxy->refcount++; + + tmp_buf = buffer_create_dynamic(pool_datastack_create(), 4096, (size_t)-1); + while (buffer_get_used_size(tmp_buf) < proxy->max_plain_input_size && + !proxy->destroyed) { + unsigned char tmp_secure_output_buf[1024]; + ret = net_receive(proxy->fd_plain, + tmp_secure_output_buf, + sizeof(tmp_secure_output_buf)); + if (ret <= 0) { + if (ret < 0) { + i_warning("net_receive in plain_read failed: %m"); + integrity_proxy_gssapi_destroy(proxy); + } + break; + } else { + buffer_append(tmp_buf, tmp_secure_output_buf, ret); + } + } + + + inbuf.value = (void*) buffer_get_data(tmp_buf, &buflen); + inbuf.length = buflen; + i_info("read %d plain bytes", buflen); + + if (buflen > 0) { + maj_stat = gss_wrap(&min_stat, + proxy->context, + 1, + GSS_C_QOP_DEFAULT, + &inbuf, + NULL, + &outbuf); + + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(proxy->context, "gss_wrap", + maj_stat, min_stat); + integrity_proxy_gssapi_destroy(proxy); + } + + /* Store the size in network byte order */ + buffer_append_c(proxy->secure_output_buf, + outbuf.length & (0xFF000000)); + buffer_append_c(proxy->secure_output_buf, + outbuf.length & (0x00FF0000)); + buffer_append_c(proxy->secure_output_buf, + outbuf.length & (0x0000FF00)); + buffer_append_c(proxy->secure_output_buf, + outbuf.length & (0x000000FF)); + buffer_append(proxy->secure_output_buf, + outbuf.value, outbuf.length); + i_info("transformed plain bytes to %d encrypted bytes", outbuf.length); + maj_stat = gss_release_buffer(&min_stat, &outbuf); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(proxy->context, "gss_release_buffer", + maj_stat, min_stat); + } + secure_write(proxy); + } + + integrity_proxy_gssapi_unref(proxy); +} + +static void plain_write(void *context) +{ + struct integrity_proxy_gssapi *proxy = context; + ssize_t ret; + const unsigned char *bufdata; + size_t bufsize; + + i_info("plain write"); + + proxy->refcount++; + + bufdata = buffer_get_data(proxy->plain_output_buf, &bufsize); + ret = net_transmit(proxy->fd_plain, bufdata, bufsize); + if (ret < 0) { + i_warning("net_transmit in plain_write failed: %m"); + integrity_proxy_gssapi_destroy(proxy); + } else if (ret > 0) { + buffer_delete(proxy->plain_output_buf, 0, ret); + i_info("wrote %d plain bytes", ret); + + if (buffer_get_used_size(proxy->plain_output_buf) > 0) { + if (proxy->io_plain_write == NULL) { + proxy->io_plain_write = + io_add(proxy->fd_plain, IO_WRITE, + plain_write, proxy); + } + } else { + if (proxy->io_plain_write != NULL) { + io_remove(proxy->io_plain_write); + proxy->io_plain_write = NULL; + } + } + + secure_block_input(proxy, FALSE); + } + + integrity_proxy_gssapi_unref(proxy); +} + +static void handle_msg_completion(struct integrity_proxy_gssapi *proxy) +{ + OM_uint32 min_stat, maj_stat; + gss_buffer_desc inbuf, outbuf; + size_t bufsize; + + inbuf.value = (void*) buffer_get_data(proxy->secure_input_buf, &bufsize); + inbuf.length = bufsize; + + maj_stat = gss_unwrap(&min_stat, + proxy->context, + &inbuf, &outbuf, + NULL, NULL); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(proxy->context, "gss_unwrap", maj_stat, min_stat); + integrity_proxy_gssapi_destroy(proxy); + return; + } + i_info("token complete, unwrapped to %d plain bytes", outbuf.length); + buffer_delete(proxy->secure_input_buf, 0, bufsize); + + buffer_append(proxy->plain_output_buf, outbuf.value, outbuf.length); + maj_stat = gss_release_buffer(&min_stat, &outbuf); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(proxy->context, "gss_release_buffer", + maj_stat, min_stat); + } + + plain_write(proxy); + + proxy->secure_size_bytes_to_read = 4; + proxy->secure_msg_bytes_to_read = 0; +} + +static void secure_read(void *context) +{ + int ret; + int did_read_size_bytes; + struct integrity_proxy_gssapi *proxy = context; + + proxy->refcount++; + + did_read_size_bytes = 0; + while (proxy->secure_size_bytes_to_read > 0 + && !proxy->destroyed) { + unsigned int offset; + + i_assert(proxy->secure_size_bytes_to_read <= sizeof(proxy->size_bytes)); + + offset = sizeof(proxy->size_bytes) - proxy->secure_size_bytes_to_read; + ret = net_receive(proxy->fd_secure, + proxy->size_bytes + offset, + proxy->secure_size_bytes_to_read); + + if (ret <= 0) { + if (ret < 0) { + i_warning("net_receive in secure_read failed: %m"); + integrity_proxy_gssapi_destroy(proxy); + } + break; + } else { + proxy->secure_size_bytes_to_read -= ret; + did_read_size_bytes = 1; + } + } + + if (did_read_size_bytes && proxy->secure_size_bytes_to_read == 0 + && !proxy->destroyed) { + /* Transition to reading the message content */ + proxy->secure_msg_bytes_to_read = + (proxy->size_bytes[0] << 24) + + (proxy->size_bytes[1] << 16) + + (proxy->size_bytes[2] << 8) + + (proxy->size_bytes[3]); + i_info("reading secure token of length %d bytes", + proxy->secure_msg_bytes_to_read); + } + + while (proxy->secure_size_bytes_to_read == 0 + && proxy->secure_msg_bytes_to_read > 0 + && !proxy->destroyed) { + unsigned char tmp_secure_input_buf[1024]; + size_t bytes_to_read; + + bytes_to_read = I_MIN(sizeof(tmp_secure_input_buf), + proxy->secure_msg_bytes_to_read); + + ret = net_receive(proxy->fd_secure, + tmp_secure_input_buf, + bytes_to_read); + if (ret <= 0) { + if (ret < 0) { + i_warning("net_receive in secure_read failed: %m"); + integrity_proxy_gssapi_destroy(proxy); + } + break; + } else { + buffer_append(proxy->secure_input_buf, + tmp_secure_input_buf, + ret); + i_info("read %d secure bytes", ret); + proxy->secure_msg_bytes_to_read -= ret; + if (proxy->secure_msg_bytes_to_read == 0) + handle_msg_completion(proxy); + } + } + + integrity_proxy_gssapi_unref(proxy); +} + +static void secure_write(void *context) +{ + int ret; + struct integrity_proxy_gssapi *proxy = context; + const unsigned char *bufdata; + size_t bufsize; + + i_info("secure write"); + + bufdata = (void*) buffer_get_data(proxy->secure_output_buf, &bufsize); + + ret = net_transmit(proxy->fd_secure, bufdata, bufsize); + if (ret < 0) { + integrity_proxy_gssapi_destroy(proxy); + i_warning("net_transmit failed: %m"); + } else { + i_info("wrote %d secure bytes", ret); + buffer_delete(proxy->secure_output_buf, 0, ret); + secure_block_output(proxy, (bufsize - ret) <= 0); + plain_block_input(proxy, FALSE); + } +} + +static int integrity_proxy_gssapi_unref(struct integrity_proxy_gssapi *proxy) +{ + if (--proxy->refcount > 0) + return TRUE; + i_assert(proxy->refcount == 0); + + pool_unref(proxy->proxy.pool); + main_unref(); + + return FALSE; +} + +static void integrity_proxy_gssapi_destroy(struct integrity_proxy_gssapi *proxy) +{ + OM_uint32 min_stat, maj_stat; + + i_info("destroying integrity proxy"); + + hash_remove(gssapi_integrity_proxies, proxy); + + (void)net_disconnect(proxy->fd_secure); + (void)net_disconnect(proxy->fd_plain); + + if (proxy->io_secure_read != NULL) + io_remove(proxy->io_secure_read); + if (proxy->io_secure_write != NULL) + io_remove(proxy->io_secure_write); + if (proxy->io_plain_read != NULL) + io_remove(proxy->io_plain_read); + if (proxy->io_plain_write != NULL) + io_remove(proxy->io_plain_write); + + maj_stat = gss_delete_sec_context(&min_stat, + &proxy->context, + NULL); + if (GSS_ERROR(maj_stat)) { + log_gssapi_error(GSS_C_NO_CONTEXT, + "gss_delete_sec_context", + maj_stat, min_stat); + } + + buffer_free(proxy->plain_output_buf); + buffer_free(proxy->secure_input_buf); + buffer_free(proxy->secure_output_buf); +} + +struct integrity_proxy_factory integrity_proxy_factory_gssapi = { + "GSSAPI", + + integrity_proxy_gssapi_factory_init, + integrity_proxy_gssapi_new, + integrity_proxy_gssapi_free, + integrity_proxy_gssapi_factory_destroy, +}; + +#endif