1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
5 * Copyright 2001-2003 Ximian, Inc. (www.ximian.com)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
33 #include <glib/gi18n-lib.h>
35 #include <libedataserver/e-iconv.h>
36 #include <libedataserver/md5-utils.h>
38 #include "camel-charset-map.h"
39 #include "camel-mime-utils.h"
40 #include "camel-net-utils.h"
41 #include "camel-sasl-digest-md5.h"
47 /* Implements rfc2831 */
49 CamelServiceAuthType camel_sasl_digest_md5_authtype = {
52 N_("This option will connect to the server using a "
53 "secure DIGEST-MD5 password, if the server supports it."),
59 static CamelSaslClass *parent_class = NULL;
61 /* Returns the class for a CamelSaslDigestMd5 */
62 #define CSCM_CLASS(so) CAMEL_SASL_DIGEST_MD5_CLASS (CAMEL_OBJECT_GET_CLASS (so))
64 static GByteArray *digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
88 static DataType digest_args[] = {
89 { "realm", DIGEST_REALM },
90 { "nonce", DIGEST_NONCE },
91 { "qop", DIGEST_QOP },
92 { "stale", DIGEST_STALE },
93 { "maxbuf", DIGEST_MAXBUF },
94 { "charset", DIGEST_CHARSET },
95 { "algorithm", DIGEST_ALGORITHM },
96 { "cipher", DIGEST_CIPHER },
97 { NULL, DIGEST_UNKNOWN }
100 #define QOP_AUTH (1<<0)
101 #define QOP_AUTH_INT (1<<1)
102 #define QOP_AUTH_CONF (1<<2)
103 #define QOP_INVALID (1<<3)
105 static DataType qop_types[] = {
106 { "auth", QOP_AUTH },
107 { "auth-int", QOP_AUTH_INT },
108 { "auth-conf", QOP_AUTH_CONF },
109 { NULL, QOP_INVALID }
112 #define CIPHER_DES (1<<0)
113 #define CIPHER_3DES (1<<1)
114 #define CIPHER_RC4 (1<<2)
115 #define CIPHER_RC4_40 (1<<3)
116 #define CIPHER_RC4_56 (1<<4)
117 #define CIPHER_INVALID (1<<5)
119 static DataType cipher_types[] = {
120 { "des", CIPHER_DES },
121 { "3des", CIPHER_3DES },
122 { "rc4", CIPHER_RC4 },
123 { "rc4-40", CIPHER_RC4_40 },
124 { "rc4-56", CIPHER_RC4_56 },
125 { NULL, CIPHER_INVALID }
133 struct _DigestChallenge {
151 struct _DigestResponse {
158 struct _DigestURI *uri;
167 struct _CamelSaslDigestMd5Private {
168 struct _DigestChallenge *challenge;
169 struct _DigestResponse *response;
174 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *camel_sasl_digest_md5_class)
176 CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_digest_md5_class);
178 parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
180 /* virtual method overload */
181 camel_sasl_class->challenge = digest_md5_challenge;
185 camel_sasl_digest_md5_init (gpointer object, gpointer klass)
187 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (object);
189 sasl_digest->priv = g_new0 (struct _CamelSaslDigestMd5Private, 1);
193 camel_sasl_digest_md5_finalize (CamelObject *object)
195 CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
196 struct _DigestChallenge *c = sasl->priv->challenge;
197 struct _DigestResponse *r = sasl->priv->response;
202 for (i = 0; i < c->realms->len; i++)
203 g_free (c->realms->pdata[i]);
204 g_ptr_array_free (c->realms, TRUE);
208 g_free (c->algorithm);
209 for (p = c->params; p; p = p->next) {
210 struct _param *param = p->data;
212 g_free (param->name);
213 g_free (param->value);
216 g_list_free (c->params);
221 g_free (r->username);
226 g_free (r->uri->type);
227 g_free (r->uri->host);
228 g_free (r->uri->name);
241 camel_sasl_digest_md5_get_type (void)
243 static CamelType type = CAMEL_INVALID_TYPE;
245 if (type == CAMEL_INVALID_TYPE) {
246 type = camel_type_register (camel_sasl_get_type (),
247 "CamelSaslDigestMd5",
248 sizeof (CamelSaslDigestMd5),
249 sizeof (CamelSaslDigestMd5Class),
250 (CamelObjectClassInitFunc) camel_sasl_digest_md5_class_init,
252 (CamelObjectInitFunc) camel_sasl_digest_md5_init,
253 (CamelObjectFinalizeFunc) camel_sasl_digest_md5_finalize);
260 decode_lwsp (const char **in)
262 const char *inptr = *in;
264 while (isspace (*inptr))
271 decode_quoted_string (const char **in)
273 const char *inptr = *in;
274 char *out = NULL, *outptr;
278 decode_lwsp (&inptr);
283 /* first, calc length */
286 while ((c = *intmp++) && c != '"') {
287 if (c == '\\' && *intmp) {
293 outlen = intmp - inptr - skip;
294 out = outptr = g_malloc (outlen + 1);
296 while ((c = *inptr++) && c != '"') {
297 if (c == '\\' && *inptr) {
311 decode_token (const char **in)
313 const char *inptr = *in;
316 decode_lwsp (&inptr);
319 while (*inptr && *inptr != '=' && *inptr != ',')
324 return g_strndup (start, inptr - start);
331 decode_value (const char **in)
333 const char *inptr = *in;
335 decode_lwsp (&inptr);
337 d(printf ("decoding quoted string token\n"));
338 return decode_quoted_string (in);
340 d(printf ("decoding string token\n"));
341 return decode_token (in);
346 parse_param_list (const char *tokens)
348 GList *params = NULL;
349 struct _param *param;
352 for (ptr = tokens; ptr && *ptr; ) {
353 param = g_new0 (struct _param, 1);
354 param->name = decode_token (&ptr);
357 param->value = decode_value (&ptr);
360 params = g_list_prepend (params, param);
370 decode_data_type (DataType *dtype, const char *name)
374 for (i = 0; dtype[i].name; i++) {
375 if (!g_ascii_strcasecmp (dtype[i].name, name))
379 return dtype[i].type;
382 #define get_digest_arg(name) decode_data_type (digest_args, name)
383 #define decode_qop(name) decode_data_type (qop_types, name)
384 #define decode_cipher(name) decode_data_type (cipher_types, name)
387 type_to_string (DataType *dtype, guint type)
391 for (i = 0; dtype[i].name; i++) {
392 if (dtype[i].type == type)
396 return dtype[i].name;
399 #define qop_to_string(type) type_to_string (qop_types, type)
400 #define cipher_to_string(type) type_to_string (cipher_types, type)
403 digest_abort (gboolean *have_type, gboolean *abort)
410 static struct _DigestChallenge *
411 parse_server_challenge (const char *tokens, gboolean *abort)
413 struct _DigestChallenge *challenge = NULL;
417 gboolean got_algorithm = FALSE;
418 gboolean got_stale = FALSE;
419 gboolean got_maxbuf = FALSE;
420 gboolean got_charset = FALSE;
421 #endif /* PARANOID */
423 params = parse_param_list (tokens);
431 challenge = g_new0 (struct _DigestChallenge, 1);
432 challenge->realms = g_ptr_array_new ();
433 challenge->maxbuf = 65536;
435 for (p = params; p; p = p->next) {
436 struct _param *param = p->data;
439 type = get_digest_arg (param->name);
442 for (ptr = param->value; ptr && *ptr; ) {
445 token = decode_token (&ptr);
447 g_ptr_array_add (challenge->realms, token);
452 g_free (param->value);
453 g_free (param->name);
457 g_free (challenge->nonce);
458 challenge->nonce = param->value;
459 g_free (param->name);
463 for (ptr = param->value; ptr && *ptr; ) {
466 token = decode_token (&ptr);
468 challenge->qop |= decode_qop (token);
474 if (challenge->qop & QOP_INVALID)
475 challenge->qop = QOP_INVALID;
476 g_free (param->value);
477 g_free (param->name);
481 PARANOID (digest_abort (&got_stale, abort));
482 if (!g_ascii_strcasecmp (param->value, "true"))
483 challenge->stale = TRUE;
485 challenge->stale = FALSE;
486 g_free (param->value);
487 g_free (param->name);
491 PARANOID (digest_abort (&got_maxbuf, abort));
492 challenge->maxbuf = atoi (param->value);
493 g_free (param->value);
494 g_free (param->name);
498 PARANOID (digest_abort (&got_charset, abort));
499 g_free (challenge->charset);
500 if (param->value && *param->value)
501 challenge->charset = param->value;
503 challenge->charset = NULL;
504 g_free (param->name);
507 case DIGEST_ALGORITHM:
508 PARANOID (digest_abort (&got_algorithm, abort));
509 g_free (challenge->algorithm);
510 challenge->algorithm = param->value;
511 g_free (param->name);
515 for (ptr = param->value; ptr && *ptr; ) {
518 token = decode_token (&ptr);
520 challenge->cipher |= decode_cipher (token);
525 if (challenge->cipher & CIPHER_INVALID)
526 challenge->cipher = CIPHER_INVALID;
527 g_free (param->value);
528 g_free (param->name);
532 challenge->params = g_list_prepend (challenge->params, param);
537 g_list_free (params);
543 digest_hex (guchar *digest, guchar hex[33])
547 /* lowercase hexify that bad-boy... */
548 for (s = digest, p = hex; p < hex + 32; s++, p += 2)
549 sprintf (p, "%.2x", *s);
553 digest_uri_to_string (struct _DigestURI *uri)
556 return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
558 return g_strdup_printf ("%s/%s", uri->type, uri->host);
562 compute_response (struct _DigestResponse *resp, const char *passwd, gboolean client, guchar out[33])
564 guchar hex_a1[33], hex_a2[33];
571 md5_update (&ctx, resp->username, strlen (resp->username));
572 md5_update (&ctx, ":", 1);
573 md5_update (&ctx, resp->realm, strlen (resp->realm));
574 md5_update (&ctx, ":", 1);
575 md5_update (&ctx, passwd, strlen (passwd));
576 md5_final (&ctx, digest);
579 md5_update (&ctx, digest, 16);
580 md5_update (&ctx, ":", 1);
581 md5_update (&ctx, resp->nonce, strlen (resp->nonce));
582 md5_update (&ctx, ":", 1);
583 md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
585 md5_update (&ctx, ":", 1);
586 md5_update (&ctx, resp->authzid, strlen (resp->authzid));
590 md5_final (&ctx, digest);
591 digest_hex (digest, hex_a1);
596 /* we are calculating the client response */
597 md5_update (&ctx, "AUTHENTICATE:", strlen ("AUTHENTICATE:"));
599 /* we are calculating the server rspauth */
600 md5_update (&ctx, ":", 1);
603 buf = digest_uri_to_string (resp->uri);
604 md5_update (&ctx, buf, strlen (buf));
607 if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
608 md5_update (&ctx, ":00000000000000000000000000000000", 33);
611 md5_final (&ctx, digest);
612 digest_hex (digest, hex_a2);
616 md5_update (&ctx, hex_a1, 32);
617 md5_update (&ctx, ":", 1);
618 md5_update (&ctx, resp->nonce, strlen (resp->nonce));
619 md5_update (&ctx, ":", 1);
620 md5_update (&ctx, resp->nc, 8);
621 md5_update (&ctx, ":", 1);
622 md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
623 md5_update (&ctx, ":", 1);
624 md5_update (&ctx, qop_to_string (resp->qop), strlen (qop_to_string (resp->qop)));
625 md5_update (&ctx, ":", 1);
626 md5_update (&ctx, hex_a2, 32);
627 md5_final (&ctx, digest);
629 digest_hex (digest, out);
632 static struct _DigestResponse *
633 generate_response (struct _DigestChallenge *challenge, const char *host,
634 const char *protocol, const char *user, const char *passwd)
636 struct _DigestResponse *resp;
637 struct _DigestURI *uri;
638 char *bgen, digest[16];
640 resp = g_new0 (struct _DigestResponse, 1);
641 resp->username = g_strdup (user);
642 /* FIXME: we should use the preferred realm */
643 if (challenge->realms && challenge->realms->len > 0)
644 resp->realm = g_strdup (challenge->realms->pdata[0]);
646 resp->realm = g_strdup ("");
648 resp->nonce = g_strdup (challenge->nonce);
650 /* generate the cnonce */
651 bgen = g_strdup_printf ("%p:%lu:%lu", resp,
652 (unsigned long) getpid (),
653 (unsigned long) time (0));
654 md5_get_digest (bgen, strlen (bgen), digest);
656 /* take our recommended 64 bits of entropy */
657 resp->cnonce = camel_base64_encode_simple (digest, 8);
659 /* we don't support re-auth so the nonce count is always 1 */
660 strcpy (resp->nc, "00000001");
663 /* FIXME: choose - probably choose "auth" ??? */
664 resp->qop = QOP_AUTH;
667 uri = g_new0 (struct _DigestURI, 1);
668 uri->type = g_strdup (protocol);
669 uri->host = g_strdup (host);
673 /* charsets... yay */
674 if (challenge->charset) {
675 /* I believe that this is only ever allowed to be
676 * UTF-8. We strdup the charset specified by the
677 * challenge anyway, just in case it's not UTF-8.
679 resp->charset = g_strdup (challenge->charset);
682 resp->cipher = CIPHER_INVALID;
683 if (resp->qop == QOP_AUTH_CONF) {
684 /* FIXME: choose a cipher? */
685 resp->cipher = CIPHER_INVALID;
688 /* we don't really care about this... */
689 resp->authzid = NULL;
691 compute_response (resp, passwd, TRUE, resp->resp);
697 digest_response (struct _DigestResponse *resp)
703 buffer = g_byte_array_new ();
704 g_byte_array_append (buffer, "username=\"", 10);
706 /* Encode the username using the requested charset */
707 char *username, *outbuf;
713 charset = e_iconv_locale_charset ();
715 charset = "iso-8859-1";
717 cd = e_iconv_open (resp->charset, charset);
719 len = strlen (resp->username);
720 outlen = 2 * len; /* plenty of space */
722 outbuf = username = g_malloc0 (outlen + 1);
723 inbuf = resp->username;
724 if (cd == (iconv_t) -1 || e_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (size_t) -1) {
725 /* We can't convert to UTF-8 - pretend we never got a charset param? */
726 g_free (resp->charset);
727 resp->charset = NULL;
729 /* Set the username to the non-UTF-8 version */
731 username = g_strdup (resp->username);
734 if (cd != (iconv_t) -1)
737 g_byte_array_append (buffer, username, strlen (username));
740 g_byte_array_append (buffer, resp->username, strlen (resp->username));
743 g_byte_array_append (buffer, "\",realm=\"", 9);
744 g_byte_array_append (buffer, resp->realm, strlen (resp->realm));
746 g_byte_array_append (buffer, "\",nonce=\"", 9);
747 g_byte_array_append (buffer, resp->nonce, strlen (resp->nonce));
749 g_byte_array_append (buffer, "\",cnonce=\"", 10);
750 g_byte_array_append (buffer, resp->cnonce, strlen (resp->cnonce));
752 g_byte_array_append (buffer, "\",nc=", 5);
753 g_byte_array_append (buffer, resp->nc, 8);
755 g_byte_array_append (buffer, ",qop=", 5);
756 str = qop_to_string (resp->qop);
757 g_byte_array_append (buffer, str, strlen (str));
759 g_byte_array_append (buffer, ",digest-uri=\"", 13);
760 buf = digest_uri_to_string (resp->uri);
761 g_byte_array_append (buffer, buf, strlen (buf));
764 g_byte_array_append (buffer, "\",response=", 11);
765 g_byte_array_append (buffer, resp->resp, 32);
767 if (resp->maxbuf > 0) {
768 g_byte_array_append (buffer, ",maxbuf=", 8);
769 buf = g_strdup_printf ("%u", resp->maxbuf);
770 g_byte_array_append (buffer, buf, strlen (buf));
775 g_byte_array_append (buffer, ",charset=", 9);
776 g_byte_array_append (buffer, resp->charset, strlen (resp->charset));
779 if (resp->cipher != CIPHER_INVALID) {
780 str = cipher_to_string (resp->cipher);
782 g_byte_array_append (buffer, ",cipher=\"", 9);
783 g_byte_array_append (buffer, str, strlen (str));
784 g_byte_array_append (buffer, "\"", 1);
789 g_byte_array_append (buffer, ",authzid=\"", 10);
790 g_byte_array_append (buffer, resp->authzid, strlen (resp->authzid));
791 g_byte_array_append (buffer, "\"", 1);
798 digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
800 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
801 struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
802 struct _param *rspauth;
803 GByteArray *ret = NULL;
804 gboolean abort = FALSE;
808 struct addrinfo *ai, hints;
810 /* Need to wait for the server */
814 g_return_val_if_fail (sasl->service->url->passwd != NULL, NULL);
816 switch (priv->state) {
818 if (token->len > 2048) {
819 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
820 _("Server challenge too long (>2048 octets)"));
824 tokens = g_strndup (token->data, token->len);
825 priv->challenge = parse_server_challenge (tokens, &abort);
827 if (!priv->challenge || abort) {
828 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
829 _("Server challenge invalid\n"));
833 if (priv->challenge->qop == QOP_INVALID) {
834 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
835 _("Server challenge contained invalid "
836 "\"Quality of Protection\" token"));
840 memset(&hints, 0, sizeof(hints));
841 hints.ai_flags = AI_CANONNAME;
842 ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, NULL);
843 if (ai && ai->ai_canonname)
844 ptr = ai->ai_canonname;
846 ptr = "localhost.localdomain";
848 priv->response = generate_response (priv->challenge, ptr, sasl->service_name,
849 sasl->service->url->user,
850 sasl->service->url->passwd);
852 camel_freeaddrinfo(ai);
853 ret = digest_response (priv->response);
858 tokens = g_strndup (token->data, token->len);
862 if (!tokens || !*tokens) {
864 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
865 _("Server response did not contain authorization data"));
869 rspauth = g_new0 (struct _param, 1);
872 rspauth->name = decode_token (&ptr);
875 rspauth->value = decode_value (&ptr);
879 if (!rspauth->value) {
880 g_free (rspauth->name);
882 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
883 _("Server response contained incomplete authorization data"));
887 compute_response (priv->response, sasl->service->url->passwd, FALSE, out);
888 if (memcmp (out, rspauth->value, 32) != 0) {
889 g_free (rspauth->name);
890 g_free (rspauth->value);
892 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
893 _("Server response does not match"));
894 sasl->authenticated = TRUE;
899 g_free (rspauth->name);
900 g_free (rspauth->value);
903 ret = g_byte_array_new ();
905 sasl->authenticated = TRUE;