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 (
606 (gulong) time (NULL));
607 checksum = g_checksum_new (G_CHECKSUM_MD5);
608 g_checksum_update (checksum, (guchar *) bgen, -1);
609 g_checksum_get_digest (checksum, digest, &length);
610 g_checksum_free (checksum);
613 /* take our recommended 64 bits of entropy */
614 resp->cnonce = g_base64_encode ((guchar *) digest, 8);
616 /* we don't support re-auth so the nonce count is always 1 */
617 strcpy (resp->nc, "00000001");
620 /* FIXME: choose - probably choose "auth" ??? */
621 resp->qop = QOP_AUTH;
624 uri = g_new0 (struct _DigestURI, 1);
625 uri->type = g_strdup (protocol);
626 uri->host = g_strdup (host);
630 /* charsets... yay */
631 if (challenge->charset) {
632 /* I believe that this is only ever allowed to be
633 * UTF-8. We strdup the charset specified by the
634 * challenge anyway, just in case it's not UTF-8.
636 resp->charset = g_strdup (challenge->charset);
639 resp->cipher = CIPHER_INVALID;
640 if (resp->qop == QOP_AUTH_CONF) {
641 /* FIXME: choose a cipher? */
642 resp->cipher = CIPHER_INVALID;
645 /* we don't really care about this... */
646 resp->authzid = NULL;
648 compute_response (resp, passwd, TRUE, (guchar *) resp->resp);
654 digest_response (struct _DigestResponse *resp)
660 buffer = g_byte_array_new ();
661 g_byte_array_append (buffer, (guint8 *) "username=\"", 10);
663 /* Encode the username using the requested charset */
664 gchar *username, *outbuf;
665 const gchar *charset;
670 charset = camel_iconv_locale_charset ();
672 charset = "iso-8859-1";
674 cd = camel_iconv_open (resp->charset, charset);
676 len = strlen (resp->username);
677 outlen = 2 * len; /* plenty of space */
679 outbuf = username = g_malloc0 (outlen + 1);
680 inbuf = resp->username;
681 if (cd == (iconv_t) -1 || camel_iconv (cd, &inbuf, &len, &outbuf, &outlen) == (gsize) -1) {
682 /* We can't convert to UTF-8 - pretend we never got a charset param? */
683 g_free (resp->charset);
684 resp->charset = NULL;
686 /* Set the username to the non-UTF-8 version */
688 username = g_strdup (resp->username);
691 if (cd != (iconv_t) -1)
692 camel_iconv_close (cd);
694 g_byte_array_append (buffer, (guint8 *) username, strlen (username));
697 g_byte_array_append (buffer, (guint8 *) resp->username, strlen (resp->username));
700 g_byte_array_append (buffer, (guint8 *) "\",realm=\"", 9);
701 g_byte_array_append (buffer, (guint8 *) resp->realm, strlen (resp->realm));
703 g_byte_array_append (buffer, (guint8 *) "\",nonce=\"", 9);
704 g_byte_array_append (buffer, (guint8 *) resp->nonce, strlen (resp->nonce));
706 g_byte_array_append (buffer, (guint8 *) "\",cnonce=\"", 10);
707 g_byte_array_append (buffer, (guint8 *) resp->cnonce, strlen (resp->cnonce));
709 g_byte_array_append (buffer, (guint8 *) "\",nc=", 5);
710 g_byte_array_append (buffer, (guint8 *) resp->nc, 8);
712 g_byte_array_append (buffer, (guint8 *) ",qop=", 5);
713 str = qop_to_string (resp->qop);
714 g_byte_array_append (buffer, (guint8 *) str, strlen (str));
716 g_byte_array_append (buffer, (guint8 *) ",digest-uri=\"", 13);
717 buf = digest_uri_to_string (resp->uri);
718 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
721 g_byte_array_append (buffer, (guint8 *) "\",response=", 11);
722 g_byte_array_append (buffer, (guint8 *) resp->resp, 32);
724 if (resp->maxbuf > 0) {
725 g_byte_array_append (buffer, (guint8 *) ",maxbuf=", 8);
726 buf = g_strdup_printf ("%u", resp->maxbuf);
727 g_byte_array_append (buffer, (guint8 *) buf, strlen (buf));
732 g_byte_array_append (buffer, (guint8 *) ",charset=", 9);
733 g_byte_array_append (buffer, (guint8 *) resp->charset, strlen ((gchar *) resp->charset));
736 if (resp->cipher != CIPHER_INVALID) {
737 str = cipher_to_string (resp->cipher);
739 g_byte_array_append (buffer, (guint8 *) ",cipher=\"", 9);
740 g_byte_array_append (buffer, (guint8 *) str, strlen (str));
741 g_byte_array_append (buffer, (guint8 *) "\"", 1);
746 g_byte_array_append (buffer, (guint8 *) ",authzid=\"", 10);
747 g_byte_array_append (buffer, (guint8 *) resp->authzid, strlen (resp->authzid));
748 g_byte_array_append (buffer, (guint8 *) "\"", 1);
755 sasl_digest_md5_finalize (GObject *object)
757 CamelSaslDigestMd5 *sasl = CAMEL_SASL_DIGEST_MD5 (object);
758 struct _DigestChallenge *c = sasl->priv->challenge;
759 struct _DigestResponse *r = sasl->priv->response;
764 for (i = 0; i < c->realms->len; i++)
765 g_free (c->realms->pdata[i]);
766 g_ptr_array_free (c->realms, TRUE);
770 g_free (c->algorithm);
771 for (p = c->params; p; p = p->next) {
772 struct _param *param = p->data;
774 g_free (param->name);
775 g_free (param->value);
778 g_list_free (c->params);
783 g_free (r->username);
788 g_free (r->uri->type);
789 g_free (r->uri->host);
790 g_free (r->uri->name);
798 /* Chain up to parent's finalize() method. */
799 G_OBJECT_CLASS (camel_sasl_digest_md5_parent_class)->finalize (object);
803 sasl_digest_md5_challenge_sync (CamelSasl *sasl,
805 GCancellable *cancellable,
808 CamelSaslDigestMd5 *sasl_digest = CAMEL_SASL_DIGEST_MD5 (sasl);
809 struct _CamelSaslDigestMd5Private *priv = sasl_digest->priv;
810 CamelNetworkSettings *network_settings;
811 CamelSettings *settings;
812 CamelService *service;
813 struct _param *rspauth;
814 GByteArray *ret = NULL;
815 gboolean abort = FALSE;
819 struct addrinfo *ai, hints;
820 const gchar *service_name;
821 const gchar *password;
825 /* Need to wait for the server */
829 service = camel_sasl_get_service (sasl);
830 service_name = camel_sasl_get_service_name (sasl);
832 settings = camel_service_ref_settings (service);
833 g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
835 network_settings = CAMEL_NETWORK_SETTINGS (settings);
836 host = camel_network_settings_dup_host (network_settings);
837 user = camel_network_settings_dup_user (network_settings);
839 g_object_unref (settings);
841 g_return_val_if_fail (user != NULL, NULL);
844 host = g_strdup ("localhost");
846 password = camel_service_get_password (service);
847 g_return_val_if_fail (password != NULL, NULL);
849 switch (priv->state) {
851 if (token->len > 2048) {
853 error, CAMEL_SERVICE_ERROR,
854 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
855 _("Server challenge too long (>2048 octets)"));
859 tokens = g_strndup ((gchar *) token->data, token->len);
860 priv->challenge = parse_server_challenge (tokens, &abort);
862 if (!priv->challenge || abort) {
864 error, CAMEL_SERVICE_ERROR,
865 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
866 _("Server challenge invalid\n"));
870 if (priv->challenge->qop == QOP_INVALID) {
872 error, CAMEL_SERVICE_ERROR,
873 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
874 _("Server challenge contained invalid "
875 "\"Quality of Protection\" token"));
879 memset (&hints, 0, sizeof (hints));
880 hints.ai_flags = AI_CANONNAME;
881 ai = camel_getaddrinfo (
882 host, NULL, &hints, cancellable, NULL);
883 if (ai && ai->ai_canonname)
884 ptr = ai->ai_canonname;
886 ptr = "localhost.localdomain";
888 priv->response = generate_response (
889 priv->challenge, ptr, service_name,
892 camel_freeaddrinfo (ai);
893 ret = digest_response (priv->response);
898 tokens = g_strndup ((gchar *) token->data, token->len);
902 if (!tokens || !*tokens) {
905 error, CAMEL_SERVICE_ERROR,
906 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
907 _("Server response did not contain "
908 "authorization data"));
912 rspauth = g_new0 (struct _param, 1);
915 rspauth->name = decode_token (&ptr);
918 rspauth->value = decode_value (&ptr);
922 if (!rspauth->value) {
923 g_free (rspauth->name);
926 error, CAMEL_SERVICE_ERROR,
927 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
928 _("Server response contained incomplete "
929 "authorization data"));
933 compute_response (priv->response, password, FALSE, out);
934 if (memcmp (out, rspauth->value, 32) != 0) {
935 g_free (rspauth->name);
936 g_free (rspauth->value);
939 error, CAMEL_SERVICE_ERROR,
940 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
941 _("Server response does not match"));
942 camel_sasl_set_authenticated (sasl, TRUE);
946 g_free (rspauth->name);
947 g_free (rspauth->value);
950 ret = g_byte_array_new ();
952 camel_sasl_set_authenticated (sasl, TRUE);
967 camel_sasl_digest_md5_class_init (CamelSaslDigestMd5Class *class)
969 GObjectClass *object_class;
970 CamelSaslClass *sasl_class;
972 g_type_class_add_private (class, sizeof (CamelSaslDigestMd5Private));
974 object_class = G_OBJECT_CLASS (class);
975 object_class->finalize = sasl_digest_md5_finalize;
977 sasl_class = CAMEL_SASL_CLASS (class);
978 sasl_class->auth_type = &sasl_digest_md5_auth_type;
979 sasl_class->challenge_sync = sasl_digest_md5_challenge_sync;
983 camel_sasl_digest_md5_init (CamelSaslDigestMd5 *sasl)
985 sasl->priv = CAMEL_SASL_DIGEST_MD5_GET_PRIVATE (sasl);