Coding style cleanups.
[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                        GError **error)
247 {
248         CamelSaslGssapiPrivate *priv;
249         CamelService *service;
250         OM_uint32 major, minor, flags, time;
251         gss_buffer_desc inbuf, outbuf;
252         GByteArray *challenge = NULL;
253         gss_buffer_t input_token;
254         gint conf_state;
255         gss_qop_t qop;
256         gss_OID mech;
257         gchar *str;
258         struct addrinfo *ai, hints;
259         const gchar *service_name;
260
261         priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
262
263         service = camel_sasl_get_service (sasl);
264         service_name = camel_sasl_get_service_name (sasl);
265
266         switch (priv->state) {
267         case GSSAPI_STATE_INIT:
268                 memset (&hints, 0, sizeof (hints));
269                 hints.ai_flags = AI_CANONNAME;
270                 ai = camel_getaddrinfo(service->url->host?service->url->host:"localhost", NULL, &hints, error);
271                 if (ai == NULL)
272                         return NULL;
273
274                 str = g_strdup_printf("%s@%s", service_name, ai->ai_canonname);
275                 camel_freeaddrinfo (ai);
276
277                 inbuf.value = str;
278                 inbuf.length = strlen (str);
279                 major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
280                 g_free (str);
281
282                 if (major != GSS_S_COMPLETE) {
283                         gssapi_set_exception (major, minor, error);
284                         return NULL;
285                 }
286
287                 input_token = GSS_C_NO_BUFFER;
288
289                 goto challenge;
290                 break;
291         case GSSAPI_STATE_CONTINUE_NEEDED:
292                 if (token == NULL) {
293                         g_set_error (
294                                 error, CAMEL_SERVICE_ERROR,
295                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
296                                 _("Bad authentication response from server."));
297                         return NULL;
298                 }
299
300                 inbuf.value = token->data;
301                 inbuf.length = token->len;
302                 input_token = &inbuf;
303
304         challenge:
305                 major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
306                                               GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
307                                               GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
308                                               0, GSS_C_NO_CHANNEL_BINDINGS,
309                                               input_token, &mech, &outbuf, &flags, &time);
310
311                 switch (major) {
312                 case GSS_S_COMPLETE:
313                         priv->state = GSSAPI_STATE_COMPLETE;
314                         break;
315                 case GSS_S_CONTINUE_NEEDED:
316                         priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
317                         break;
318                 default:
319                         if (major == (OM_uint32)GSS_S_FAILURE &&
320                             (minor == (OM_uint32)KRB5KRB_AP_ERR_TKT_EXPIRED ||
321                              minor == (OM_uint32)KRB5KDC_ERR_NEVER_VALID) &&
322                             send_dbus_message (service->url->user))
323                                         goto challenge;
324
325                         gssapi_set_exception (major, minor, error);
326                         return NULL;
327                 }
328
329                 challenge = g_byte_array_new ();
330                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
331 #ifndef HAVE_HEIMDAL_KRB5
332                 gss_release_buffer (&minor, &outbuf);
333 #endif
334                 break;
335         case GSSAPI_STATE_COMPLETE:
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                         return NULL;
342                 }
343
344                 inbuf.value = token->data;
345                 inbuf.length = token->len;
346
347                 major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
348                 if (major != GSS_S_COMPLETE) {
349                         gssapi_set_exception (major, minor, error);
350                         return NULL;
351                 }
352
353                 if (outbuf.length < 4) {
354                         g_set_error (
355                                 error, CAMEL_SERVICE_ERROR,
356                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
357                                 _("Bad authentication response from server."));
358 #ifndef HAVE_HEIMDAL_KRB5
359                         gss_release_buffer (&minor, &outbuf);
360 #endif
361                         return NULL;
362                 }
363
364                 /* check that our desired security layer is supported */
365                 if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
366                         g_set_error (
367                                 error, CAMEL_SERVICE_ERROR,
368                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
369                                 _("Unsupported security layer."));
370 #ifndef HAVE_HEIMDAL_KRB5
371                         gss_release_buffer (&minor, &outbuf);
372 #endif
373                         return NULL;
374                 }
375
376                 inbuf.length = 4 + strlen (service->url->user);
377                 inbuf.value = str = g_malloc (inbuf.length);
378                 memcpy (inbuf.value, outbuf.value, 4);
379                 str[0] = DESIRED_SECURITY_LAYER;
380                 memcpy (str + 4, service->url->user, inbuf.length - 4);
381
382 #ifndef HAVE_HEIMDAL_KRB5
383                 gss_release_buffer (&minor, &outbuf);
384 #endif
385
386                 major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
387                 if (major != GSS_S_COMPLETE) {
388                         gssapi_set_exception (major, minor, error);
389                         g_free (str);
390                         return NULL;
391                 }
392
393                 g_free (str);
394                 challenge = g_byte_array_new ();
395                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
396
397 #ifndef HAVE_HEIMDAL_KRB5
398                 gss_release_buffer (&minor, &outbuf);
399 #endif
400
401                 priv->state = GSSAPI_STATE_AUTHENTICATED;
402
403                 camel_sasl_set_authenticated (sasl, TRUE);
404                 break;
405         default:
406                 return NULL;
407         }
408
409         return challenge;
410 }
411
412 static void
413 camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
414 {
415         GObjectClass *object_class;
416         CamelSaslClass *sasl_class;
417
418         g_type_class_add_private (class, sizeof (CamelSaslGssapiPrivate));
419
420         object_class = G_OBJECT_CLASS (class);
421         object_class->finalize = sasl_gssapi_finalize;
422
423         sasl_class = CAMEL_SASL_CLASS (class);
424         sasl_class->challenge = sasl_gssapi_challenge;
425 }
426
427 static void
428 camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
429 {
430         sasl->priv = CAMEL_SASL_GSSAPI_GET_PRIVATE (sasl);
431
432         sasl->priv->state = GSSAPI_STATE_INIT;
433         sasl->priv->ctx = GSS_C_NO_CONTEXT;
434         sasl->priv->target = GSS_C_NO_NAME;
435 }
436
437 #endif /* HAVE_KRB5 */