Rename camel_service_get_settings().
[platform/upstream/evolution-data-server.git] / camel / camel-sasl-gssapi.c
index d9a3f56..75745e2 100644 (file)
@@ -2,53 +2,97 @@
 /*
  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
  *
- *  Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.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
+ *  it under the terms of the GNU Lesser 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.
+ *  GNU Lesser General Public License for more details.
  *
- *  You should have received a copy of the GNU General Public License
+ *  You should have received a copy of the GNU Lesser 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.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  *
  */
 
+/* If building without Kerberos support, this class is an empty shell. */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
-#ifdef HAVE_KRB5
+#include <errno.h>
 
 #include <string.h>
+#include <sys/types.h>
+
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#endif
+
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include "camel-net-utils.h"
+#include "camel-network-settings.h"
+#include "camel-sasl-gssapi.h"
+#include "camel-session.h"
+
+#ifdef HAVE_KRB5
+
+#ifdef HAVE_HEIMDAL_KRB5
+#include <krb5.h>
+#else
+#include <krb5/krb5.h>
+#endif /* HAVE_HEIMDAL_KRB5 */
+
+#ifdef HAVE_ET_COM_ERR_H
 #include <et/com_err.h>
+#else
+#ifdef HAVE_COM_ERR_H
+#include <com_err.h>
+#endif /* HAVE_COM_ERR_H */
+#endif /* HAVE_ET_COM_ERR_H */
+
 #ifdef HAVE_MIT_KRB5
 #include <gssapi/gssapi.h>
 #include <gssapi/gssapi_generic.h>
-#else /* HAVE_HEIMDAL_KRB5 */
+#endif /* HAVE_MIT_KRB5 */
+
+#ifdef HAVE_HEIMDAL_KRB5
 #include <gssapi.h>
-#endif
-#include <errno.h>
+#else
+#ifdef HAVE_SUN_KRB5
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_ext.h>
+extern gss_OID gss_nt_service_name;
+#endif /* HAVE_SUN_KRB5 */
+#endif /* HAVE_HEIMDAL_KRB5 */
+
+#define CAMEL_SASL_GSSAPI_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiPrivate))
 
 #ifndef GSS_C_OID_KRBV5_DES
 #define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
 #endif
 
-#include "camel-sasl-gssapi.h"
+#define DBUS_PATH              "/org/gnome/KrbAuthDialog"
+#define DBUS_INTERFACE         "org.gnome.KrbAuthDialog"
+#define DBUS_METHOD            "org.gnome.KrbAuthDialog.acquireTgt"
 
