1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.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.
32 #include <glib/gi18n-lib.h>
34 #include "camel-charset-map.h"
35 #include "camel-iconv.h"
36 #include "camel-mime-utils.h"
37 #include "camel-net-utils.h"
38 #include "camel-network-settings.h"
46 #include "camel-sasl-digest-md5.h"
52 #define CAMEL_SASL_DIGEST_MD5_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE \
54 ((obj), CAMEL_TYPE_SASL_DIGEST_MD5, CamelSaslDigestMd5Private))
56 /* Implements rfc2831 */
58 static CamelServiceAuthType sasl_digest_md5_auth_type = {
61 N_("This option will connect to the server using a "
62 "secure DIGEST-MD5 password, if the server supports it."),
90 static DataType digest_args[] = {
91 { "realm", DIGEST_REALM },
92 { "nonce", DIGEST_NONCE },
93 { "qop", DIGEST_QOP },
94 { "stale", DIGEST_STALE },
95 { "maxbuf", DIGEST_MAXBUF },
96 { "charset", DIGEST_CHARSET },
97 { "algorithm", DIGEST_ALGORITHM },
98 { "cipher", DIGEST_CIPHER },
99 { NULL, DIGEST_UNKNOWN }
102 #define QOP_AUTH (1 << 0)
103 #define QOP_AUTH_INT (1 << 1)
104 #define QOP_AUTH_CONF (1 << 2)
105 #define QOP_INVALID (1 << 3)
107 static DataType qop_types[] = {
108 { "auth", QOP_AUTH },
109 { "auth-int", QOP_AUTH_INT },
110 { "auth-conf", QOP_AUTH_CONF },
111 { NULL, QOP_INVALID }
114 #define CIPHER_DES (1 << 0)
115 #define CIPHER_3DES (1 << 1)
116 #define CIPHER_RC4 (1 << 2)
117 #define CIPHER_RC4_40 (1 << 3)
118 #define CIPHER_RC4_56 (1 << 4)
119 #define CIPHER_INVALID (1 << 5)
121 static DataType cipher_types[] = {
122 { "des", CIPHER_DES },
123 { "3des", CIPHER_3DES },
124 { "rc4", CIPHER_RC4 },
125 { "rc4-40", CIPHER_RC4_40 },
126 { "rc4-56", CIPHER_RC4_56 },
127 { NULL, CIPHER_INVALID }
135 struct _DigestChallenge {
153 struct _DigestResponse {
160 struct _DigestURI *uri;
169 struct _CamelSaslDigestMd5Private {
170 struct _DigestChallenge *challenge;
171 struct _DigestResponse *response;
175 G_DEFINE_TYPE (CamelSaslDigestMd5, camel_sasl_digest_md5, CAMEL_TYPE_SASL)
178 decode_lwsp (const gchar **in)
180 const gchar *inptr = *in;
182 while (isspace (*inptr))
189 decode_quoted_string (const gchar **in)
191 const gchar *inptr = *in;
192 gchar *out = NULL, *outptr;
196 decode_lwsp (&inptr);
201 /* first, calc length */
204 while ((c = *intmp++) && c != '"') {
205 if (c == '\\' && *intmp) {
211 outlen = intmp - inptr - skip;
212 out = outptr = g_malloc (outlen + 1);
214 while ((c = *inptr++) && c != '"') {
215 if (c == '\\' && *inptr) {
229 decode_token (const gchar **in)
231 const gchar *inptr = *in;
234 decode_lwsp (&inptr);
237 while (*inptr && *inptr != '=' && *inptr != ',')
242 return g_strndup (start, inptr - start);
249 decode_value (const gchar **in)
251 const gchar *inptr = *in;
253 decode_lwsp (&inptr);
255 d(printf ("decoding quoted string token\n"));
256 return decode_quoted_string (in);
258 d(printf ("decoding string token\n"));
259 return decode_token (in);
264 parse_param_list (const gchar *tokens)
266 GList *params = NULL;
267 struct _param *param;
270 for (ptr = tokens; ptr && *ptr; ) {
271 param = g_new0 (struct _param, 1);
272 param->name = decode_token (&ptr);
275 param->value = decode_value (&ptr);
278 params = g_list_prepend (params, param);
288 decode_data_type (DataType *dtype,
293 for (i = 0; dtype[i].name; i++) {
294 if (!g_ascii_strcasecmp (dtype[i].name, name))
298 return dtype[i].type;
301 #define get_digest_arg(name) decode_data_type (digest_args, name)
302 #define decode_qop(name) decode_data_type (qop_types, name)
303 #define decode_cipher(name) decode_data_type (cipher_types, name)
306 type_to_string (DataType *dtype,
311 for (i = 0; dtype[i].name; i++) {
312 if (dtype[i].type == type)
316 return dtype[i].name;
319 #define qop_to_string(type) type_to_string (qop_types, type)
320 #define cipher_to_string(type) type_to_string (cipher_types, type)
323 digest_abort (gboolean *have_type,
331 static struct _DigestChallenge *
332 parse_server_challenge (const gchar *tokens,
335 struct _DigestChallenge *challenge = NULL;
339 gboolean got_algorithm = FALSE;
340 gboolean got_stale = FALSE;
341 gboolean got_maxbuf = FALSE;
342 gboolean got_charset = FALSE;
343 #endif /* PARANOID */
345 params = parse_param_list (tokens);
353 challenge = g_new0 (struct _DigestChallenge, 1);
354 challenge->realms = g_ptr_array_new ();
355 challenge->maxbuf = 65536;
357 for (p = params; p; p = p->next) {
358 struct _param *param = p->data;
361 type = get_digest_arg (param->name);
364 for (ptr = param->value; ptr && *ptr; ) {
367 token = decode_token (&ptr);
369 g_ptr_array_add (challenge->realms, token);
374 g_free (param->value);
375 g_free (param->name);
379 g_free (challenge->nonce);
380 challenge->nonce = param->value;
381 g_free (param->name);
385 for (ptr = param->value; ptr && *ptr; ) {
388 token = decode_token (&ptr);
390 challenge->qop |= decode_qop (token);
396 if (challenge->qop & QOP_INVALID)
397 challenge->qop = QOP_INVALID;
398 g_free (param->value);
399 g_free (param->name);
403 PARANOID (digest_abort (&got_stale, abort));
404 if (!g_ascii_strcasecmp (param->value, "true"))
405 challenge->stale = TRUE;
407 challenge->stale = FALSE;
408 g_free (param->value);
409 g_free (param->name);
413 PARANOID (digest_abort (&got_maxbuf, abort));
414 challenge->maxbuf = atoi (param->value);
415 g_free (param->value);
416 g_free (param->name);
420 PARANOID (digest_abort (&got_charset, abort));
421 g_free (challenge->charset);
422 if (param->value && *param->value)
423 challenge->charset = param->value;
425 challenge->charset = NULL;
426 g_free (param->name);
429 case DIGEST_ALGORITHM:
430 PARANOID (digest_abort (&got_algorithm, abort));
431 g_free (challenge->algorithm);
432 challenge->algorithm = param->value;
433 g_free (param->name);
437 for (ptr = param->value; ptr && *ptr; ) {
440 token = decode_token (&ptr);
442 challenge->cipher |= decode_cipher (token);
447 if (challenge->cipher & CIPHER_INVALID)
448 challenge->cipher = CIPHER_INVALID;
449 g_free (param->value);
450 g_free (param->name);
454 challenge->params = g_list_prepend (challenge->params, param);
459 g_list_free (params);
465 digest_uri_to_string (struct _DigestURI *uri)
468 return g_strdup_printf ("%s/%s/%s", uri->type, uri->host, uri->name);
470 return g_strdup_printf ("%s/%s", uri->type, uri->host);
474 compute_response (struct _DigestResponse *resp,
488 buffer = g_string_sized_new (256);
489 length = g_checksum_type_get_length (G_CHECKSUM_MD5);
490 digest = g_alloca (length);
494 g_string_append (buffer, resp->username);
495 g_string_append_c (buffer, ':');
496 g_string_append (buffer, resp->realm);
497 g_string_append_c (buffer, ':');
498 g_string_append (buffer, passwd);
500 checksum = g_checksum_new (G_CHECKSUM_MD5);
502 checksum, (const guchar *) buffer->str, buffer->len);
503 g_checksum_get_digest (checksum, digest, &length);
504 g_checksum_free (checksum);
506 /* Clear the buffer. */
507 g_string_truncate (buffer, 0);
509 g_string_append_len (buffer, (gchar *) digest, length);
510 g_string_append_c (buffer, ':');
511 g_string_append (buffer, resp->nonce);
512 g_string_append_c (buffer, ':');
513 g_string_append (buffer, resp->cnonce);
514 if (resp->authzid != NULL) {
515 g_string_append_c (buffer, ':');
516 g_string_append (buffer, resp->authzid);
519 hex_a1 = g_compute_checksum_for_string (
520 G_CHECKSUM_MD5, buffer->str, buffer->len);
522 /* Clear the buffer. */
523 g_string_truncate (buffer, 0);
528 /* We are calculating the client response. */
529 g_string_append (buffer, "AUTHENTICATE:");
531 /* We are calculating the server rspauth. */
532 g_string_append_c (buffer, ':');
535 uri = digest_uri_to_string (resp->uri);
536 g_string_append (buffer, uri);
539 if (resp->qop == QOP_AUTH_INT || resp->qop == QOP_AUTH_CONF)
540 g_string_append (buffer, ":00000000000000000000000000000000");
542 hex_a2 = g_compute_checksum_for_string (
543 G_CHECKSUM_MD5, buffer->str, buffer->len);
545 /* Clear the buffer. */
546 g_string_truncate (buffer, 0);
550 g_string_append (buffer, hex_a1);
551 g_string_append_c (buffer, ':');
552 g_string_append (buffer, resp->nonce);
553 g_string_append_c (buffer, ':');
554 g_string_append_len (buffer, resp->nc, 8);
555 g_string_append_c (buffer, ':');
556 g_string_append (buffer, resp->cnonce);
557 g_string_append_c (buffer, ':');
558 g_string_append (buffer, qop_to_string (resp->qop));
559 g_string_append_c (buffer, ':');
560 g_string_append (buffer, hex_a2);
562 hex_kd = g_compute_checksum_for_string (
563 G_CHECKSUM_MD5, buffer->str, buffer->len);
565 g_strlcpy ((gchar *) out, hex_kd, 33);
571 g_string_free (buffer, TRUE);
574 static struct _DigestResponse *
575 generate_response (struct _DigestChallenge *challenge,
577 const gchar *protocol,
581 struct _DigestResponse *resp;
582 struct _DigestURI *uri;
588 length = g_checksum_type_get_length (G_CHECKSUM_MD5);
589 digest = g_alloca (length);
591 resp = g_new0 (struct _DigestResponse, 1);
592 resp->username = g_strdup (user);
593 /* FIXME: we should use the preferred realm */
594 if (challenge->realms && challenge->realms->len > 0)
595 resp->realm = g_strdup (challenge->realms->pdata[0]);
597 resp->realm = g_strdup ("");
599 resp->nonce = g_strdup (challenge->nonce);
601 /* generate the cnonce */
602 bgen = g_strdup_printf ("%p:%lu:%lu", (gpointer) resp,
604 (gulong) time (NULL));
605 checksum = g_checksum_new (G_CHECKSUM_MD5);
606 g_checksum_update (checksum, (guchar *) bgen, -1);
607 g_checksum_get_digest (checksum, digest, &length);
608 g_checksum_free (checksum);
611 /* take our recommended 64 bits of entropy */
612 resp->cnonce = g_base64_encode ((guchar *) digest, 8);
614 /* we don't support re-auth so the nonce count is always 1 */
615 strcpy (resp->nc, "00000001");
618 /* FIXME: choose - probably choose "auth" ??? */
619 resp->qop = QOP_AUTH;
622 uri = g_new0 (struct _DigestURI, 1);
623 uri->type = g_strdup (protocol);
624 uri->host = g_strdup (host);
628 /* charsets... yay */
629 if (challenge->charset) {
630 /* I believe that this is only ever allowed to be
631 * UTF-8. We strdup the charset specified by the
632 * challenge anyway, just in case it's not UTF-8.
634 resp->charset = g_strdup (challenge->charset);
637 resp->cipher = CIPHER_INVALID;
638 if (resp->qop == QOP_AUTH_CONF) {
639 /* FIXME: choose a cipher? */
640 resp->cipher = CIPHER_INVALID;
643 /* we don't really care about this... */
644 resp->authzid = NULL;
646 compute_response (resp, passwd, TRUE, (guchar *) resp->resp);
652 digest_response (struct _DigestResponse *resp)
658 buffer = g_byte_array_new ();
659 g_byte_array_append (buffer, (guint8 *) "username=\"", 10);
661 /* Encode the username using the requested charset */
662 gchar *username, *outbuf;
663 const gchar *charset;
668 charset = camel_iconv_locale_charset ();
670 charset = "iso-8859-1";
672 cd = camel_iconv_open (resp->charset, charset);
674 len = strlen (resp->username);
675 outlen = 2 * len; /* plenty of space */
677 outbuf = username = g_malloc0 (outlen + 1);
678 inbuf = resp->username;
679 if (cd == (iconv_t) -1 || camel_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (gsize) -1) {
680 /* We can't convert to UTF-8 - pretend we never got a charset param? */
681 g_free (resp->charset);
682 resp->charset = NULL;
684 /* Set the username to the non-UTF-8 version */
686 username = g_strdup (resp->username);
689 if (cd != (iconv_t) -1)
690 camel_iconv_close (cd);
692 g_byte_array_append (buffer, (guint8 *) username, strlen (username));
695 g_byte_array_append (buffer, (guint8 *) resp->username, strlen (resp->username));
698 g_byte_array_append (buffer, (guint8 *) "\",realm=\"", 9);
699 g_byte_array_append (buffer, (guint8 *) resp->realm, strlen (resp->realm));
701 g_byte_array_append (buffer, (guint8 *) "\",nonce=\"", 9);
702 g_byte_array_append (buffer, (guint8 *) resp->nonce, strlen (resp->nonce));
704 g_byte_array_append (buffer, (guint8 *) "\",cnonce=\"", 10);
705 g_byte_array_append (buffer, (guint8 *) resp->cnonce, strlen (resp->cnonce));
707 g_byte_array_append (buffer, (guint8 *) "\",nc=", 5);
708 g_byte_array_append (buffer, (guint8 *) resp->nc, 8);
710 g_byte_array_append (buffer, (guint8 *) ",qop=", 5);
711 str = qop_to_string (resp->qop);
712 g_byte_array_append (buffer, (guint8 *) str, strlen (str));
714 g_byte_array_append (buffer, (guint8 *) ",digest-uri=\"", 13);
715 buf = digest_uri_to_string (resp->uri);
716 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
719 g_byte_array_append (buffer, (guint8 *) "\",response=", 11);
720 g_byte_array_append (buffer, (guint8 *) resp->resp, 32);
722 if (resp->maxbuf > 0) {
723 g_byte_array_append (buffer, (guint8 *) ",maxbuf=", 8);
724 buf = g_strdup_printf ("%u", resp->maxbuf);
725 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
730 g_byte_array_append (buffer, (guint8 *) ",charset=", 9);
731 g_byte_array_append (buffer, (guint8 *) resp->charset, strlen ((gchar *) resp->charset));
734 if (resp->cipher != CIPHER_INVALID) {
735 str = cipher_to_string (resp->cipher);
737 g_byte_array_append (buffer, (guint8 *) ",cipher=\"", 9);
738 g_byte_array_append (buffer, (guint8 *) str, strlen (str));
739 g_byte_array_append (buffer, (guint8 *) "\"", 1);
744 g_byte_array_append (buffer, (guint8 *) ",authzid=\"", 10);
745 g_byte_array_append (buffer, (guint8 *) resp->authzid, strlen (resp->authzid));
746 g_byte_array_append (buffer, (guint8 *) "\"", 1);
753 sasl_digest_md5_finalize (GObject *object)
755 CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
756 struct _DigestChallenge *c = sasl->priv->challenge;
757 struct _DigestResponse *r = sasl->priv->response;
762 for (i = 0; i < c->realms->len; i++)
763 g_free (c->realms->pdata[i]);
764 g_ptr_array_free (c->realms, TRUE);
768 g_free (c->algorithm);
769 for (p = c->params; p; p = p->next) {
770 struct _param *param = p->data;
772 g_free (param->name);
773 g_free (param->value);
776 g_list_free (c->params);
781 g_free (r->username);
786 g_free (r->uri->type);
787 g_free (r->uri->host);
788 g_free (r->uri->name);
796 /* Chain up to parent's finalize() method. */
797 G_OBJECT_CLASS (camel_sasl_digest_md5_parent_class)->finalize (object);
801 sasl_digest_md5_challenge_sync (CamelSasl *sasl,
803 GCancellable *cancellable,
806 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
807 struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
808 CamelNetworkSettings *network_settings;
809 CamelSettings *settings;
810 CamelService *service;
811 struct _param *rspauth;
812 GByteArray *ret = NULL;
813 gboolean abort = FALSE;
817 struct addrinfo *ai, hints;
818 const gchar *service_name;
819 const gchar *password;
823 /* Need to wait for the server */
827 service = camel_sasl_get_service (sasl);
828 service_name = camel_sasl_get_service_name (sasl);
830 settings = camel_service_ref_settings (service);
831 g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
833 network_settings = CAMEL_NETWORK_SETTINGS (settings);
834 host = camel_network_settings_dup_host (network_settings);
835 user = camel_network_settings_dup_user (network_settings);
837 g_object_unref (settings);
839 g_return_val_if_fail (user != NULL, NULL);
842 host = g_strdup ("localhost");
844 password = camel_service_get_password (service);
845 g_return_val_if_fail (password != NULL, NULL);
847 switch (priv->state) {
849 if (token->len > 2048) {
851 error, CAMEL_SERVICE_ERROR,
852 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
853 _("Server challenge too long (>2048 octets)"));
857 tokens = g_strndup ((gchar *) token->data, token->len);
858 priv->challenge = parse_server_challenge (tokens, &abort);
860 if (!priv->challenge || abort) {
862 error, CAMEL_SERVICE_ERROR,
863 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
864 _("Server challenge invalid\n"));
868 if (priv->challenge->qop == QOP_INVALID) {
870 error, CAMEL_SERVICE_ERROR,
871 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
872 _("Server challenge contained invalid "
873 "\"Quality of Protection\" token"));
877 memset (&hints, 0, sizeof (hints));
878 hints.ai_flags = AI_CANONNAME;
879 ai = camel_getaddrinfo (
880 host, NULL, &hints, cancellable, NULL);
881 if (ai && ai->ai_canonname)
882 ptr = ai->ai_canonname;
884 ptr = "localhost.localdomain";
886 priv->response = generate_response (
887 priv->challenge, ptr, service_name,
890 camel_freeaddrinfo (ai);
891 ret = digest_response (priv->response);
896 tokens = g_strndup ((gchar *) token->data, token->len);
900 if (!tokens || !*tokens) {
903 error, CAMEL_SERVICE_ERROR,
904 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
905 _("Server response did not contain "
906 "authorization data"));
910 rspauth = g_new0 (struct _param, 1);
913 rspauth->name = decode_token (&ptr);
916 rspauth->value = decode_value (&ptr);
920 if (!rspauth->value) {
921 g_free (rspauth->name);
924 error, CAMEL_SERVICE_ERROR,
925 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
926 _("Server response contained incomplete "
927 "authorization data"));
931 compute_response (priv->response, password, FALSE, out);
932 if (memcmp (out, rspauth->value, 32) != 0) {
933 g_free (rspauth->name);
934 g_free (rspauth->value);
937 error, CAMEL_SERVICE_ERROR,
938 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
939 _("Server response does not match"));
940 camel_sasl_set_authenticated (sasl, TRUE);
944 g_free (rspauth->name);
945 g_free (rspauth->value);
948 ret = g_byte_array_new ();
950 camel_sasl_set_authenticated (sasl, TRUE);
965 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *class)
967 GObjectClass *object_class;
968 CamelSaslClass *sasl_class;
970 g_type_class_add_private (class, sizeof (CamelSaslDigestMd5Private));
972 object_class = G_OBJECT_CLASS (class);
973 object_class->finalize = sasl_digest_md5_finalize;
975 sasl_class = CAMEL_SASL_CLASS (class);
976 sasl_class->auth_type = &sasl_digest_md5_auth_type;
977 sasl_class->challenge_sync = sasl_digest_md5_challenge_sync;
981 camel_sasl_digest_md5_init (CamelSaslDigestMd5 *sasl)
983 sasl->priv = CAMEL_SASL_DIGEST_MD5_GET_PRIVATE (sasl);