Rename camel_service_get_settings().
[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 /* If building without Kerberos support, this class is an empty shell. */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <errno.h>
30
31 #include <string.h>
32 #include <sys/types.h>
33
34 #ifndef _WIN32
35 #include <netdb.h>
36 #include <sys/socket.h>
37 #endif
38
39 #include <gio/gio.h>
40 #include <glib/gi18n-lib.h>
41
42 #include "camel-net-utils.h"
43 #include "camel-network-settings.h"
44 #include "camel-sasl-gssapi.h"
45 #include "camel-session.h"
46
47 #ifdef HAVE_KRB5
48
49 #ifdef HAVE_HEIMDAL_KRB5
50 #include <krb5.h>
51 #else
52 #include <krb5/krb5.h>
53 #endif /* HAVE_HEIMDAL_KRB5 */
54
55 #ifdef HAVE_ET_COM_ERR_H
56 #include <et/com_err.h>
57 #else
58 #ifdef HAVE_COM_ERR_H
59 #include <com_err.h>
60 #endif /* HAVE_COM_ERR_H */
61 #endif /* HAVE_ET_COM_ERR_H */
62
63 #ifdef HAVE_MIT_KRB5
64 #include <gssapi/gssapi.h>
65 #include <gssapi/gssapi_generic.h>
66 #endif /* HAVE_MIT_KRB5 */
67
68 #ifdef HAVE_HEIMDAL_KRB5
69 #include <gssapi.h>
70 #else
71 #ifdef HAVE_SUN_KRB5
72 #include <gssapi/gssapi.h>
73 #include <gssapi/gssapi_ext.h>
74 extern gss_OID gss_nt_service_name;
75 #endif /* HAVE_SUN_KRB5 */
76 #endif /* HAVE_HEIMDAL_KRB5 */
77
78 #define CAMEL_SASL_GSSAPI_GET_PRIVATE(obj) \
79         (G_TYPE_INSTANCE_GET_PRIVATE \
80         ((obj), CAMEL_TYPE_SASL_GSSAPI, CamelSaslGssapiPrivate))
81
82 #ifndef GSS_C_OID_KRBV5_DES
83 #define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
84 #endif
85
86 #define DBUS_PATH               "/org/gnome/KrbAuthDialog"
87 #define DBUS_INTERFACE          "org.gnome.KrbAuthDialog"
88 #define DBUS_METHOD             "org.gnome.KrbAuthDialog.acquireTgt"
89
90 static CamelServiceAuthType sasl_gssapi_auth_type = {
91         N_("GSSAPI"),
92
93         N_("This option will connect to the server using "
94            "Kerberos 5 authentication."),
95
96         "GSSAPI",
97         FALSE
98 };
99
100 enum {
101         GSSAPI_STATE_INIT,
102         GSSAPI_STATE_CONTINUE_NEEDED,
103         GSSAPI_STATE_COMPLETE,
104         GSSAPI_STATE_AUTHENTICATED
105 };
106
107 #define GSSAPI_SECURITY_LAYER_NONE       (1 << 0)
108 #define GSSAPI_SECURITY_LAYER_INTEGRITY  (1 << 1)
109 #define GSSAPI_SECURITY_LAYER_PRIVACY    (1 << 2)
110
111 #define DESIRED_SECURITY_LAYER  GSSAPI_SECURITY_LAYER_NONE
112
113 struct _CamelSaslGssapiPrivate {
114         gint state;
115         gss_ctx_id_t ctx;
116         gss_name_t target;
117 };
118
119 #endif /* HAVE_KRB5 */
120
121 G_DEFINE_TYPE (CamelSaslGssapi, camel_sasl_gssapi, CAMEL_TYPE_SASL)
122
123 #ifdef HAVE_KRB5
124
125 static void
126 gssapi_set_exception (OM_uint32 major,
127                       OM_uint32 minor,
128                       GError **error)
129 {
130         const gchar *str;
131
132         switch (major) {
133         case GSS_S_BAD_MECH:
134                 str = _("The specified mechanism is not supported by the "
135                         "provided credential, or is unrecognized by the "
136                         "implementation.");
137                 break;
138         case GSS_S_BAD_NAME:
139                 str = _("The provided target_name parameter was ill-formed.");
140                 break;
141         case GSS_S_BAD_NAMETYPE:
142                 str = _("The provided target_name parameter contained an "
143                         "invalid or unsupported type of name.");
144                 break;
145         case GSS_S_BAD_BINDINGS:
146                 str = _("The input_token contains different channel "
147                         "bindings to those specified via the "
148                         "input_chan_bindings parameter.");
149                 break;
150         case GSS_S_BAD_SIG:
151                 str = _("The input_token contains an invalid signature, or a "
152                         "signature that could not be verified.");
153                 break;
154         case GSS_S_NO_CRED:
155                 str = _("The supplied credentials were not valid for context "
156                         "initiation, or the credential handle did not "
157                         "reference any credentials.");
158                 break;
159         case GSS_S_NO_CONTEXT:
160                 str = _("The supplied context handle did not refer to a valid context.");
161                 break;
162         case GSS_S_DEFECTIVE_TOKEN:
163                 str = _("The consistency checks performed on the input_token failed.");
164                 break;
165         case GSS_S_DEFECTIVE_CREDENTIAL:
166                 str = _("The consistency checks performed on the credential failed.");
167                 break;
168         case GSS_S_CREDENTIALS_EXPIRED:
169                 str = _("The referenced credentials have expired.");
170                 break;
171         case GSS_S_FAILURE:
172                 str = error_message (minor);
173                 break;
174         default:
175                 str = _("Bad authentication response from server.");
176         }
177
178         g_set_error (
179                 error, CAMEL_SERVICE_ERROR,
180                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
181                 "%s", str);
182 }
183
184 static void
185 sasl_gssapi_finalize (GObject *object)
186 {
187         CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
188         guint32 status;
189
190         if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
191                 gss_delete_sec_context (
192                         &status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
193
194         if (sasl->priv->target != GSS_C_NO_NAME)
195                 gss_release_name (&status, &sasl->priv->target);
196
197         /* Chain up to parent's finalize() method. */
198         G_OBJECT_CLASS (camel_sasl_gssapi_parent_class)->finalize (object);
199 }
200
201 /* DBUS Specific code */
202
203 static gboolean
204 send_dbus_message (const gchar *name)
205 {
206         gint success = FALSE;
207         GError *error = NULL;
208         GDBusConnection *connection;
209         GDBusMessage *message, *reply;
210
211         connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
212         if (error) {
213                 g_warning ("could not get system bus: %s\n", error->message);
214                 g_error_free (error);
215
216                 return FALSE;
217         }
218
219         g_dbus_connection_set_exit_on_close (connection, FALSE);
220
221         /* Create a new message on the DBUS_INTERFACE */
222         message = g_dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt");
223         if (!message) {
224                 g_object_unref (connection);
225                 return FALSE;
226         }
227
228         /* Appends the data as an argument to the message */
229         if (strchr (name, '\\'))
230                 name = strchr (name, '\\');
231         g_dbus_message_set_body (message, g_variant_new ("(s)", name));
232
233         /* Sends the message: Have a 300 sec wait timeout  */
234         reply = g_dbus_connection_send_message_with_reply_sync (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, 300 * 1000, NULL, NULL, &error);
235
236         if (!error && reply) {
237                 if (g_dbus_message_to_gerror (reply, &error)) {
238                         g_object_unref (reply);
239                         reply = NULL;
240                 }
241         }
242
243         if (error) {
244                 g_dbus_error_strip_remote_error (error);
245                 g_warning ("%s: %s\n", G_STRFUNC, error->message);
246                 g_error_free (error);
247         }
248
249         if (reply) {
250                 GVariant *body = g_dbus_message_get_body (reply);
251
252                 if (body)
253                         g_variant_get (body, "(b)", &success);
254
255                 g_object_unref (reply);
256         }
257
258         /* Free the message */
259         g_object_unref (message);
260         g_object_unref (connection);
261
262         return success;
263 }
264
265 /* END DBus stuff */
266
267 static GByteArray *
268 sasl_gssapi_challenge_sync (CamelSasl *sasl,
269                             GByteArray *token,
270                             GCancellable *cancellable,
271                             GError **error)
272 {
273         CamelSaslGssapiPrivate *priv;
274         CamelNetworkSettings *network_settings;
275         CamelSettings *settings;
276         CamelService *service;
277         OM_uint32 major, minor, flags, time;
278         gss_buffer_desc inbuf, outbuf;
279         GByteArray *challenge = NULL;
280         gss_buffer_t input_token;
281         gint conf_state;
282         gss_qop_t qop;
283         gss_OID mech;
284         gchar *str;
285         struct addrinfo *ai, hints;
286         const gchar *service_name;
287         gchar *host;
288         gchar *user;
289
290         priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
291
292         service = camel_sasl_get_service (sasl);
293         service_name = camel_sasl_get_service_name (sasl);
294
295         settings = camel_service_ref_settings (service);
296         g_return_val_if_fail (CAMEL_IS_NETWORK_SETTINGS (settings), NULL);
297
298         network_settings = CAMEL_NETWORK_SETTINGS (settings);
299         host = camel_network_settings_dup_host (network_settings);
300         user = camel_network_settings_dup_user (network_settings);
301
302         g_object_unref (settings);
303
304         g_return_val_if_fail (user != NULL, NULL);
305
306         if (host == NULL)
307                 host = g_strdup ("localhost");
308
309         switch (priv->state) {
310         case GSSAPI_STATE_INIT:
311                 memset (&hints, 0, sizeof (hints));
312                 hints.ai_flags = AI_CANONNAME;
313                 ai = camel_getaddrinfo (
314                         host, NULL, &hints, cancellable, error);
315                 if (ai == NULL)
316                         goto exit;
317
318                 str = g_strdup_printf("%s@%s", service_name, ai->ai_canonname);
319                 camel_freeaddrinfo (ai);
320
321                 inbuf.value = str;
322                 inbuf.length = strlen (str);
323                 major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
324                 g_free (str);
325
326                 if (major != GSS_S_COMPLETE) {
327                         gssapi_set_exception (major, minor, error);
328                         goto exit;
329                 }
330
331                 input_token = GSS_C_NO_BUFFER;
332
333                 goto challenge;
334                 break;
335         case GSSAPI_STATE_CONTINUE_NEEDED:
336                 if (token == NULL) {
337                         g_set_error (
338                                 error, CAMEL_SERVICE_ERROR,
339                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
340                                 _("Bad authentication response from server."));
341                         goto exit;
342                 }
343
344                 inbuf.value = token->data;
345                 inbuf.length = token->len;
346                 input_token = &inbuf;
347
348         challenge:
349                 major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
350                                               GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
351                                               GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
352                                               0, GSS_C_NO_CHANNEL_BINDINGS,
353                                               input_token, &mech, &outbuf, &flags, &time);
354
355                 switch (major) {
356                 case GSS_S_COMPLETE:
357                         priv->state = GSSAPI_STATE_COMPLETE;
358                         break;
359                 case GSS_S_CONTINUE_NEEDED:
360                         priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
361                         break;
362                 default:
363                         if (major == (OM_uint32) GSS_S_FAILURE &&
364                             (minor == (OM_uint32) KRB5KRB_AP_ERR_TKT_EXPIRED ||
365                              minor == (OM_uint32) KRB5KDC_ERR_NEVER_VALID) &&
366                             send_dbus_message (user))
367                                         goto challenge;
368
369                         gssapi_set_exception (major, minor, error);
370                         goto exit;
371                 }
372
373                 challenge = g_byte_array_new ();
374                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
375 #ifndef HAVE_HEIMDAL_KRB5
376                 gss_release_buffer (&minor, &outbuf);
377 #endif
378                 break;
379         case GSSAPI_STATE_COMPLETE:
380                 if (token == NULL) {
381                         g_set_error (
382                                 error, CAMEL_SERVICE_ERROR,
383                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
384                                 _("Bad authentication response from server."));
385                         goto exit;
386                 }
387
388                 inbuf.value = token->data;
389                 inbuf.length = token->len;
390
391                 major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
392                 if (major != GSS_S_COMPLETE) {
393                         gssapi_set_exception (major, minor, error);
394                         goto exit;
395                 }
396
397                 if (outbuf.length < 4) {
398                         g_set_error (
399                                 error, CAMEL_SERVICE_ERROR,
400                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
401                                 _("Bad authentication response from server."));
402 #ifndef HAVE_HEIMDAL_KRB5
403                         gss_release_buffer (&minor, &outbuf);
404 #endif
405                         goto exit;
406                 }
407
408                 /* check that our desired security layer is supported */
409                 if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
410                         g_set_error (
411                                 error, CAMEL_SERVICE_ERROR,
412                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
413                                 _("Unsupported security layer."));
414 #ifndef HAVE_HEIMDAL_KRB5
415                         gss_release_buffer (&minor, &outbuf);
416 #endif
417                         goto exit;
418                 }
419
420                 inbuf.length = 4 + strlen (user);
421                 inbuf.value = str = g_malloc (inbuf.length);
422                 memcpy (inbuf.value, outbuf.value, 4);
423                 str[0] = DESIRED_SECURITY_LAYER;
424                 memcpy (str + 4, user, inbuf.length - 4);
425
426 #ifndef HAVE_HEIMDAL_KRB5
427                 gss_release_buffer (&minor, &outbuf);
428 #endif
429
430                 major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
431                 if (major != GSS_S_COMPLETE) {
432                         gssapi_set_exception (major, minor, error);
433                         g_free (str);
434                         goto exit;
435                 }
436
437                 g_free (str);
438                 challenge = g_byte_array_new ();
439                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
440
441 #ifndef HAVE_HEIMDAL_KRB5
442                 gss_release_buffer (&minor, &outbuf);
443 #endif
444
445                 priv->state = GSSAPI_STATE_AUTHENTICATED;
446
447                 camel_sasl_set_authenticated (sasl, TRUE);
448                 break;
449         default:
450                 break;
451         }
452
453 exit:
454         g_free (host);
455         g_free (user);
456
457         return challenge;
458 }
459
460 #endif /* HAVE_KRB5 */
461
462 static void
463 camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
464 {
465 #ifdef HAVE_KRB5
466         GObjectClass *object_class;
467         CamelSaslClass *sasl_class;
468
469         g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
470
471         object_class = G_OBJECT_CLASS (class);
472         object_class->finalize = sasl_gssapi_finalize;
473
474         sasl_class = CAMEL_SASL_CLASS (class);
475         sasl_class->auth_type = &sasl_gssapi_auth_type;
476         sasl_class->challenge_sync = sasl_gssapi_challenge_sync;
477 #endif /* HAVE_KRB5 */
478 }
479
480 static void
481 camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
482 {
483 #ifdef HAVE_KRB5
484         sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
485         sasl->priv->state = GSSAPI_STATE_INIT;
486         sasl->priv->ctx = GSS_C_NO_CONTEXT;
487         sasl->priv->target = GSS_C_NO_NAME;
488 #endif /* HAVE_KRB5 */
489 }