Пример #1
0
int
auth_cram_md5_server(auth_instance *ablock, uschar *data)
{
auth_cram_md5_options_block *ob =
  (auth_cram_md5_options_block *)(ablock->options_block);
uschar *challenge = string_sprintf("<%d.%ld@%s>", getpid(),
    (long int) time(NULL), primary_hostname);
uschar *clear, *secret;
uschar digest[16];
int i, rc, len;

/* If we are running in the test harness, always send the same challenge,
an example string taken from the RFC. */

if (running_in_test_harness)
  challenge = US"<*****@*****.**>";

/* No data should have been sent with the AUTH command */

if (*data != 0) return UNEXPECTED;

/* Send the challenge, read the return */

if ((rc = auth_get_data(&data, challenge, Ustrlen(challenge))) != OK) return rc;
if ((len = b64decode(data, &clear)) < 0) return BAD64;

/* The return consists of a user name, space-separated from the CRAM-MD5
digest, expressed in hex. Extract the user name and put it in $auth1 and $1.
The former is now the preferred variable; the latter is the original one. Then
check that the remaining length is 32. */

auth_vars[0] = expand_nstring[1] = clear;
while (*clear != 0 && !isspace(*clear)) clear++;
if (!isspace(*clear)) return FAIL;
*clear++ = 0;

expand_nlength[1] = clear - expand_nstring[1] - 1;
if (len - expand_nlength[1] - 1 != 32) return FAIL;
expand_nmax = 1;

/* Expand the server_secret string so that it can compute a value dependent on
the user name if necessary. */

debug_print_string(ablock->server_debug_string);    /* customized debugging */
secret = expand_string(ob->server_secret);

/* A forced fail implies failure of authentication - i.e. we have no secret for
the given name. */

if (secret == NULL)
  {
  if (expand_string_forcedfail) return FAIL;
  auth_defer_msg = expand_string_message;
  return DEFER;
  }

/* Compute the CRAM-MD5 digest that we should have received from the client. */

compute_cram_md5(secret, challenge, digest);

HDEBUG(D_auth)
  {
  uschar buff[64];
  debug_printf("CRAM-MD5: user name = %s\n", auth_vars[0]);
  debug_printf("          challenge = %s\n", challenge);
  debug_printf("          received  = %s\n", clear);
  Ustrcpy(buff,"          digest    = ");
  for (i = 0; i < 16; i++) sprintf(CS buff+22+2*i, "%02x", digest[i]);
  debug_printf("%.54s\n", buff);
  }

/* We now have to compare the digest, which is 16 bytes in binary, with the
data received, which is expressed in lower case hex. We checked above that
there were 32 characters of data left. */

for (i = 0; i < 16; i++)
  {
  int a = *clear++;
  int b = *clear++;
  if (((((a >= 'a')? a - 'a' + 10 : a - '0') << 4) +
        ((b >= 'a')? b - 'a' + 10 : b - '0')) != digest[i]) return FAIL;
  }

/* Expand server_condition as an authorization check */
return auth_check_serv_cond(ablock);
}
Пример #2
0
int
auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
{
auth_cyrus_sasl_options_block *ob =
  (auth_cyrus_sasl_options_block *)(ablock->options_block);
uschar *output, *out2, *input, *clear, *hname;
uschar *debug = NULL;   /* Stops compiler complaining */
sasl_callback_t cbs[]={{SASL_CB_LIST_END, NULL, NULL}};
sasl_conn_t *conn;
int rc, firsttime=1, clen;
unsigned int inlen, outlen;

input=data;
inlen=Ustrlen(data);

HDEBUG(D_auth) debug=string_copy(data);

hname=expand_string(ob->server_hostname);
if(hname == NULL)
  {
  auth_defer_msg = expand_string_message;
  return DEFER;
  }

if(inlen)
  {
  clen=auth_b64decode(input, &clear);
  if(clen < 0)
    {
    return BAD64;
    }
  input=clear;
  inlen=clen;
  }

rc=sasl_server_init(cbs, "exim");
if (rc != SASL_OK)
  {
  auth_defer_msg = US"couldn't initialise Cyrus SASL library";
  return DEFER;
  }

rc=sasl_server_new(CS ob->server_service, CS hname, CS ob->server_realm, NULL,
  NULL, NULL, 0, &conn);

if( rc != SASL_OK )
  {
  auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
  sasl_done();
  return DEFER;
  }

rc=SASL_CONTINUE;

while(rc==SASL_CONTINUE)
  {
  if(firsttime)
    {
    firsttime=0;
    HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
    rc=sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
           (const char **)(&output), &outlen);
    }
  else
    {
    /* make sure that we have a null-terminated string */
    out2=store_get(outlen+1);
    memcpy(out2,output,outlen);
    out2[outlen]='\0';
    if((rc=auth_get_data(&input, out2, outlen))!=OK)
      {
      /* we couldn't get the data, so free up the library before
       * returning whatever error we get */
      sasl_dispose(&conn);
      sasl_done();
      return rc;
      }
    inlen=Ustrlen(input);

    HDEBUG(D_auth) debug=string_copy(input);
    if(inlen)
      {
      clen=auth_b64decode(input, &clear);
      if(clen < 0)
       {
        sasl_dispose(&conn);
        sasl_done();
       return BAD64;
       }
      input=clear;
      inlen=clen;
      }

    HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
    rc=sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
    }
  if(rc==SASL_BADPROT)
    {
    sasl_dispose(&conn);
    sasl_done();
    return UNEXPECTED;
    }
  else if( rc==SASL_FAIL     || rc==SASL_BUFOVER
       || rc==SASL_BADMAC   || rc==SASL_BADAUTH
       || rc==SASL_NOAUTHZ  || rc==SASL_ENCRYPT
       || rc==SASL_EXPIRED  || rc==SASL_DISABLED
       || rc==SASL_NOUSER   )
    {
    /* these are considered permanent failure codes */
    HDEBUG(D_auth)
      debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
    log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
       "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
       sasl_errstring(rc, NULL, NULL));
    sasl_dispose(&conn);
    sasl_done();
    return FAIL;
    }
  else if(rc==SASL_NOMECH)
    {
    /* this is a temporary failure, because the mechanism is not
     * available for this user. If it wasn't available at all, we
     * shouldn't have got here in the first place...
     */
    HDEBUG(D_auth)
      debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
    auth_defer_msg =
        string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
    sasl_dispose(&conn);
    sasl_done();
    return DEFER;
    }
  else if(!(rc==SASL_OK || rc==SASL_CONTINUE))
    {
    /* Anything else is a temporary failure, and we'll let SASL print out
     * the error string for us
     */
    HDEBUG(D_auth)
      debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
    auth_defer_msg =
        string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
    sasl_dispose(&conn);
    sasl_done();
    return DEFER;
    }
  else if(rc==SASL_OK)
    {
    /* Get the username and copy it into $auth1 and $1. The former is now the
    preferred variable; the latter is the original variable. */
    rc = sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2));
    auth_vars[0] = expand_nstring[1] = string_copy(out2);
    expand_nlength[1] = Ustrlen(expand_nstring[1]);
    expand_nmax = 1;

    HDEBUG(D_auth)
      debug_printf("Cyrus SASL %s authentication succeeded for %s\n", ob->server_mech, out2);
    /* close down the connection, freeing up library's memory */
    sasl_dispose(&conn);
    sasl_done();

    /* Expand server_condition as an authorization check */
    return auth_check_serv_cond(ablock);
    }
  }
