Use GCancellable in all methods that may block.
[platform/upstream/evolution-data-server.git] / camel / camel-sasl-gssapi.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *
5  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU Lesser General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28
29 #ifdef HAVE_KRB5
30 #include <netdb.h>
31 #include <string.h>
32 #include <sys/socket.h>
33 #include <sys/types.h>
34 #ifdef HAVE_HEIMDAL_KRB5
35 #include <krb5.h>
36 #else
37 #include <krb5/krb5.h>
38 #endif
39 #ifdef HAVE_ET_COM_ERR_H
40 #include <et/com_err.h>
41 #else
42 #ifdef HAVE_COM_ERR_H
43 #include <com_err.h>
44 #endif
45 #endif
46 #ifdef HAVE_MIT_KRB5
47 #include <gssapi/gssapi.h>
48 #include <gssapi/gssapi_generic.h>
49 #endif
50 #ifdef HAVE_HEIMDAL_KRB5
51 #include <gssapi.h>
52 #else
53 #ifdef  HAVE_SUN_KRB5
54 #include <gssapi/gssapi.h>
55 #include <gssapi/gssapi_ext.h>
56 extern gss_OID gss_nt_service_name;
57 #endif
58 #endif
59
60 #ifndef GSS_C_OID_KRBV5_DES
61 #define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
62 #endif
63
64 #include <glib/gi18n-lib.h>
65 #include <gio/gio.h>
66
67 #include "camel-net-utils.h"
68 #include "camel-sasl-gssapi.h"
69 #include "camel-session.h"
70
71 #define DBUS_PATH               "/org/gnome/KrbAuthDialog"
72 #define DBUS_INTERFACE          "org.gnome.KrbAuthDialog"
73 #define DBUS_METHOD             "org.gnome.KrbAuthDialog.acquireTgt"
74
75 #define CAMEL_SASL_GSSAPI_GET_PRIVATE(obj) \
76         (G_TYPE_INSTANCE_GET_PRIVATE \
77         ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiPrivate))
78
79 CamelServiceAuthType camel_sasl_gssapi_authtype = {
80         N_("GSSAPI"),
81
82         N_("This option will connect to the server using "
83            "Kerberos 5 authentication."),
84
85         "GSSAPI",
86         FALSE
87 };
88
89 enum {
90         GSSAPI_STATE_INIT,
91         GSSAPI_STATE_CONTINUE_NEEDED,
92         GSSAPI_STATE_COMPLETE,
93         GSSAPI_STATE_AUTHENTICATED
94 };
95
96 #define GSSAPI_SECURITY_LAYER_NONE       (1 << 0)
97 #define GSSAPI_SECURITY_LAYER_INTEGRITY  (1 << 1)
98 #define GSSAPI_SECURITY_LAYER_PRIVACY    (1 << 2)
99
100 #define DESIRED_SECURITY_LAYER  GSSAPI_SECURITY_LAYER_NONE
101
102 struct _CamelSaslGssapiPrivate {
103         gint state;
104         gss_ctx_id_t ctx;
105         gss_name_t target;
106 };
107
108 G_DEFINE_TYPE (CamelSaslGssapi, camel_sasl_gssapi, CAMEL_TYPE_SASL)
109
110 static void
111 gssapi_set_exception (OM_uint32 major,
112                       OM_uint32 minor,
113                       GError **error)
114 {
115         const gchar *str;
116
117         switch (major) {
118         case GSS_S_BAD_MECH:
119                 str = _("The specified mechanism is not supported by the "
120                         "provided credential, or is unrecognized by the "
121                         "implementation.");
122                 break;
123         case GSS_S_BAD_NAME:
124                 str = _("The provided target_name parameter was ill-formed.");
125                 break;
126         case GSS_S_BAD_NAMETYPE:
127                 str = _("The provided target_name parameter contained an "
128                         "invalid or unsupported type of name.");
129                 break;
130         case GSS_S_BAD_BINDINGS:
131                 str = _("The input_token contains different channel "
132                         "bindings to those specified via the "
133                         "input_chan_bindings parameter.");
134                 break;
135         case GSS_S_BAD_SIG:
136                 str = _("The input_token contains an invalid signature, or a "
137                         "signature that could not be verified.");
138                 break;
139         case GSS_S_NO_CRED:
140                 str = _("The supplied credentials were not valid for context "
141                         "initiation, or the credential handle did not "
142                         "reference any credentials.");
143                 break;
144         case GSS_S_NO_CONTEXT:
145                 str = _("The supplied context handle did not refer to a valid context.");
146                 break;
147         case GSS_S_DEFECTIVE_TOKEN:
148                 str = _("The consistency checks performed on the input_token failed.");
149                 break;
150         case GSS_S_DEFECTIVE_CREDENTIAL:
151                 str = _("The consistency checks performed on the credential failed.");
152                 break;
153         case GSS_S_CREDENTIALS_EXPIRED:
154                 str = _("The referenced credentials have expired.");
155                 break;
156         case GSS_S_FAILURE:
157                 str = error_message (minor);
158                 break;
159         default:
160                 str = _("Bad authentication response from server.");
161         }
162
163         g_set_error (
164                 error, CAMEL_SERVICE_ERROR,
165                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
166                 "%s", str);
167 }
168
169 static void
170 sasl_gssapi_finalize (GObject *object)
171 {
172         CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
173         guint32 status;
174
175         if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
176                 gss_delete_sec_context (
177                         &status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
178
179         if (sasl->priv->target != GSS_C_NO_NAME)
180                 gss_release_name (&status, &sasl->priv->target);
181
182         /* Chain up to parent's finalize() method. */
183         G_OBJECT_CLASS (camel_sasl_gssapi_parent_class)->finalize (object);
184 }
185
186 /* DBUS Specific code */
187
188 static gboolean
189 send_dbus_message (gchar *name)
190 {
191         gint success = FALSE;
192         GError *error = NULL;
193         GDBusConnection *connection;
194         GDBusMessage *message, *reply;
195
196         connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
197         if (error) {
198                 g_warning ("could not get system bus: %s\n", error->message);
199                 g_error_free (error);
200
201                 return FALSE;
202         }
203
204         g_dbus_connection_set_exit_on_close (connection, FALSE);
205
206         /* Create a new message on the DBUS_INTERFACE */
207         message = g_dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt");
208         if (!message) {
209                 g_object_unref (connection);
210                 return FALSE;
211         }
212
213         /* Appends the data as an argument to the message */
214         if (strchr (name, '\\'))
215                 name = strchr (name, '\\');
216         g_dbus_message_set_body (message, g_variant_new ("(s)", name));
217
218         /* Sends the message: Have a 300 sec wait timeout  */
219         reply = g_dbus_connection_send_message_with_reply_sync (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 300 * 1000, NULL, NULL, &error);
220
221         if (error) {
222                 g_warning ("%s: %s\n", G_STRFUNC, error->message);
223                 g_error_free (error);
224         }
225
226         if (reply) {
227                 GVariant *body = g_dbus_message_get_body (reply);
228
229                 success = body && g_variant_get_boolean (body);
230
231                 g_object_unref (reply);
232         }
233
234         /* Free the message */
235         g_object_unref (message);
236         g_object_unref (connection);
237
238         return success;
239 }
240
241 /* END DBus stuff */
242
243 static GByteArray *
244 sasl_gssapi_challenge (CamelSasl *sasl,
245                        GByteArray *token,
246                        GCancellable *cancellable,
247                        GError **error)
248 {
249         CamelSaslGssapiPrivate *priv;
250         CamelService *service;
251         OM_uint32 major, minor, flags, time;
252         gss_buffer_desc inbuf, outbuf;
253         GByteArray *challenge = NULL;
254         gss_buffer_t input_token;
255         gint conf_state;
256         gss_qop_t qop;
257         gss_OID mech;
258         gchar *str;
259         struct addrinfo *ai, hints;
260         const gchar *service_name;
261
262         priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
263
264         service = camel_sasl_get_service (sasl);
265         service_name = camel_sasl_get_service_name (sasl);
266
267         switch (priv->state) {
268         case GSSAPI_STATE_INIT:
269                 memset (&hints, 0, sizeof (hints));
270                 hints.ai_flags = AI_CANONNAME;
271                 ai = camel_getaddrinfo (
272                         service->url->host ?
273                         service->url->host : "localhost",
274                         NULL, &hints, cancellable, error);
275                 if (ai == NULL)
276                         return NULL;
277
278                 str = g_strdup_printf("%s@%s", service_name, ai->ai_canonname);
279                 camel_freeaddrinfo (ai);
280
281                 inbuf.value = str;
282                 inbuf.length = strlen (str);
283                 major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
284                 g_free (str);
285
286                 if (major != GSS_S_COMPLETE) {
287                         gssapi_set_exception (major, minor, error);
288                         return NULL;
289                 }
290
291                 input_token = GSS_C_NO_BUFFER;
292
293                 goto challenge;
294                 break;
295         case GSSAPI_STATE_CONTINUE_NEEDED:
296                 if (token == NULL) {
297                         g_set_error (
298                                 error, CAMEL_SERVICE_ERROR,
299                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
300                                 _("Bad authentication response from server."));
301                         return NULL;
302                 }
303
304                 inbuf.value = token->data;
305                 inbuf.length = token->len;
306                 input_token = &inbuf;
307
308         challenge:
309                 major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
310                                               GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
311                                               GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
312                                               0, GSS_C_NO_CHANNEL_BINDINGS,
313                                               input_token, &mech, &outbuf, &flags, &time);
314
315                 switch (major) {
316                 case GSS_S_COMPLETE:
317                         priv->state = GSSAPI_STATE_COMPLETE;
318                         break;
319                 case GSS_S_CONTINUE_NEEDED:
320                         priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
321                         break;
322                 default:
323                         if (major == (OM_uint32)GSS_S_FAILURE &&
324                             (minor == (OM_uint32)KRB5KRB_AP_ERR_TKT_EXPIRED ||
325                              minor == (OM_uint32)KRB5KDC_ERR_NEVER_VALID) &&
326                             send_dbus_message (service->url->user))
327                                         goto challenge;
328
329                         gssapi_set_exception (major, minor, error);
330                         return NULL;
331                 }
332
333                 challenge = g_byte_array_new ();
334                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
335 #ifndef HAVE_HEIMDAL_KRB5
336                 gss_release_buffer (&minor, &outbuf);
337 #endif
338                 break;
339         case GSSAPI_STATE_COMPLETE:
340                 if (token == NULL) {
341                         g_set_error (
342                                 error, CAMEL_SERVICE_ERROR,
343                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
344                                 _("Bad authentication response from server."));
345                         return NULL;
346                 }
347
348                 inbuf.value = token->data;
349                 inbuf.length = token->len;
350
351                 major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
352                 if (major != GSS_S_COMPLETE) {
353                         gssapi_set_exception (major, minor, error);
354                         return NULL;
355                 }
356
357                 if (outbuf.length < 4) {
358                         g_set_error (
359                                 error, CAMEL_SERVICE_ERROR,
360                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
361                                 _("Bad authentication response from server."));
362 #ifndef HAVE_HEIMDAL_KRB5
363                         gss_release_buffer (&minor, &outbuf);
364 #endif
365                         return NULL;
366                 }
367
368                 /* check that our desired security layer is supported */
369                 if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
370                         g_set_error (
371                                 error, CAMEL_SERVICE_ERROR,
372                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
373                                 _("Unsupported security layer."));
374 #ifndef HAVE_HEIMDAL_KRB5
375                         gss_release_buffer (&minor, &outbuf);
376 #endif
377                         return NULL;
378                 }
379
380                 inbuf.length = 4 + strlen (service->url->user);
381                 inbuf.value = str = g_malloc (inbuf.length);
382                 memcpy (inbuf.value, outbuf.value, 4);
383                 str[0] = DESIRED_SECURITY_LAYER;
384                 memcpy (str + 4, service->url->user, inbuf.length - 4);
385
386 #ifndef HAVE_HEIMDAL_KRB5
387                 gss_release_buffer (&minor, &outbuf);
388 #endif
389
390                 major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
391                 if (major != GSS_S_COMPLETE) {
392                         gssapi_set_exception (major, minor, error);
393                         g_free (str);
394                         return NULL;
395                 }
396
397                 g_free (str);
398                 challenge = g_byte_array_new ();
399                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
400
401 #ifndef HAVE_HEIMDAL_KRB5
402                 gss_release_buffer (&minor, &outbuf);
403 #endif
404
405                 priv->state = GSSAPI_STATE_AUTHENTICATED;
406
407                 camel_sasl_set_authenticated (sasl, TRUE);
408                 break;
409         default:
410                 return NULL;
411         }
412
413         return challenge;
414 }
415
416 static void
417 camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
418 {
419         GObjectClass *object_class;
420         CamelSaslClass *sasl_class;
421
422         g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
423
424         object_class = G_OBJECT_CLASS (class);
425         object_class->finalize = sasl_gssapi_finalize;
426
427         sasl_class = CAMEL_SASL_CLASS (class);
428         sasl_class->challenge = sasl_gssapi_challenge;
429 }
430
431 static void
432 camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
433 {
434         sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
435
436         sasl->priv->state = GSSAPI_STATE_INIT;
437         sasl->priv->ctx = GSS_C_NO_CONTEXT;
438         sasl->priv->target = GSS_C_NO_NAME;
439 }
440
441 #endif /* HAVE_KRB5 */