aboutsummaryrefslogtreecommitdiffstats
path: root/camel/camel-sasl-digest-md5.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/camel-sasl-digest-md5.c')
-rw-r--r--camel/camel-sasl-digest-md5.c842
1 files changed, 842 insertions, 0 deletions
diff --git a/camel/camel-sasl-digest-md5.c b/camel/camel-sasl-digest-md5.c
new file mode 100644
index 0000000000..0b1a7f4432
--- /dev/null
+++ b/camel/camel-sasl-digest-md5.c
@@ -0,0 +1,842 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Jeffrey Stedfast <fejj@ximian.com>
+ *
+ * Copyright 2001 Ximian, Inc. (www.ximian.com)
+ *
+ * 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 Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+#include "camel-sasl-digest-md5.h"
+#include "camel-mime-utils.h"
+#include <e-util/md5-utils.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#define d(x)
+
+#define PARANOID(x) x
+
+CamelServiceAuthType camel_sasl_digest_md5_authtype = {
+ N_("DIGEST-MD5"),
+
+ N_("This option will connect to the server using a "
+ "secure DIGEST-MD5 password, if the server supports it."),
+
+ "DIGEST-MD5",
+ TRUE
+};
+
+static CamelSaslClass *parent_class = NULL;
+
+/* Returns the class for a CamelSaslDigestMd5 */
+#define CSCM_CLASS(so) CAMEL_SASL_DIGEST_MD5_CLASS (CAMEL_OBJECT_GET_CLASS (so))
+
+static GByteArray *digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
+
+enum {
+ STATE_AUTH,
+ STATE_FINAL
+};
+
+typedef struct {
+ char *name;
+ guint type;
+} DataType;
+
+enum {
+ DIGEST_REALM,
+ DIGEST_NONCE,
+ DIGEST_QOP,
+ DIGEST_STALE,
+ DIGEST_MAXBUF,
+ DIGEST_CHARSET,
+ DIGEST_ALGORITHM,
+ DIGEST_CIPHER,
+ DIGEST_UNKNOWN
+};
+
+static DataType digest_args[] = {
+ { "realm", DIGEST_REALM },
+ { "nonce", DIGEST_NONCE },
+ { "qop", DIGEST_QOP },
+ { "stale", DIGEST_STALE },
+ { "maxbuf", DIGEST_MAXBUF },
+ { "charset", DIGEST_CHARSET },
+ { "algorithm", DIGEST_ALGORITHM },
+ { "cipher", DIGEST_CIPHER },
+ { NULL, DIGEST_UNKNOWN }
+};
+
+#define QOP_AUTH (1<<0)
+#define QOP_AUTH_INT (1<<1)
+#define QOP_AUTH_CONF (1<<2)
+#define QOP_INVALID (1<<3)
+
+static DataType qop_types[] = {
+ { "auth", QOP_AUTH },
+ { "auth-int", QOP_AUTH_INT },
+ { "auth-conf", QOP_AUTH_CONF },
+ { NULL, QOP_INVALID }
+};
+
+#define CIPHER_DES (1<<0)
+#define CIPHER_3DES (1<<1)
+#define CIPHER_RC4 (1<<2)
+#define CIPHER_RC4_40 (1<<3)
+#define CIPHER_RC4_56 (1<<4)
+#define CIPHER_INVALID (1<<5)
+
+static DataType cipher_types[] = {
+ { "des", CIPHER_DES },
+ { "3des", CIPHER_3DES },
+ { "rc4", CIPHER_RC4 },
+ { "rc4-40", CIPHER_RC4_40 },
+ { "rc4-56", CIPHER_RC4_56 },
+ { NULL, CIPHER_INVALID }
+};
+
+struct _param {
+ char *name;
+ char *value;
+};
+
+struct _DigestChallenge {
+ GPtrArray *realms;
+ char *nonce;
+ guint qop;
+ gboolean stale;
+ gint32 maxbuf;
+ char *charset;
+ char *algorithm;
+ guint cipher;
+ GList *params;
+};
+
+struct _DigestURI {
+ char *type;
+ char *host;
+ char *name;
+};
+
+struct _DigestResponse {
+ char *username;
+ char *realm;
+ char *nonce;
+ char *cnonce;
+ char nc[9];
+ guint qop;
+ struct _DigestURI *uri;
+ char resp[33];
+ guint32 maxbuf;
+ char *charset;
+ guint cipher;
+ char *authzid;
+ char *param;
+};
+
+struct _CamelSaslDigestMd5Private {
+ struct _DigestChallenge *challenge;
+ struct _DigestResponse *response;
+ int state;
+};
+
+static void
+camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *camel_sasl_digest_md5_class)
+{
+ CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_digest_md5_class);
+
+ parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
+
+ /* virtual method overload */
+ camel_sasl_class->challenge = digest_md5_challenge;
+}
+
+static void
+camel_sasl_digest_md5_init (gpointer object, gpointer klass)
+{
+ CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (object);
+
+ sasl_digest->priv = g_new0 (struct _CamelSaslDigestMd5Private, 1);
+}
+
+static void
+camel_sasl_digest_md5_finalize (CamelObject *object)
+{
+ CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
+ struct _DigestChallenge *c = sasl->priv->challenge;
+ struct _DigestResponse *r = sasl->priv->response;
+ GList *p;
+ int i;
+
+ for (i = 0; i < c->realms->len; i++)
+ g_free (c->realms->pdata[i]);
+ g_ptr_array_free (c->realms, TRUE);
+ g_free (c->nonce);
+ g_free (c->charset);
+ g_free (c->algorithm);
+ for (p = c->params; p; p = p->next) {
+ struct _param *param = p->data;
+
+ g_free (param->name);
+ g_free (param->value);
+ g_free (param);
+ }
+ g_list_free (c->params);
+ g_free (c);
+
+ g_free (r->username);
+ g_free (r->realm);
+ g_free (r->nonce);
+ g_free (r->cnonce);
+ if (r->uri) {
+ g_free (r->uri->type);
+ g_free (r->uri->host);
+ g_free (r->uri->name);
+ }
+ g_free (r->charset);
+ g_free (r->authzid);
+ g_free (r->param);
+ g_free (r);
+
+ g_free (sasl->priv);
+}
+
+
+CamelType
+camel_sasl_digest_md5_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (camel_sasl_get_type (),
+ "CamelSaslDigestMd5",
+ sizeof (CamelSaslDigestMd5),
+ sizeof (CamelSaslDigestMd5Class),
+ (CamelObjectClassInitFunc) camel_sasl_digest_md5_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_sasl_digest_md5_init,
+ (CamelObjectFinalizeFunc) camel_sasl_digest_md5_finalize);
+ }
+
+ return type;
+}
+
+static void
+decode_lwsp (const char **in)
+{
+ const char *inptr = *in;
+
+ while (isspace (*inptr))
+ inptr++;
+
+ *in = inptr;
+}
+
+static char *
+decode_quoted_string (const char **in)
+{
+ const char *inptr = *in;
+ char *out = NULL, *outptr;
+ int outlen;
+ int c;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ const char *intmp;
+ int skip = 0;
+
+ /* first, calc length */
+ inptr++;
+ intmp = inptr;
+ while ((c = *intmp++) && c != '"') {
+ if (c == '\\' && *intmp) {
+ intmp++;
+ skip++;
+ }
+ }
+
+ outlen = intmp - inptr - skip;
+ out = outptr = g_malloc (outlen + 1);
+
+ while ((c = *inptr++) && c != '"') {
+ if (c == '\\' && *inptr) {
+ c = *inptr++;
+ }
+ *outptr++ = c;
+ }
+ *outptr = 0;
+ }
+
+ *in = inptr;
+
+ return out;
+}
+
+static char *
+decode_token (const char **in)
+{
+ const char *inptr = *in;
+ const char *start;
+
+ decode_lwsp (&inptr);
+ start = inptr;
+
+ while (*inptr && *inptr != '=' && *inptr != ',')
+ inptr++;
+
+ if (inptr > start) {
+ *in = inptr;
+ return g_strndup (start, inptr - start);
+ } else {
+ return NULL;
+ }
+}
+
+static char *
+decode_value (const char **in)
+{
+ const char *inptr = *in;
+
+ decode_lwsp (&inptr);
+ if (*inptr == '"') {
+ d(printf ("decoding quoted string token\n"));
+ return decode_quoted_string (in);
+ } else {
+ d(printf ("decoding string token\n"));
+ return decode_token (in);
+ }
+}
+
+static GList *
+parse_param_list (const char *tokens)
+{
+ GList *params = NULL;
+ struct _param *param;
+ const char *ptr;
+
+ for (ptr = tokens; ptr && *ptr; ) {
+ param = g_new0 (struct _param, 1);
+ param->name = decode_token (&ptr);
+ if (*ptr == '=') {
+ ptr++;
+ param->value = decode_value (&ptr);
+ }
+
+ params = g_list_prepend (params, param);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ return params;
+}
+
+static guint
+decode_data_type (DataType *dtype, const char *name)
+{
+ int i;
+
+ for (i = 0; dtype[i].name; i++) {
+ if (!g_strcasecmp (dtype[i].name, name))
+ break;
+ }
+
+ return dtype[i].type;
+}
+
+#define get_digest_arg(name) decode_data_type (digest_args, name)
+#define decode_qop(name) decode_data_type (qop_types, name)
+#define decode_cipher(name) decode_data_type (cipher_types, name)
+
+static const char *
+type_to_string (DataType *dtype, guint type)
+{
+ int i;
+
+ for (i = 0; dtype[i].name; i++) {
+ if (dtype[i].type == type)
+ break;
+ }
+
+ return dtype[i].name;
+}
+
+#define qop_to_string(type) type_to_string (qop_types, type)
+#define cipher_to_string(type) type_to_string (cipher_types, type)
+
+static void
+digest_abort (gboolean *have_type, gboolean *abort)
+{
+ if (*have_type)
+ *abort = TRUE;
+ *have_type = TRUE;
+}
+
+static struct _DigestChallenge *
+parse_server_challenge (const char *tokens, gboolean *abort)
+{
+ struct _DigestChallenge *challenge = NULL;
+ GList *params, *p;
+ const char *ptr;
+#ifdef PARANOID
+ gboolean got_algorithm = FALSE;
+ gboolean got_stale = FALSE;
+ gboolean got_maxbuf = FALSE;
+ gboolean got_charset = FALSE;
+#endif /* PARANOID */
+
+ params = parse_param_list (tokens);
+ if (!params) {
+ *abort = TRUE;
+ return NULL;
+ }
+
+ *abort = FALSE;
+
+ challenge = g_new0 (struct _DigestChallenge, 1);
+ challenge->realms = g_ptr_array_new ();
+ challenge->maxbuf = 65536;
+
+ for (p = params; p; p = p->next) {
+ struct _param *param = p->data;
+ int type;
+
+ type = get_digest_arg (param->name);
+ switch (type) {
+ case DIGEST_REALM:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ g_ptr_array_add (challenge->realms, token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_NONCE:
+ g_free (challenge->nonce);
+ challenge->nonce = param->value;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_QOP:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ challenge->qop |= decode_qop (token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+
+ if (challenge->qop & QOP_INVALID)
+ challenge->qop = QOP_INVALID;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_STALE:
+ PARANOID (digest_abort (&got_stale, abort));
+ if (!g_strcasecmp (param->value, "true"))
+ challenge->stale = TRUE;
+ else
+ challenge->stale = FALSE;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_MAXBUF:
+ PARANOID (digest_abort (&got_maxbuf, abort));
+ challenge->maxbuf = atoi (param->value);
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_CHARSET:
+ PARANOID (digest_abort (&got_charset, abort));
+ g_free (challenge->charset);
+ challenge->charset = param->value;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_ALGORITHM:
+ PARANOID (digest_abort (&got_algorithm, abort));
+ g_free (challenge->algorithm);
+ challenge->algorithm = param->value;
+ g_free (param->name);
+ g_free (param);
+ break;
+ case DIGEST_CIPHER:
+ for (ptr = param->value; ptr && *ptr; ) {
+ char *token;
+
+ token = decode_token (&ptr);
+ if (token)
+ challenge->cipher |= decode_cipher (token);
+
+ if (*ptr == ',')
+ ptr++;
+ }
+ if (challenge->cipher & CIPHER_INVALID)
+ challenge->cipher = CIPHER_INVALID;
+ g_free (param->value);
+ g_free (param->name);
+ g_free (param);
+ break;
+ default:
+ challenge->params = g_list_prepend (challenge->params, param);
+ break;
+ }
+ }
+
+ g_list_free (params);
+
+ return challenge;
+}
+
+static void
+digest_hex (guchar *digest, guchar hex[33])
+{
+ guchar *s, *p;
+
+ /* lowercase hexify that bad-boy... */
+ for (s = digest, p = hex; p < hex + 32; s++, p += 2)
+ sprintf (p, "%.2x", *s);
+}
+
+static void
+digest_kd (const char *k, const char *s, guchar digest[16])
+{
+ char *str;
+
+ str = g_strdup_printf ("%s:%s", k, s);
+ md5_get_digest (str, strlen (str), digest);
+ g_free (str);
+}
+
+static char *
+digest_uri_to_string (struct _DigestURI *uri)
+{
+ if (uri->name)
+ return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
+ else
+ return g_strdup_printf ("%s/%s", uri->type, uri->host);
+}
+
+static void
+compute_response (struct _DigestResponse *resp, const char *passwd, gboolean client, guchar out[33])
+{
+ guchar hex_a1[33], hex_a2[33];
+ GByteArray *buffer;
+ guchar digest[16];
+ char *buf;
+
+ /* compute A1 */
+ buf = g_strdup_printf ("%s:%s:%s", resp->username, resp->realm, passwd);
+ md5_get_digest (buf, strlen (buf), digest);
+ g_free (buf);
+ if (resp->authzid)
+ buf = g_strdup_printf (":%s:%s:%s", resp->nonce, resp->cnonce, resp->authzid);
+ else
+ buf = g_strdup_printf (":%s:%s", resp->nonce, resp->cnonce);
+ buffer = g_byte_array_new ();
+ g_byte_array_append (buffer, digest, 16);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+
+ /* now hash and hex A1 */
+ md5_get_digest (buffer->data, buffer->len, digest);
+ g_byte_array_free (buffer, TRUE);
+ digest_hex (digest, hex_a1);
+
+ /* compute A2 */
+ buffer = g_byte_array_new ();
+ if (client) {
+ g_byte_array_append (buffer, "AUTHENTICATE:", strlen ("AUTHENTICATE:"));
+ buf = digest_uri_to_string (resp->uri);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+ } else {
+ /* we are calculating the server rspauth */
+ g_byte_array_append (buffer, ":", 1);
+ buf = digest_uri_to_string (resp->uri);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+ }
+
+ if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
+ g_byte_array_append (buffer, ":00000000000000000000000000000000",
+ strlen (":00000000000000000000000000000000"));
+ /* now hash and hex A2 */
+ md5_get_digest (buffer->data, buffer->len, digest);
+ g_byte_array_free (buffer, TRUE);
+ digest_hex (digest, hex_a2);
+
+ /* compute KD */
+ buf = g_strdup_printf ("%s:%s:%s:%s:%s", resp->nonce, resp->nc, resp->cnonce,
+ qop_to_string (resp->qop), hex_a2);
+ digest_kd (hex_a1, buf, digest);
+ digest_hex (digest, out);
+ g_free (buf);
+}
+
+static struct _DigestResponse *
+generate_response (struct _DigestChallenge *challenge, struct hostent *host,
+ const char *protocol, const char *user, const char *passwd)
+{
+ struct _DigestResponse *resp;
+ struct _DigestURI *uri;
+ char *bgen, digest[16];
+
+ resp = g_new0 (struct _DigestResponse, 1);
+ resp->username = g_strdup (user);
+ /* FIXME: we should use the preferred realm */
+ if (challenge->realms)
+ resp->realm = g_strdup (challenge->realms->pdata[0]);
+ else
+ resp->realm = g_strdup ("");
+
+ resp->nonce = g_strdup (challenge->nonce);
+
+ /* generate the cnonce */
+ bgen = g_strdup_printf ("%p:%lu:%lu", resp,
+ (unsigned long) getpid (),
+ (unsigned long) time (0));
+ md5_get_digest (bgen, strlen (bgen), digest);
+ g_free (bgen);
+ /* take our recommended 64 bits of entropy */
+ resp->cnonce = base64_encode_simple (digest, 8);
+
+ /* we don't support re-auth so the nonce count is always 1 */
+ strcpy (resp->nc, "00000001");
+
+ /* choose the QOP */
+ /* FIXME: choose - probably choose "auth" ??? */
+ resp->qop = QOP_AUTH;
+
+ /* create the URI */
+ uri = g_new0 (struct _DigestURI, 1);
+ uri->type = g_strdup (protocol);
+ uri->host = g_strdup (host->h_name);
+ uri->name = NULL;
+ resp->uri = uri;
+
+ /* charsets... yay */
+ if (challenge->charset) {
+ resp->charset = NULL;
+ }
+
+ resp->cipher = CIPHER_INVALID;
+ if (resp->qop == QOP_AUTH_CONF) {
+ /* FIXME: choose a cipher */
+ resp->cipher = CIPHER_INVALID;
+ }
+
+ /* we don't really care about this... */
+ resp->authzid = NULL;
+
+ compute_response (resp, passwd, TRUE, resp->resp);
+
+ return resp;
+}
+
+static GByteArray *
+digest_response (struct _DigestResponse *resp)
+{
+ GByteArray *buffer;
+ const char *str;
+ char *buf;
+
+ buffer = g_byte_array_new ();
+ g_byte_array_append (buffer, "username=\"", 10);
+ g_byte_array_append (buffer, resp->username, strlen (resp->username));
+
+ g_byte_array_append (buffer, "\",realm=\"", 9);
+ g_byte_array_append (buffer, resp->realm, strlen (resp->realm));
+
+ g_byte_array_append (buffer, "\",nonce=\"", 9);
+ g_byte_array_append (buffer, resp->nonce, strlen (resp->nonce));
+
+ g_byte_array_append (buffer, "\",cnonce=\"", 10);
+ g_byte_array_append (buffer, resp->cnonce, strlen (resp->cnonce));
+
+ g_byte_array_append (buffer, "\",nc=", 5);
+ g_byte_array_append (buffer, resp->nc, 8);
+
+ g_byte_array_append (buffer, ",qop=\"", 6);
+ str = qop_to_string (resp->qop);
+ g_byte_array_append (buffer, str, strlen (str));
+
+ g_byte_array_append (buffer, "\",digest-uri=\"", 14);
+ buf = digest_uri_to_string (resp->uri);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+
+ g_byte_array_append (buffer, "\",response=\"", 12);
+ g_byte_array_append (buffer, resp->resp, 32);
+ g_byte_array_append (buffer, "\"", 1);
+
+ if (resp->maxbuf > 0) {
+ g_byte_array_append (buffer, ",maxbuf=", 8);
+ buf = g_strdup_printf ("%d", resp->maxbuf);
+ g_byte_array_append (buffer, buf, strlen (buf));
+ g_free (buf);
+ }
+
+ if (resp->charset) {
+ g_byte_array_append (buffer, ",charset=\"", 10);
+ g_byte_array_append (buffer, resp->charset, strlen (resp->charset));
+ g_byte_array_append (buffer, "\"", 1);
+ }
+
+ if (resp->cipher != CIPHER_INVALID) {
+ str = cipher_to_string (resp->cipher);
+ if (str) {
+ g_byte_array_append (buffer, ",cipher=\"", 9);
+ g_byte_array_append (buffer, str, strlen (str));
+ g_byte_array_append (buffer, "\"", 1);
+ }
+ }
+
+ if (resp->authzid) {
+ g_byte_array_append (buffer, ",authzid=\"", 10);
+ g_byte_array_append (buffer, resp->authzid, strlen (resp->authzid));
+ g_byte_array_append (buffer, "\"", 1);
+ }
+
+ return buffer;
+}
+
+static GByteArray *
+digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+{
+ CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
+ struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
+ struct _param *rspauth;
+ GByteArray *ret = NULL;
+ gboolean abort = FALSE;
+ struct hostent *h;
+ const char *ptr;
+ guchar out[33];
+ char *tokens;
+
+ /* Need to wait for the server */
+ if (!token)
+ return NULL;
+
+ g_return_val_if_fail (sasl->service->url->passwd != NULL, NULL);
+
+ switch (priv->state) {
+ case STATE_AUTH:
+ if (token->len > 2048) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge too long (>2048 octets)\n"));
+ return NULL;
+ }
+
+ tokens = g_strndup (token->data, token->len);
+ priv->challenge = parse_server_challenge (tokens, &abort);
+ g_free (tokens);
+ if (!priv->challenge || abort) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge invalid\n"));
+ return NULL;
+ }
+
+ if (priv->challenge->qop == QOP_INVALID) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server challenge contained invalid "
+ "\"Quality of Protection\" token\n"));
+ return NULL;
+ }
+
+ h = camel_service_gethost (sasl->service, ex);
+ priv->response = generate_response (priv->challenge, h, sasl->service_name,
+ sasl->service->url->user,
+ sasl->service->url->passwd);
+
+ ret = digest_response (priv->response);
+
+ break;
+ case STATE_FINAL:
+ if (token->len)
+ tokens = g_strndup (token->data, token->len);
+ else
+ tokens = NULL;
+
+ if (!tokens || !*tokens) {
+ g_free (tokens);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response did not contain authorization data\n"));
+ return NULL;
+ }
+
+ rspauth = g_new0 (struct _param, 1);
+
+ ptr = tokens;
+ rspauth->name = decode_token (&ptr);
+ if (*ptr == '=') {
+ ptr++;
+ rspauth->value = decode_value (&ptr);
+ }
+ g_free (tokens);
+
+ if (!rspauth->value) {
+ g_free (rspauth->name);
+ g_free (rspauth);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response contained incomplete authorization data\n"));
+ return NULL;
+ }
+
+ compute_response (priv->response, sasl->service->url->passwd, FALSE, out);
+ if (memcmp (out, rspauth->value, 32) != 0) {
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Server response does not match\n"));
+ sasl->authenticated = TRUE;
+
+ return NULL;
+ }
+
+ g_free (rspauth->name);
+ g_free (rspauth->value);
+ g_free (rspauth);
+
+ ret = g_byte_array_new ();
+
+ sasl->authenticated = TRUE;
+ default:
+ break;
+ }
+
+ priv->state++;
+
+ return ret;
+}