/* NOTREACHED */
return 0;  /* Stop compiler complaints */
}
Пример #3
0
int
auth_cyrus_sasl_server(auth_instance *ablock, uschar *data)
{
auth_cyrus_sasl_options_block *ob =
  (auth_cyrus_sasl_options_block *)(ablock->options_block);
uschar *output, *out2, *input, *clear, *hname;
uschar *debug = NULL;   /* Stops compiler complaining */
sasl_callback_t cbs[] = {{SASL_CB_LIST_END, NULL, NULL}};
sasl_conn_t *conn;
char * realm_expanded = NULL;
int rc, firsttime = 1, clen, *negotiated_ssf_ptr = NULL, negotiated_ssf;
unsigned int inlen, outlen;

input = data;
inlen = Ustrlen(data);

HDEBUG(D_auth) debug = string_copy(data);

hname = expand_string(ob->server_hostname);
if (hname && ob->server_realm)
  realm_expanded = CS expand_string(ob->server_realm);
if (!hname  ||  !realm_expanded  && ob->server_realm)
  {
  auth_defer_msg = expand_string_message;
  return DEFER;
  }

if (inlen)
  {
  if ((clen = b64decode(input, &clear)) < 0)
    return BAD64;
  input = clear;
  inlen = clen;
  }

if ((rc = sasl_server_init(cbs, "exim")) != SASL_OK)
  {
  auth_defer_msg = US"couldn't initialise Cyrus SASL library";
  return DEFER;
  }

rc = sasl_server_new(CS ob->server_service, CS hname, realm_expanded, NULL,
  NULL, NULL, 0, &conn);

HDEBUG(D_auth)
  debug_printf("Initialised Cyrus SASL server connection; service=\"%s\" fqdn=\"%s\" realm=\"%s\"\n",
      ob->server_service, hname, realm_expanded);

if (rc != SASL_OK )
  {
  auth_defer_msg = US"couldn't initialise Cyrus SASL connection";
  sasl_done();
  return DEFER;
  }

if (tls_in.cipher)
  {
  if ((rc = sasl_setprop(conn, SASL_SSF_EXTERNAL, (sasl_ssf_t *) &tls_in.bits)) != SASL_OK)
    {
    HDEBUG(D_auth) debug_printf("Cyrus SASL EXTERNAL SSF set %d failed: %s\n",
        tls_in.bits, sasl_errstring(rc, NULL, NULL));
    auth_defer_msg = US"couldn't set Cyrus SASL EXTERNAL SSF";
    sasl_done();
    return DEFER;
    }
  else
    HDEBUG(D_auth) debug_printf("Cyrus SASL set EXTERNAL SSF to %d\n", tls_in.bits);
  }
else
  HDEBUG(D_auth) debug_printf("Cyrus SASL: no TLS, no EXTERNAL SSF set\n");

/* So sasl_setprop() documents non-shorted IPv6 addresses which is incredibly
annoying; looking at cyrus-imapd-2.3.x source, the IP address is constructed
with their iptostring() function, which just wraps
getnameinfo(..., NI_NUMERICHOST|NI_NUMERICSERV), which is equivalent to the
inet_ntop which we wrap in our host_ntoa() function.

So the docs are too strict and we shouldn't worry about :: contractions. */

/* Set properties for remote and local host-ip;port */
for (int i = 0; i < 2; ++i)
  {
  struct sockaddr_storage ss;
  int (*query)(int, struct sockaddr *, socklen_t *);
  int propnum, port;
  const uschar *label;
  uschar *address, *address_port;
  const char *s_err;
  socklen_t sslen;

  if (i)
    {
    query = &getpeername;
    propnum = SASL_IPREMOTEPORT;
    label = CUS"peer";
    }
  else
    {
    query = &getsockname;
    propnum = SASL_IPLOCALPORT;
    label = CUS"local";
    }

  sslen = sizeof(ss);
  if ((rc = query(fileno(smtp_in), (struct sockaddr *) &ss, &sslen)) < 0)
    {
    HDEBUG(D_auth)
      debug_printf("Failed to get %s address information: %s\n",
          label, strerror(errno));
    break;
    }

  address = host_ntoa(-1, &ss, NULL, &port);
  address_port = string_sprintf("%s;%d", address, port);

  if ((rc = sasl_setprop(conn, propnum, address_port)) != SASL_OK)
    {
    s_err = sasl_errdetail(conn);
    HDEBUG(D_auth)
      debug_printf("Failed to set %s SASL property: [%d] %s\n",
          label, rc, s_err ? s_err : "<unknown reason>");
    break;
    }
  HDEBUG(D_auth) debug_printf("Cyrus SASL set %s hostport to: %s\n",
      label, address_port);
  }

for (rc = SASL_CONTINUE; rc == SASL_CONTINUE; )
  {
  if (firsttime)
    {
    firsttime = 0;
    HDEBUG(D_auth) debug_printf("Calling sasl_server_start(%s,\"%s\")\n", ob->server_mech, debug);
    rc = sasl_server_start(conn, CS ob->server_mech, inlen?CS input:NULL, inlen,
           (const char **)(&output), &outlen);
    }
  else
    {
    /* make sure that we have a null-terminated string */
    out2 = string_copyn(output, outlen);

    if ((rc = auth_get_data(&input, out2, outlen)) != OK)
      {
      /* we couldn't get the data, so free up the library before
       * returning whatever error we get */
      sasl_dispose(&conn);
      sasl_done();
      return rc;
      }
    inlen = Ustrlen(input);

    HDEBUG(D_auth) debug = string_copy(input);
    if (inlen)
      {
      if ((clen = b64decode(input, &clear)) < 0)
       {
       sasl_dispose(&conn);
       sasl_done();
       return BAD64;
       }
      input = clear;
      inlen = clen;
      }

    HDEBUG(D_auth) debug_printf("Calling sasl_server_step(\"%s\")\n", debug);
    rc = sasl_server_step(conn, CS input, inlen, (const char **)(&output), &outlen);
    }

  if (rc == SASL_BADPROT)
    {
    sasl_dispose(&conn);
    sasl_done();
    return UNEXPECTED;
    }
  if (rc == SASL_CONTINUE)
    continue;

  /* Get the username and copy it into $auth1 and $1. The former is now the
  preferred variable; the latter is the original variable. */

  if ((sasl_getprop(conn, SASL_USERNAME, (const void **)(&out2))) != SASL_OK)
    {
    HDEBUG(D_auth)
      debug_printf("Cyrus SASL library will not tell us the username: %s\n",
	  sasl_errstring(rc, NULL, NULL));
    log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
       "Cyrus SASL username fetch problem: %s", ablock->name, ob->server_mech,
       sasl_errstring(rc, NULL, NULL));
    sasl_dispose(&conn);
    sasl_done();
    return FAIL;
    }
  auth_vars[0] = expand_nstring[1] = string_copy(out2);
  expand_nlength[1] = Ustrlen(out2);
  expand_nmax = 1;

  switch (rc)
    {
    case SASL_FAIL: case SASL_BUFOVER: case SASL_BADMAC: case SASL_BADAUTH:
    case SASL_NOAUTHZ: case SASL_ENCRYPT: case SASL_EXPIRED:
    case SASL_DISABLED: case SASL_NOUSER:
      /* these are considered permanent failure codes */
      HDEBUG(D_auth)
	debug_printf("Cyrus SASL permanent failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
      log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
	 "Cyrus SASL permanent failure: %s", ablock->name, ob->server_mech,
	 sasl_errstring(rc, NULL, NULL));
      sasl_dispose(&conn);
      sasl_done();
      return FAIL;

    case SASL_NOMECH:
      /* this is a temporary failure, because the mechanism is not
       * available for this user. If it wasn't available at all, we
       * shouldn't have got here in the first place...
       */
      HDEBUG(D_auth)
	debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
      auth_defer_msg =
	  string_sprintf("Cyrus SASL: mechanism %s not available", ob->server_mech);
      sasl_dispose(&conn);
      sasl_done();
      return DEFER;

    case SASL_OK:
      HDEBUG(D_auth)
	debug_printf("Cyrus SASL %s authentication succeeded for %s\n",
	    ob->server_mech, auth_vars[0]);

      if ((rc = sasl_getprop(conn, SASL_SSF, (const void **)(&negotiated_ssf_ptr)))!= SASL_OK)
	{
	HDEBUG(D_auth)
	  debug_printf("Cyrus SASL library will not tell us the SSF: %s\n",
	      sasl_errstring(rc, NULL, NULL));
	log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
	    "Cyrus SASL SSF value not available: %s", ablock->name, ob->server_mech,
	    sasl_errstring(rc, NULL, NULL));
	sasl_dispose(&conn);
	sasl_done();
	return FAIL;
	}
      negotiated_ssf = *negotiated_ssf_ptr;
      HDEBUG(D_auth)
	debug_printf("Cyrus SASL %s negotiated SSF: %d\n", ob->server_mech, negotiated_ssf);
      if (negotiated_ssf > 0)
	{
	HDEBUG(D_auth)
	  debug_printf("Exim does not implement SASL wrapping (needed for SSF %d).\n", negotiated_ssf);
	log_write(0, LOG_REJECT, "%s authenticator (%s):\n  "
	    "Cyrus SASL SSF %d not supported by Exim", ablock->name, ob->server_mech, negotiated_ssf);
	sasl_dispose(&conn);
	sasl_done();
	return FAIL;
	}

      /* close down the connection, freeing up library's memory */
      sasl_dispose(&conn);
      sasl_done();

      /* Expand server_condition as an authorization check */
      return auth_check_serv_cond(ablock);

    default:
      /* Anything else is a temporary failure, and we'll let SASL print out
       * the error string for us
       */
      HDEBUG(D_auth)
	debug_printf("Cyrus SASL temporary failure %d (%s)\n", rc, sasl_errstring(rc, NULL, NULL));
      auth_defer_msg =
	  string_sprintf("Cyrus SASL: %s", sasl_errstring(rc, NULL, NULL));
      sasl_dispose(&conn);
      sasl_done();
      return DEFER;
    }
  }