-CamelServiceAuthType camel_sasl_gssapi_authtype = {
+static CamelServiceAuthType sasl_gssapi_auth_type = {
        N_("GSSAPI"),
-       
+
        N_("This option will connect to the server using "
           "Kerberos 5 authentication."),
-       
+
        "GSSAPI",
        FALSE
 };
@@ -67,81 +111,24 @@ enum {
 #define DESIRED_SECURITY_LAYER  GSSAPI_SECURITY_LAYER_NONE
 
 struct _CamelSaslGssapiPrivate {
-       int state;
+       gint state;
        gss_ctx_id_t ctx;
        gss_name_t target;
 };
 
+#endif /* HAVE_KRB5 */
 
-static GByteArray *gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
-
-
-static CamelSaslClass *parent_class = NULL;
-
-
-static void
-camel_sasl_gssapi_class_init (CamelSaslGssapiClass *klass)
-{
-       CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (klass);
-       
-       parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
-       
-       /* virtual method overload */
-       camel_sasl_class->challenge = gssapi_challenge;
-}
+G_DEFINE_TYPE (CamelSaslGssapi, camel_sasl_gssapi, CAMEL_TYPE_SASL)
 
-static void
-camel_sasl_gssapi_init (gpointer object, gpointer klass)
-{
-       CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
-       
-       gssapi->priv = g_new (struct _CamelSaslGssapiPrivate, 1);
-       gssapi->priv->state = GSSAPI_STATE_INIT;
-       gssapi->priv->ctx = GSS_C_NO_CONTEXT;
-       gssapi->priv->target = GSS_C_NO_NAME;
-}
+#ifdef HAVE_KRB5
 
 static void
-camel_sasl_gssapi_finalize (CamelObject *object)
-{
-       CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
-       guint32 status;
-       
-       if (gssapi->priv->ctx != GSS_C_NO_CONTEXT)
-               gss_delete_sec_context (&status, gssapi->priv->ctx, GSS_C_NO_BUFFER);
-       
-       if (gssapi->priv->target != GSS_C_NO_NAME)
-               gss_release_name (&status, gssapi->priv->target);
-       
-       g_free (gssapi->priv);
-}
-
-
-CamelType
-camel_sasl_gssapi_get_type (void)
+gssapi_set_exception (OM_uint32 major,
+                      OM_uint32 minor,
+                      GError **error)
 {
-       static CamelType type = CAMEL_INVALID_TYPE;
-       
-       if (type == CAMEL_INVALID_TYPE) {
-               type = camel_type_register (
-                       camel_sasl_get_type (),
-                       "CamelSaslGssapi",
-                       sizeof (CamelSaslGssapi),
-                       sizeof (CamelSaslGssapiClass),
-                       (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init,
-                       NULL,
-                       (CamelObjectInitFunc) camel_sasl_gssapi_init,
-                       (CamelObjectFinalizeFunc) camel_sasl_gssapi_finalize);
-       }
-       
-       return type;
-}
+       const gchar *str;
 
-static void
-gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex)
-{
-       const char *str;
-       
        switch (major) {
        case GSS_S_BAD_MECH:
                str = _("The specified mechanism is not supported by the "
@@ -187,69 +174,184 @@ gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex)
        default:
                str = _("Bad authentication response from server.");
        }
-       
-       camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str);
+
+       g_set_error (
+               error, CAMEL_SERVICE_ERROR,
+               CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+               "%s", str);
+}
+
+static void
+sasl_gssapi_finalize (GObject *object)
+{
+       CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
+       guint32 status;
+
+       if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
+               gss_delete_sec_context (
+                       &status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
+
+       if (sasl->priv->target != GSS_C_NO_NAME)
+               gss_release_name (&status, &sasl->priv->target);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (camel_sasl_gssapi_parent_class)->finalize (object);
+}
+
+/* DBUS Specific code */
+
+static gboolean
+send_dbus_message (const gchar *name)
+{
+       gint success = FALSE;
+       GError *error = NULL;
+       GDBusConnection *connection;
+       GDBusMessage *message, *reply;
+
+       connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+       if (error) {
+               g_warning ("could not get system bus: %s\n", error->message);
+               g_error_free (error);
+
+               return FALSE;
+       }
+
+       g_dbus_connection_set_exit_on_close (connection, FALSE);
+
+       /* Create a new message on the DBUS_INTERFACE */
+       message = g_dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt");
+       if (!message) {
+               g_object_unref (connection);
+               return FALSE;
+       }
+
+       /* Appends the data as an argument to the message */
+       if (strchr (name, '\\'))
+               name = strchr (name, '\\');
+       g_dbus_message_set_body (message, g_variant_new ("(s)", name));
+
+       /* Sends the message: Have a 300 sec wait timeout  */
+       reply = g_dbus_connection_send_message_with_reply_sync (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 300 * 1000, NULL, NULL, &error);
+
+       if (!error && reply) {
+               if (g_dbus_message_to_gerror (reply, &error)) {
+                       g_object_unref (reply);
+                       reply = NULL;
+               }
+       }
+
+       if (error) {
+               g_dbus_error_strip_remote_error (error);
+               g_warning ("%s: %s\n", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
+
+       if (reply) {
+               GVariant *body = g_dbus_message_get_body (reply);
+
+               if (body)
+                       g_variant_get (body, "(b)", &success);
+
+               g_object_unref (reply);
+       }
+
+       /* Free the message */
+       g_object_unref (message);
+       g_object_unref (connection);
+
+       return success;
 }
 
+/* END DBus stuff */
+
 static GByteArray *
-gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
+sasl_gssapi_challenge_sync (CamelSasl *sasl,
+                            GByteArray *token,
+                            GCancellable *cancellable,
+                            GError **error)
 {
-       struct _CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv;
+       CamelSaslGssapiPrivate *priv;
+       CamelNetworkSettings *network_settings;
+       CamelSettings *settings;
+       CamelService *service;
        OM_uint32 major, minor, flags, time;
        gss_buffer_desc inbuf, outbuf;
        GByteArray *challenge = NULL;
        gss_buffer_t input_token;
-       struct hostent *h;
-       int conf_state;
+       gint conf_state;
        gss_qop_t qop;
        gss_OID mech;
-       char *str;
-       
+       gchar *str;
+       struct addrinfo *ai, hints;
+       const gchar *service_name;
+       gchar *host;
+       gchar *user;
+
+       priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
+
+       service = camel_sasl_get_service (sasl);
+       service_name = camel_sasl_get_service_name (sasl);
+
+       settings = camel_service_ref_settings (service);
+       g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
+
+       network_settings = CAMEL_NETWORK_SETTINGS (settings);
+       host = camel_network_settings_dup_host (network_settings);
+       user = camel_network_settings_dup_user (network_settings);
+
+       g_object_unref (settings);
+
+       g_return_val_if_fail (user != NULL, NULL);
+
+       if (host == NULL)
+               host = g_strdup ("localhost");
+
        switch (priv->state) {
        case GSSAPI_STATE_INIT:
-               if (!(h = camel_service_gethost (sasl->service, ex))) {
-                       camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
-                                             _("Failed to resolve host `%s': %s"),
-                                             sasl->service->url->host, g_strerror (errno));
-                       return NULL;
-               }
-               
-               str = g_strdup_printf ("%s@%s", sasl->service_name, h->h_name);
-               printf ("FQDN: %s (%s)\n", h->h_name, str);
-               camel_free_host (h);
-               
+               memset (&hints, 0, sizeof (hints));
+               hints.ai_flags = AI_CANONNAME;
+               ai = camel_getaddrinfo (
+                       host, NULL, &hints, cancellable, error);
+               if (ai == NULL)
+                       goto exit;
+
+               str = g_strdup_printf("%s@%s", service_name, ai->ai_canonname);
+               camel_freeaddrinfo (ai);
+
                inbuf.value = str;
                inbuf.length = strlen (str);
-               major = gss_import_name (&minor, &inbuf, gss_nt_service_name, &priv->target);
+               major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
                g_free (str);
-               
+
                if (major != GSS_S_COMPLETE) {
-                       gssapi_set_exception (major, minor, ex);
-                       return NULL;
+                       gssapi_set_exception (major, minor, error);
+                       goto exit;
                }
-               
+
                input_token = GSS_C_NO_BUFFER;
-               
+
                goto challenge;
                break;
        case GSSAPI_STATE_CONTINUE_NEEDED:
                if (token == NULL) {
-                       camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                            _("Bad authentication response from server."));
-                       return NULL;
+                       g_set_error (
+                               error, CAMEL_SERVICE_ERROR,
+                               CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+                               _("Bad authentication response from server."));
+                       goto exit;
                }
-               
+
                inbuf.value = token->data;
                inbuf.length = token->len;
                input_token = &inbuf;
-               
+
        challenge:
                major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
                                              GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
                                              GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
                                              0, GSS_C_NO_CHANNEL_BINDINGS,
                                              input_token, &mech, &outbuf, &flags, &time);
-               
+
                switch (major) {
                case GSS_S_COMPLETE:
                        priv->state = GSSAPI_STATE_COMPLETE;
@@ -258,79 +360,130 @@ gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
                        priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
                        break;
                default:
-                       gssapi_set_exception (major, minor, ex);
-                       printf ("gss_init_sec_context() exception\n");
-                       gss_release_buffer (&minor, &outbuf);
-                       return NULL;
+                       if (major == (OM_uint32) GSS_S_FAILURE &&
+                           (minor == (OM_uint32) KRB5KRB_AP_ERR_TKT_EXPIRED ||
+                            minor == (OM_uint32) KRB5KDC_ERR_NEVER_VALID) &&
+                           send_dbus_message (user))
+                                       goto challenge;
+
+                       gssapi_set_exception (major, minor, error);
+                       goto exit;
                }
-               
+
                challenge = g_byte_array_new ();
                g_byte_array_append (challenge, outbuf.value, outbuf.length);
+#ifndef HAVE_HEIMDAL_KRB5
                gss_release_buffer (&minor, &outbuf);
+#endif
                break;
        case GSSAPI_STATE_COMPLETE:
                if (token == NULL) {
-                       camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                            _("Bad authentication response from server."));
-                       return NULL;
+                       g_set_error (
+                               error, CAMEL_SERVICE_ERROR,
+                               CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+                               _("Bad authentication response from server."));
+                       goto exit;
                }
-               
+
                inbuf.value = token->data;
                inbuf.length = token->len;
-               
+
                major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
                if (major != GSS_S_COMPLETE) {
-                       gssapi_set_exception (major, minor, ex);
-                       printf ("gss_unwrap() exception\n");
-                       gss_release_buffer (&minor, &outbuf);
-                       return NULL;
+                       gssapi_set_exception (major, minor, error);
+                       goto exit;
                }
-               
+
                if (outbuf.length < 4) {
-                       camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                            _("Bad authentication response from server."));
+                       g_set_error (
+                               error, CAMEL_SERVICE_ERROR,
+                               CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+                               _("Bad authentication response from server."));
+#ifndef HAVE_HEIMDAL_KRB5
                        gss_release_buffer (&minor, &outbuf);
-                       return NULL;
+#endif
+                       goto exit;
                }
-               
+
                /* check that our desired security layer is supported */
-               if ((((unsigned char *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
-                       camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                            _("Unsupported security layer."));
+               if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
+                       g_set_error (
+                               error, CAMEL_SERVICE_ERROR,
+                               CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
+                               _("Unsupported security layer."));
+#ifndef HAVE_HEIMDAL_KRB5
                        gss_release_buffer (&minor, &outbuf);
-                       return NULL;
+#endif
+                       goto exit;
                }
-               
-               inbuf.length = 4 + strlen (sasl->service->url->user);
+
+               inbuf.length = 4 + strlen (user);
                inbuf.value = str = g_malloc (inbuf.length);
                memcpy (inbuf.value, outbuf.value, 4);
                str[0] = DESIRED_SECURITY_LAYER;
-               memcpy (str + 4, sasl->service->url->user, inbuf.length - 4);
+               memcpy (str + 4, user, inbuf.length - 4);
+
+#ifndef HAVE_HEIMDAL_KRB5
                gss_release_buffer (&minor, &outbuf);
-               
+#endif
+
                major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
-               if (major != 0) {
-                       gssapi_set_exception (major, minor, ex);
-                       printf ("gss_wrap() exception\n");
-                       gss_release_buffer (&minor, &outbuf);
+               if (major != GSS_S_COMPLETE) {
+                       gssapi_set_exception (major, minor, error);
                        g_free (str);
-                       return NULL;
+                       goto exit;
                }
-               
+
+               g_free (str);
                challenge = g_byte_array_new ();
                g_byte_array_append (challenge, outbuf.value, outbuf.length);
+
+#ifndef HAVE_HEIMDAL_KRB5
                gss_release_buffer (&minor, &outbuf);
-               
+#endif
+
                priv->state = GSSAPI_STATE_AUTHENTICATED;
-               
-               sasl->authenticated = TRUE;
+
+               camel_sasl_set_authenticated (sasl, TRUE);
                break;
        default:
-               printf ("unknown state exception\n");
-               return NULL;
+               break;
        }
-       
+
+exit:
+       g_free (host);
+       g_free (user);
+
        return challenge;
 }
 
 #endif /* HAVE_KRB5 */
+
+static void
+camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
+{
+#ifdef HAVE_KRB5
+       GObjectClass *object_class;
+       CamelSaslClass *sasl_class;
+
+       g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->finalize = sasl_gssapi_finalize;
+
+       sasl_class = CAMEL_SASL_CLASS (class);
+       sasl_class->auth_type = &sasl_gssapi_auth_type;
+       sasl_class->challenge_sync = sasl_gssapi_challenge_sync;
+#endif /* HAVE_KRB5 */
+}
+
+static void
+camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
+{
+#ifdef HAVE_KRB5
+       sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
+       sasl->priv->state = GSSAPI_STATE_INIT;
+       sasl->priv->ctx = GSS_C_NO_CONTEXT;
+       sasl->priv->target = GSS_C_NO_NAME;
+#endif /* HAVE_KRB5 */
+}