/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2021 Free Software Foundation, Inc.
GNU Mailutils 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 3, or (at your option)
any later version.
GNU Mailutils 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 GNU Mailutils. If not, see . */
/*
GSSAPI authentication for imap (rfc 1731).
*/
#include "imap4d.h"
#include
#ifdef WITH_GSS
# include
#else
# include
# ifdef HAVE_GSSAPI_H
# include
# else
# ifdef HAVE_GSSAPI_GSSAPI_H
# include
# endif
# ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
# include
# endif
# endif
#endif
#define GSS_AUTH_P_NONE 1
#define GSS_AUTH_P_INTEGRITY 2
#define GSS_AUTH_P_PRIVACY 4
#define SUPPORTED_P_MECH GSS_AUTH_P_NONE
static int protection_mech;
size_t server_buffer_size = 8192;
size_t client_buffer_size;
static void
display_status_1 (char *m, OM_uint32 code, int type)
{
OM_uint32 maj_stat, min_stat;
gss_buffer_desc msg;
OM_uint32 msg_ctx;
msg_ctx = 0;
do
{
maj_stat = gss_display_status (&min_stat, code,
type, GSS_C_NO_OID, &msg_ctx, &msg);
if (GSS_ERROR (maj_stat))
{
mu_asprintf ((char**)&msg.value, "code %d", code);
msg.length = strlen (msg.value);
}
mu_diag_output (MU_DIAG_ERROR, _("GSS-API error %s (%s): %.*s"),
m, type == GSS_C_GSS_CODE ? _("major") : _("minor"),
(int) msg.length, (char *) msg.value);
if (GSS_ERROR (maj_stat))
free (msg.value);
else
gss_release_buffer (&min_stat, &msg);
}
while (!GSS_ERROR (maj_stat) && msg_ctx);
}
static void
display_status (char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
{
display_status_1 (msg, maj_stat, GSS_C_GSS_CODE);
display_status_1 (msg, min_stat, GSS_C_MECH_CODE);
}
#ifndef WITH_GSS
static int
imap4d_gss_userok (gss_buffer_t client_name, char *name)
{
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;
}
#endif
static enum imap4d_auth_result
auth_gssapi (struct imap4d_auth *ap)
{
gss_buffer_desc tokbuf, outbuf;
OM_uint32 maj_stat, min_stat, min_stat2;
int cflags;
OM_uint32 sec_level, mech;
gss_ctx_id_t context;
gss_cred_id_t cred_handle, server_creds;
gss_OID mech_type;
char *token_str = NULL;
size_t token_size = 0;
size_t token_len;
unsigned char *tmp = NULL;
size_t size;
gss_name_t server_name;
gss_qop_t quality;
gss_name_t client;
gss_buffer_desc client_name;
int baduser;
/* Obtain server credentials. RFC 1732 states, that
"The server must issue a ready response with no data and pass the
resulting client supplied token to GSS_Accept_sec_context as
input_token, setting acceptor_cred_handle to NULL (for "use default
credentials"), and 0 for input_context_handle (initially)."
In MIT implementation, passing NULL as acceptor_cred_handle won't
work (possibly due to a bug in krb5_gss_accept_sec_context()), so
we acquire server credentials explicitly. */
mu_asprintf ((char **) &tmp, "imap@%s", util_localname ());
tokbuf.value = tmp;
tokbuf.length = strlen (tokbuf.value) + 1;
maj_stat = gss_import_name (&min_stat, &tokbuf,
GSS_C_NT_HOSTBASED_SERVICE, &server_name);
if (maj_stat != GSS_S_COMPLETE)
{
display_status ("import name", maj_stat, min_stat);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
maj_stat = gss_acquire_cred (&min_stat, server_name, 0,
GSS_C_NULL_OID_SET, GSS_C_ACCEPT,
&server_creds, NULL, NULL);
gss_release_name (&min_stat2, &server_name);
if (maj_stat != GSS_S_COMPLETE)
{
display_status ("acquire credentials", maj_stat, min_stat);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
/* Start the dialogue */
io_sendf ("+ \n");
io_flush ();
context = GSS_C_NO_CONTEXT;
for (;;)
{
OM_uint32 ret_flags;
io_getline (&token_str, &token_size, &token_len);
mu_base64_decode ((unsigned char*) token_str, token_len, &tmp, &size);
tokbuf.value = tmp;
tokbuf.length = size;
maj_stat = gss_accept_sec_context (&min_stat,
&context,
server_creds,
&tokbuf,
GSS_C_NO_CHANNEL_BINDINGS,
&client,
&mech_type,
&outbuf,
&ret_flags, NULL, &cred_handle);
free (tmp);
if (maj_stat == GSS_S_CONTINUE_NEEDED)
{
if (outbuf.length)
{
mu_base64_encode (outbuf.value, outbuf.length, &tmp, &size);
io_sendf ("+ %s\n", tmp);
free (tmp);
gss_release_buffer (&min_stat, &outbuf);
}
continue;
}
else if (maj_stat == GSS_S_COMPLETE)
break;
/* Bail out otherwise */
display_status ("accept context", maj_stat, min_stat);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
free (token_str);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
if (outbuf.length)
{
mu_base64_encode (outbuf.value, outbuf.length, &tmp, &size);
io_sendf ("+ %s\n", tmp);
free (tmp);
gss_release_buffer (&min_stat, &outbuf);
io_getline (&token_str, &token_size, &token_len);
}
/* Construct security-level data */
sec_level = htonl ((SUPPORTED_P_MECH << 24) | server_buffer_size);
tokbuf.length = 4;
tokbuf.value = &sec_level;
maj_stat = gss_wrap (&min_stat, context, 0, GSS_C_QOP_DEFAULT,
&tokbuf, &cflags, &outbuf);
if (maj_stat != GSS_S_COMPLETE)
{
display_status ("wrap", maj_stat, min_stat);
free (token_str);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
mu_base64_encode (outbuf.value, outbuf.length, &tmp, &size);
io_sendf ("+ %s\n", tmp);
free (tmp);
io_getline (&token_str, &token_size, &token_len);
mu_base64_decode ((unsigned char *) token_str, token_len,
(unsigned char **) &tokbuf.value, &tokbuf.length);
free (token_str);
maj_stat = gss_unwrap (&min_stat, context, &tokbuf, &outbuf, &cflags,
&quality);
free (tokbuf.value);
if (maj_stat != GSS_S_COMPLETE)
{
display_status ("unwrap", maj_stat, min_stat);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
sec_level = ntohl (*(OM_uint32 *) outbuf.value);
/* FIXME: parse sec_level and act accordingly to its settings */
mech = sec_level >> 24;
if ((mech & SUPPORTED_P_MECH) == 0)
{
mu_diag_output (MU_DIAG_NOTICE,
_("client requested unsupported protection mechanism (%d)"),
mech);
gss_release_buffer (&min_stat, &outbuf);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
protection_mech = mech;
client_buffer_size = sec_level & 0x00ffffffff;
ap->username = malloc (outbuf.length - 4 + 1);
if (!ap->username)
{
mu_diag_output (MU_DIAG_NOTICE, _("not enough memory"));
gss_release_buffer (&min_stat, &outbuf);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
memcpy (ap->username, (char *) outbuf.value + 4, outbuf.length - 4);
ap->username[outbuf.length - 4] = '\0';
gss_release_buffer (&min_stat, &outbuf);
maj_stat = gss_display_name (&min_stat, client, &client_name, &mech_type);
if (maj_stat != GSS_S_COMPLETE)
{
display_status ("get client name", maj_stat, min_stat);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
free (ap->username);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
#ifdef WITH_GSS
baduser = !gss_userok (client, ap->username);
#else
baduser = imap4d_gss_userok (&client_name, ap->username);
#endif
if (baduser)
{
mu_diag_output (MU_DIAG_NOTICE,
_("GSSAPI user %s is NOT authorized as %s"),
(char *) client_name.value, ap->username);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
gss_release_buffer (&min_stat, &client_name);
free (ap->username);
ap->response = RESP_NO;
return imap4d_auth_resp;
}
else
{
mu_diag_output (MU_DIAG_NOTICE, _("GSSAPI user %s is authorized as %s"),
(char *) client_name.value, ap->username);
}
gss_release_buffer (&min_stat, &client_name);
maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf);
gss_release_buffer (&min_stat, &outbuf);
ap->response = RESP_OK;
return imap4d_auth_resp;
}
void
auth_gssapi_init ()
{
auth_add ("GSSAPI", auth_gssapi);
}