/* NOTREACHED */
return 0;  /* Stop compiler complaints */
}
Пример #4
0
int
auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
{
  gss_name_t gclient = GSS_C_NO_NAME;
  gss_name_t gserver = GSS_C_NO_NAME;
  gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL;
  gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT;
  uschar *ex_server_str;
  gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER;
  gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER;
  gss_OID mech_type;
  OM_uint32 maj_stat, min_stat;
  int step, error_out, i;
  uschar *tmp1, *tmp2, *from_client;
  auth_heimdal_gssapi_options_block *ob =
    (auth_heimdal_gssapi_options_block *)(ablock->options_block);
  BOOL handled_empty_ir;
  uschar *store_reset_point;
  uschar *keytab;
  uschar sasl_config[4];
  uschar requested_qop;

  store_reset_point = store_get(0);

  HDEBUG(D_auth)
    debug_printf("heimdal: initialising auth context for %s\n", ablock->name);

  /* Construct our gss_name_t gserver describing ourselves */
  tmp1 = expand_string(ob->server_service);
  tmp2 = expand_string(ob->server_hostname);
  ex_server_str = string_sprintf("%s@%s", tmp1, tmp2);
  gbufdesc.value = (void *) ex_server_str;
  gbufdesc.length = Ustrlen(ex_server_str);
  maj_stat = gss_import_name(&min_stat,
      &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver);
  if (GSS_ERROR(maj_stat))
    return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
        "gss_import_name(%s)", CS gbufdesc.value);

  /* Use a specific keytab, if specified */
  if (ob->server_keytab) {
    keytab = expand_string(ob->server_keytab);
    maj_stat = gsskrb5_register_acceptor_identity(CCS keytab);
    if (GSS_ERROR(maj_stat))
      return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
          "registering keytab \"%s\"", keytab);
    HDEBUG(D_auth)
      debug_printf("heimdal: using keytab \"%s\"\n", keytab);
  }

  /* Acquire our credentials */
  maj_stat = gss_acquire_cred(&min_stat,
      gserver,             /* desired name */
      0,                   /* time */
      GSS_C_NULL_OID_SET,  /* desired mechs */
      GSS_C_ACCEPT,        /* cred usage */
      &gcred,              /* handle */
      NULL                 /* actual mechs */,
      NULL                 /* time rec */);
  if (GSS_ERROR(maj_stat))
    return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
        "gss_acquire_cred(%s)", ex_server_str);

  maj_stat = gss_release_name(&min_stat, &gserver);

  HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n");

  /* Loop talking to client */
  step = 0;
  from_client = initial_data;
  handled_empty_ir = FALSE;
  error_out = OK;

  /* buffer sizes: auth_get_data() uses big_buffer, which we grow per
  GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB.
  (big_buffer starts life at the MUST size of 16KB). */

  /* step values
  0: getting initial data from client to feed into GSSAPI
  1: iterating for as long as GSS_S_CONTINUE_NEEDED
  2: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client
  3: unpick final auth message from client
  4: break/finish (non-step)
  */
  while (step < 4) {
    switch (step) {
      case 0:
        if (!from_client || *from_client == '\0') {
          if (handled_empty_ir) {
            HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n");
            error_out = BAD64;
            goto ERROR_OUT;
          } else {
            HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n");
            error_out = auth_get_data(&from_client, US"", 0);
            if (error_out != OK)
              goto ERROR_OUT;
            handled_empty_ir = TRUE;
            continue;
          }
        }
        /* We should now have the opening data from the client, base64-encoded. */
        step += 1;
        HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n");
        break;

      case 1:
        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
        if (gclient) {
          maj_stat = gss_release_name(&min_stat, &gclient);
          gclient = GSS_C_NO_NAME;
        }
        maj_stat = gss_accept_sec_context(&min_stat,
            &gcontext,          /* context handle */
            gcred,              /* acceptor cred handle */
            &gbufdesc_in,       /* input from client */
            GSS_C_NO_CHANNEL_BINDINGS,  /* XXX fixme: use the channel bindings from GnuTLS */
            &gclient,           /* client identifier */
            &mech_type,         /* mechanism in use */
            &gbufdesc_out,      /* output to send to client */
            NULL,               /* return flags */
            NULL,               /* time rec */
            NULL                /* delegated cred_handle */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_accept_sec_context()");
          error_out = FAIL;
          goto ERROR_OUT;
        }
        if (&gbufdesc_out.length != 0) {
          error_out = auth_get_data(&from_client,
              gbufdesc_out.value, gbufdesc_out.length);
          if (error_out != OK)
            goto ERROR_OUT;

          gss_release_buffer(&min_stat, &gbufdesc_out);
          EmptyBuf(gbufdesc_out);
        }
        if (maj_stat == GSS_S_COMPLETE) {
          step += 1;
          HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n");
        } else {
          HDEBUG(D_auth) debug_printf("heimdal: need more data\n");
        }
        break;

      case 2:
        memset(sasl_config, 0xFF, 4);
        /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet
        0x01 No security layer
        0x02 Integrity protection
        0x04 Confidentiality protection

        The remaining three octets are the maximum buffer size for wrapped
        content. */
        sasl_config[0] = 0x01;  /* Exim does not wrap/unwrap SASL layers after auth */
        gbufdesc.value = (void *) sasl_config;
        gbufdesc.length = 4;
        maj_stat = gss_wrap(&min_stat,
            gcontext,
            0,                    /* conf_req_flag: integrity only */
            GSS_C_QOP_DEFAULT,    /* qop requested */
            &gbufdesc,            /* message to protect */
            NULL,                 /* conf_state: no confidentiality applied */
            &gbufdesc_out         /* output buffer */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_wrap(SASL state after auth)");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n");

        error_out = auth_get_data(&from_client,
            gbufdesc_out.value, gbufdesc_out.length);
        if (error_out != OK)
          goto ERROR_OUT;

        gss_release_buffer(&min_stat, &gbufdesc_out);
        EmptyBuf(gbufdesc_out);
        step += 1;
        break;

      case 3:
        gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
        maj_stat = gss_unwrap(&min_stat,
            gcontext,
            &gbufdesc_in,       /* data from client */
            &gbufdesc_out,      /* results */
            NULL,               /* conf state */
            NULL                /* qop state */
            );
        if (GSS_ERROR(maj_stat)) {
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_unwrap(final SASL message from client)");
          error_out = FAIL;
          goto ERROR_OUT;
        }
        if (gbufdesc_out.length < 4) {
          HDEBUG(D_auth)
            debug_printf("gssapi: final message too short; "
                "need flags, buf sizes and optional authzid\n");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        requested_qop = (CS gbufdesc_out.value)[0];
        if ((requested_qop & 0x01) == 0) {
          HDEBUG(D_auth)
            debug_printf("gssapi: client requested security layers (%x)\n",
                (unsigned int) requested_qop);
          error_out = FAIL;
          goto ERROR_OUT;
        }

        for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
        expand_nmax = 0;

        /* Identifiers:
        The SASL provided identifier is an unverified authzid.
        GSSAPI provides us with a verified identifier, but it might be empty
        for some clients.
        */

        /* $auth2 is authzid requested at SASL layer */
        if (gbufdesc_out.length > 4) {
          expand_nlength[2] = gbufdesc_out.length - 4;
          auth_vars[1] = expand_nstring[2] =
            string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]);
          expand_nmax = 2;
        }

        gss_release_buffer(&min_stat, &gbufdesc_out);
        EmptyBuf(gbufdesc_out);

        /* $auth1 is GSSAPI display name */
        maj_stat = gss_display_name(&min_stat,
            gclient,
            &gbufdesc_out,
            &mech_type);
        if (GSS_ERROR(maj_stat)) {
          auth_vars[1] = expand_nstring[2] = NULL;
          expand_nmax = 0;
          exim_gssapi_error_defer(NULL, maj_stat, min_stat,
              "gss_display_name(client identifier)");
          error_out = FAIL;
          goto ERROR_OUT;
        }

        expand_nlength[1] = gbufdesc_out.length;
        auth_vars[0] = expand_nstring[1] =
          string_copyn(gbufdesc_out.value, gbufdesc_out.length);

        if (expand_nmax == 0) { /* should be: authzid was empty */
          expand_nmax = 2;
          expand_nlength[2] = expand_nlength[1];
          auth_vars[1] = expand_nstring[2] = string_copyn(expand_nstring[1], expand_nlength[1]);
          HDEBUG(D_auth)
            debug_printf("heimdal SASL: empty authzid, set to dup of GSSAPI display name\n");
        }

        HDEBUG(D_auth)
          debug_printf("heimdal SASL: happy with client request\n"
             "  auth1 (verified GSSAPI display-name): \"%s\"\n"
             "  auth2 (unverified SASL requested authzid): \"%s\"\n",
             auth_vars[0], auth_vars[1]);

        step += 1;
        break;

    } /* switch */
  } /* while step */


ERROR_OUT:
  maj_stat = gss_release_cred(&min_stat, &gcred);
  if (gclient) {
    gss_release_name(&min_stat, &gclient);
    gclient = GSS_C_NO_NAME;
  }
  if (gbufdesc_out.length) {
    gss_release_buffer(&min_stat, &gbufdesc_out);
    EmptyBuf(gbufdesc_out);
  }
  if (gcontext != GSS_C_NO_CONTEXT) {
    gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER);
  }

  store_reset(store_reset_point);

  if (error_out != OK)
    return error_out;

  /* Auth succeeded, check server_condition */
  return auth_check_serv_cond(ablock);
}