1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
5 * Copyright 2001 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 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 General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
32 #include "camel-sasl-digest-md5.h"
33 #include "camel-mime-utils.h"
34 #include "camel-charset-map.h"
35 #include <e-util/md5-utils.h>
36 #include <gal/util/e-iconv.h>
42 CamelServiceAuthType camel_sasl_digest_md5_authtype = {
45 N_("This option will connect to the server using a "
46 "secure DIGEST-MD5 password, if the server supports it."),
52 static CamelSaslClass *parent_class = NULL;
54 /* Returns the class for a CamelSaslDigestMd5 */
55 #define CSCM_CLASS(so) CAMEL_SASL_DIGEST_MD5_CLASS (CAMEL_OBJECT_GET_CLASS (so))
57 static GByteArray *digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
81 static DataType digest_args[] = {
82 { "realm", DIGEST_REALM },
83 { "nonce", DIGEST_NONCE },
84 { "qop", DIGEST_QOP },
85 { "stale", DIGEST_STALE },
86 { "maxbuf", DIGEST_MAXBUF },
87 { "charset", DIGEST_CHARSET },
88 { "algorithm", DIGEST_ALGORITHM },
89 { "cipher", DIGEST_CIPHER },
90 { NULL, DIGEST_UNKNOWN }
93 #define QOP_AUTH (1<<0)
94 #define QOP_AUTH_INT (1<<1)
95 #define QOP_AUTH_CONF (1<<2)
96 #define QOP_INVALID (1<<3)
98 static DataType qop_types[] = {
100 { "auth-int", QOP_AUTH_INT },
101 { "auth-conf", QOP_AUTH_CONF },
102 { NULL, QOP_INVALID }
105 #define CIPHER_DES (1<<0)
106 #define CIPHER_3DES (1<<1)
107 #define CIPHER_RC4 (1<<2)
108 #define CIPHER_RC4_40 (1<<3)
109 #define CIPHER_RC4_56 (1<<4)
110 #define CIPHER_INVALID (1<<5)
112 static DataType cipher_types[] = {
113 { "des", CIPHER_DES },
114 { "3des", CIPHER_3DES },
115 { "rc4", CIPHER_RC4 },
116 { "rc4-40", CIPHER_RC4_40 },
117 { "rc4-56", CIPHER_RC4_56 },
118 { NULL, CIPHER_INVALID }
126 struct _DigestChallenge {
144 struct _DigestResponse {
151 struct _DigestURI *uri;
160 struct _CamelSaslDigestMd5Private {
161 struct _DigestChallenge *challenge;
162 struct _DigestResponse *response;
167 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *camel_sasl_digest_md5_class)
169 CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (camel_sasl_digest_md5_class);
171 parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
173 /* virtual method overload */
174 camel_sasl_class->challenge = digest_md5_challenge;
178 camel_sasl_digest_md5_init (gpointer object, gpointer klass)
180 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (object);
182 sasl_digest->priv = g_new0 (struct _CamelSaslDigestMd5Private, 1);
186 camel_sasl_digest_md5_finalize (CamelObject *object)
188 CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
189 struct _DigestChallenge *c = sasl->priv->challenge;
190 struct _DigestResponse *r = sasl->priv->response;
194 for (i = 0; i < c->realms->len; i++)
195 g_free (c->realms->pdata[i]);
196 g_ptr_array_free (c->realms, TRUE);
199 g_free (c->algorithm);
200 for (p = c->params; p; p = p->next) {
201 struct _param *param = p->data;
203 g_free (param->name);
204 g_free (param->value);
207 g_list_free (c->params);
210 g_free (r->username);
215 g_free (r->uri->type);
216 g_free (r->uri->host);
217 g_free (r->uri->name);
229 camel_sasl_digest_md5_get_type (void)
231 static CamelType type = CAMEL_INVALID_TYPE;
233 if (type == CAMEL_INVALID_TYPE) {
234 type = camel_type_register (camel_sasl_get_type (),
235 "CamelSaslDigestMd5",
236 sizeof (CamelSaslDigestMd5),
237 sizeof (CamelSaslDigestMd5Class),
238 (CamelObjectClassInitFunc) camel_sasl_digest_md5_class_init,
240 (CamelObjectInitFunc) camel_sasl_digest_md5_init,
241 (CamelObjectFinalizeFunc) camel_sasl_digest_md5_finalize);
248 decode_lwsp (const char **in)
250 const char *inptr = *in;
252 while (isspace (*inptr))
259 decode_quoted_string (const char **in)
261 const char *inptr = *in;
262 char *out = NULL, *outptr;
266 decode_lwsp (&inptr);
271 /* first, calc length */
274 while ((c = *intmp++) && c != '"') {
275 if (c == '\\' && *intmp) {
281 outlen = intmp - inptr - skip;
282 out = outptr = g_malloc (outlen + 1);
284 while ((c = *inptr++) && c != '"') {
285 if (c == '\\' && *inptr) {
299 decode_token (const char **in)
301 const char *inptr = *in;
304 decode_lwsp (&inptr);
307 while (*inptr && *inptr != '=' && *inptr != ',')
312 return g_strndup (start, inptr - start);
319 decode_value (const char **in)
321 const char *inptr = *in;
323 decode_lwsp (&inptr);
325 d(printf ("decoding quoted string token\n"));
326 return decode_quoted_string (in);
328 d(printf ("decoding string token\n"));
329 return decode_token (in);
334 parse_param_list (const char *tokens)
336 GList *params = NULL;
337 struct _param *param;
340 for (ptr = tokens; ptr && *ptr; ) {
341 param = g_new0 (struct _param, 1);
342 param->name = decode_token (&ptr);
345 param->value = decode_value (&ptr);
348 params = g_list_prepend (params, param);
358 decode_data_type (DataType *dtype, const char *name)
362 for (i = 0; dtype[i].name; i++) {
363 if (!strcasecmp (dtype[i].name, name))
367 return dtype[i].type;
370 #define get_digest_arg(name) decode_data_type (digest_args, name)
371 #define decode_qop(name) decode_data_type (qop_types, name)
372 #define decode_cipher(name) decode_data_type (cipher_types, name)
375 type_to_string (DataType *dtype, guint type)
379 for (i = 0; dtype[i].name; i++) {
380 if (dtype[i].type == type)
384 return dtype[i].name;
387 #define qop_to_string(type) type_to_string (qop_types, type)
388 #define cipher_to_string(type) type_to_string (cipher_types, type)
391 digest_abort (gboolean *have_type, gboolean *abort)
398 static struct _DigestChallenge *
399 parse_server_challenge (const char *tokens, gboolean *abort)
401 struct _DigestChallenge *challenge = NULL;
405 gboolean got_algorithm = FALSE;
406 gboolean got_stale = FALSE;
407 gboolean got_maxbuf = FALSE;
408 gboolean got_charset = FALSE;
409 #endif /* PARANOID */
411 params = parse_param_list (tokens);
419 challenge = g_new0 (struct _DigestChallenge, 1);
420 challenge->realms = g_ptr_array_new ();
421 challenge->maxbuf = 65536;
423 for (p = params; p; p = p->next) {
424 struct _param *param = p->data;
427 type = get_digest_arg (param->name);
430 for (ptr = param->value; ptr && *ptr; ) {
433 token = decode_token (&ptr);
435 g_ptr_array_add (challenge->realms, token);
440 g_free (param->value);
441 g_free (param->name);
445 g_free (challenge->nonce);
446 challenge->nonce = param->value;
447 g_free (param->name);
451 for (ptr = param->value; ptr && *ptr; ) {
454 token = decode_token (&ptr);
456 challenge->qop |= decode_qop (token);
462 if (challenge->qop & QOP_INVALID)
463 challenge->qop = QOP_INVALID;
464 g_free (param->value);
465 g_free (param->name);
469 PARANOID (digest_abort (&got_stale, abort));
470 if (!strcasecmp (param->value, "true"))
471 challenge->stale = TRUE;
473 challenge->stale = FALSE;
474 g_free (param->value);
475 g_free (param->name);
479 PARANOID (digest_abort (&got_maxbuf, abort));
480 challenge->maxbuf = atoi (param->value);
481 g_free (param->value);
482 g_free (param->name);
486 PARANOID (digest_abort (&got_charset, abort));
487 g_free (challenge->charset);
488 if (param->value && *param->value)
489 challenge->charset = param->value;
491 challenge->charset = NULL;
492 g_free (param->name);
495 case DIGEST_ALGORITHM:
496 PARANOID (digest_abort (&got_algorithm, abort));
497 g_free (challenge->algorithm);
498 challenge->algorithm = param->value;
499 g_free (param->name);
503 for (ptr = param->value; ptr && *ptr; ) {
506 token = decode_token (&ptr);
508 challenge->cipher |= decode_cipher (token);
513 if (challenge->cipher & CIPHER_INVALID)
514 challenge->cipher = CIPHER_INVALID;
515 g_free (param->value);
516 g_free (param->name);
520 challenge->params = g_list_prepend (challenge->params, param);
525 g_list_free (params);
531 digest_hex (guchar *digest, guchar hex[33])
535 /* lowercase hexify that bad-boy... */
536 for (s = digest, p = hex; p < hex + 32; s++, p += 2)
537 sprintf (p, "%.2x", *s);
541 digest_uri_to_string (struct _DigestURI *uri)
544 return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
546 return g_strdup_printf ("%s/%s", uri->type, uri->host);
550 compute_response (struct _DigestResponse *resp, const char *passwd, gboolean client, guchar out[33])
552 guchar hex_a1[33], hex_a2[33];
559 md5_update (&ctx, resp->username, strlen (resp->username));
560 md5_update (&ctx, ":", 1);
561 md5_update (&ctx, resp->realm, strlen (resp->realm));
562 md5_update (&ctx, ":", 1);
563 md5_update (&ctx, passwd, strlen (passwd));
564 md5_final (&ctx, digest);
567 md5_update (&ctx, digest, 16);
568 md5_update (&ctx, ":", 1);
569 md5_update (&ctx, resp->nonce, strlen (resp->nonce));
570 md5_update (&ctx, ":", 1);
571 md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
573 md5_update (&ctx, ":", 1);
574 md5_update (&ctx, resp->authzid, strlen (resp->authzid));
578 md5_final (&ctx, digest);
579 digest_hex (digest, hex_a1);
584 /* we are calculating the client response */
585 md5_update (&ctx, "AUTHENTICATE:", strlen ("AUTHENTICATE:"));
587 /* we are calculating the server rspauth */
588 md5_update (&ctx, ":", 1);
591 buf = digest_uri_to_string (resp->uri);
592 md5_update (&ctx, buf, strlen (buf));
595 if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
596 md5_update (&ctx, ":00000000000000000000000000000000", 33);
599 md5_final (&ctx, digest);
600 digest_hex (digest, hex_a2);
604 md5_update (&ctx, hex_a1, 32);
605 md5_update (&ctx, ":", 1);
606 md5_update (&ctx, resp->nonce, strlen (resp->nonce));
607 md5_update (&ctx, ":", 1);
608 md5_update (&ctx, resp->nc, 8);
609 md5_update (&ctx, ":", 1);
610 md5_update (&ctx, resp->cnonce, strlen (resp->cnonce));
611 md5_update (&ctx, ":", 1);
612 md5_update (&ctx, qop_to_string (resp->qop), strlen (qop_to_string (resp->qop)));
613 md5_update (&ctx, ":", 1);
614 md5_update (&ctx, hex_a2, 32);
615 md5_final (&ctx, digest);
617 digest_hex (digest, out);
620 static struct _DigestResponse *
621 generate_response (struct _DigestChallenge *challenge, struct hostent *host,
622 const char *protocol, const char *user, const char *passwd)
624 struct _DigestResponse *resp;
625 struct _DigestURI *uri;
626 char *bgen, digest[16];
628 resp = g_new0 (struct _DigestResponse, 1);
629 resp->username = g_strdup (user);
630 /* FIXME: we should use the preferred realm */
631 if (challenge->realms && challenge->realms->len > 0)
632 resp->realm = g_strdup (challenge->realms->pdata[0]);
634 resp->realm = g_strdup ("");
636 resp->nonce = g_strdup (challenge->nonce);
638 /* generate the cnonce */
639 bgen = g_strdup_printf ("%p:%lu:%lu", resp,
640 (unsigned long) getpid (),
641 (unsigned long) time (0));
642 md5_get_digest (bgen, strlen (bgen), digest);
644 /* take our recommended 64 bits of entropy */
645 resp->cnonce = base64_encode_simple (digest, 8);
647 /* we don't support re-auth so the nonce count is always 1 */
648 strcpy (resp->nc, "00000001");
651 /* FIXME: choose - probably choose "auth" ??? */
652 resp->qop = QOP_AUTH;
655 uri = g_new0 (struct _DigestURI, 1);
656 uri->type = g_strdup (protocol);
657 uri->host = g_strdup (host->h_name);
661 /* charsets... yay */
662 if (challenge->charset) {
663 /* I believe that this is only ever allowed to be
664 * UTF-8. We strdup the charset specified by the
665 * challenge anyway, just in case it's not UTF-8.
667 resp->charset = g_strdup (challenge->charset);
670 resp->cipher = CIPHER_INVALID;
671 if (resp->qop == QOP_AUTH_CONF) {
672 /* FIXME: choose a cipher? */
673 resp->cipher = CIPHER_INVALID;
676 /* we don't really care about this... */
677 resp->authzid = NULL;
679 compute_response (resp, passwd, TRUE, resp->resp);
685 digest_response (struct _DigestResponse *resp)
691 buffer = g_byte_array_new ();
692 g_byte_array_append (buffer, "username=\"", 10);
694 /* Encode the username using the requested charset */
695 char *username, *outbuf;
701 charset = e_iconv_locale_charset();
703 charset = "iso-8859-1";
705 cd = e_iconv_open (resp->charset, charset);
707 len = strlen (resp->username);
708 outlen = 2 * len; /* plenty of space */
710 outbuf = username = g_malloc0 (outlen + 1);
711 buf = resp->username;
712 if (cd == (iconv_t) -1 || e_iconv (cd, &buf, &len, &outbuf, &outlen) == (size_t) -1) {
713 /* We can't convert to UTF-8 - pretend we never got a charset param? */
714 g_free (resp->charset);
715 resp->charset = NULL;
717 /* Set the username to the non-UTF-8 version */
719 username = g_strdup (resp->username);
722 if (cd != (iconv_t) -1)
725 g_byte_array_append (buffer, username, strlen (username));
728 g_byte_array_append (buffer, resp->username, strlen (resp->username));
731 g_byte_array_append (buffer, "\",realm=\"", 9);
732 g_byte_array_append (buffer, resp->realm, strlen (resp->realm));
734 g_byte_array_append (buffer, "\",nonce=\"", 9);
735 g_byte_array_append (buffer, resp->nonce, strlen (resp->nonce));
737 g_byte_array_append (buffer, "\",cnonce=\"", 10);
738 g_byte_array_append (buffer, resp->cnonce, strlen (resp->cnonce));
740 g_byte_array_append (buffer, "\",nc=", 5);
741 g_byte_array_append (buffer, resp->nc, 8);
743 g_byte_array_append (buffer, ",qop=\"", 6);
744 str = qop_to_string (resp->qop);
745 g_byte_array_append (buffer, str, strlen (str));
747 g_byte_array_append (buffer, "\",digest-uri=\"", 14);
748 buf = digest_uri_to_string (resp->uri);
749 g_byte_array_append (buffer, buf, strlen (buf));
752 g_byte_array_append (buffer, "\",response=\"", 12);
753 g_byte_array_append (buffer, resp->resp, 32);
754 g_byte_array_append (buffer, "\"", 1);
756 if (resp->maxbuf > 0) {
757 g_byte_array_append (buffer, ",maxbuf=", 8);
758 buf = g_strdup_printf ("%d", resp->maxbuf);
759 g_byte_array_append (buffer, buf, strlen (buf));
764 g_byte_array_append (buffer, ",charset=\"", 10);
765 g_byte_array_append (buffer, resp->charset, strlen (resp->charset));
766 g_byte_array_append (buffer, "\"", 1);
769 if (resp->cipher != CIPHER_INVALID) {
770 str = cipher_to_string (resp->cipher);
772 g_byte_array_append (buffer, ",cipher=\"", 9);
773 g_byte_array_append (buffer, str, strlen (str));
774 g_byte_array_append (buffer, "\"", 1);
779 g_byte_array_append (buffer, ",authzid=\"", 10);
780 g_byte_array_append (buffer, resp->authzid, strlen (resp->authzid));
781 g_byte_array_append (buffer, "\"", 1);
788 digest_md5_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
790 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
791 struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
792 struct _param *rspauth;
793 GByteArray *ret = NULL;
794 gboolean abort = FALSE;
800 /* Need to wait for the server */
804 g_return_val_if_fail (sasl->service->url->passwd != NULL, NULL);
806 switch (priv->state) {
808 if (token->len > 2048) {
809 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
810 _("Server challenge too long (>2048 octets)\n"));
814 tokens = g_strndup (token->data, token->len);
815 priv->challenge = parse_server_challenge (tokens, &abort);
817 if (!priv->challenge || abort) {
818 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
819 _("Server challenge invalid\n"));
823 if (priv->challenge->qop == QOP_INVALID) {
824 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
825 _("Server challenge contained invalid "
826 "\"Quality of Protection\" token\n"));
830 h = camel_service_gethost (sasl->service, ex);
831 priv->response = generate_response (priv->challenge, h, sasl->service_name,
832 sasl->service->url->user,
833 sasl->service->url->passwd);
835 ret = digest_response (priv->response);
840 tokens = g_strndup (token->data, token->len);
844 if (!tokens || !*tokens) {
846 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
847 _("Server response did not contain authorization data\n"));
851 rspauth = g_new0 (struct _param, 1);
854 rspauth->name = decode_token (&ptr);
857 rspauth->value = decode_value (&ptr);
861 if (!rspauth->value) {
862 g_free (rspauth->name);
864 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
865 _("Server response contained incomplete authorization data\n"));
869 compute_response (priv->response, sasl->service->url->passwd, FALSE, out);
870 if (memcmp (out, rspauth->value, 32) != 0) {
871 g_free (rspauth->name);
872 g_free (rspauth->value);
874 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
875 _("Server response does not match\n"));
876 sasl->authenticated = TRUE;
881 g_free (rspauth->name);
882 g_free (rspauth->value);
885 ret = g_byte_array_new ();
887 sasl->authenticated = TRUE;