Bug 603773 - com_err.h doesn't exist on Solaris
[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
66 #include <dbus/dbus.h>
67 #include <dbus/dbus-glib-lowlevel.h>
68
69 #include "camel-net-utils.h"
70 #include "camel-sasl-gssapi.h"
71 #include "camel-session.h"
72
73 #define DBUS_PATH               "/org/gnome/KrbAuthDialog"
74 #define DBUS_INTERFACE          "org.gnome.KrbAuthDialog"
75 #define DBUS_METHOD             "org.gnome.KrbAuthDialog.acquireTgt"
76
77 CamelServiceAuthType camel_sasl_gssapi_authtype = {
78         N_("GSSAPI"),
79
80         N_("This option will connect to the server using "
81            "Kerberos 5 authentication."),
82
83         "GSSAPI",
84         FALSE
85 };
86
87 enum {
88         GSSAPI_STATE_INIT,
89         GSSAPI_STATE_CONTINUE_NEEDED,
90         GSSAPI_STATE_COMPLETE,
91         GSSAPI_STATE_AUTHENTICATED
92 };
93
94 #define GSSAPI_SECURITY_LAYER_NONE       (1 << 0)
95 #define GSSAPI_SECURITY_LAYER_INTEGRITY  (1 << 1)
96 #define GSSAPI_SECURITY_LAYER_PRIVACY    (1 << 2)
97
98 #define DESIRED_SECURITY_LAYER  GSSAPI_SECURITY_LAYER_NONE
99
100 struct _CamelSaslGssapiPrivate {
101         gint state;
102         gss_ctx_id_t ctx;
103         gss_name_t target;
104 };
105
106 static CamelSaslClass *parent_class = NULL;
107
108 static void
109 gssapi_set_exception (OM_uint32 major,
110                       OM_uint32 minor,
111                       CamelException *ex)
112 {
113         const gchar *str;
114
115         switch (major) {
116         case GSS_S_BAD_MECH:
117                 str = _("The specified mechanism is not supported by the "
118                         "provided credential, or is unrecognized by the "
119                         "implementation.");
120                 break;
121         case GSS_S_BAD_NAME:
122                 str = _("The provided target_name parameter was ill-formed.");
123                 break;
124         case GSS_S_BAD_NAMETYPE:
125                 str = _("The provided target_name parameter contained an "
126                         "invalid or unsupported type of name.");
127                 break;
128         case GSS_S_BAD_BINDINGS:
129                 str = _("The input_token contains different channel "
130                         "bindings to those specified via the "
131                         "input_chan_bindings parameter.");
132                 break;
133         case GSS_S_BAD_SIG:
134                 str = _("The input_token contains an invalid signature, or a "
135                         "signature that could not be verified.");
136                 break;
137         case GSS_S_NO_CRED:
138                 str = _("The supplied credentials were not valid for context "
139                         "initiation, or the credential handle did not "
140                         "reference any credentials.");
141                 break;
142         case GSS_S_NO_CONTEXT:
143                 str = _("The supplied context handle did not refer to a valid context.");
144                 break;
145         case GSS_S_DEFECTIVE_TOKEN:
146                 str = _("The consistency checks performed on the input_token failed.");
147                 break;
148         case GSS_S_DEFECTIVE_CREDENTIAL:
149                 str = _("The consistency checks performed on the credential failed.");
150                 break;
151         case GSS_S_CREDENTIALS_EXPIRED:
152                 str = _("The referenced credentials have expired.");
153                 break;
154         case GSS_S_FAILURE:
155                 str = error_message (minor);
156                 break;
157         default:
158                 str = _("Bad authentication response from server.");
159         }
160
161         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str);
162 }
163
164 static void
165 sasl_gssapi_finalize (CamelObject *object)
166 {
167         CamelSaslGssapi *sasl = CAMEL_SASL_GSSAPI (object);
168         guint32 status;
169
170         if (sasl->priv->ctx != GSS_C_NO_CONTEXT)
171                 gss_delete_sec_context (
172                         &status, &sasl->priv->ctx, GSS_C_NO_BUFFER);
173
174         if (sasl->priv->target != GSS_C_NO_NAME)
175                 gss_release_name (&status, &sasl->priv->target);
176
177         g_free (sasl->priv);
178 }
179
180 /* DBUS Specific code */
181
182 static gboolean
183 send_dbus_message (gchar *name)
184 {
185         DBusMessage *message, *reply;
186         DBusError dbus_error;
187         gint success = FALSE;
188         DBusConnection *bus = NULL;
189
190         dbus_error_init (&dbus_error);
191         if (!(bus = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error))) {
192                 g_warning ("could not get system bus: %s\n", dbus_error.message);
193                 dbus_error_free (&dbus_error);
194                 return FALSE;
195         }
196
197         dbus_error_free (&dbus_error);
198
199         dbus_connection_setup_with_g_main (bus, NULL);
200         dbus_connection_set_exit_on_disconnect (bus, FALSE);
201
202         /* Create a new message on the DBUS_INTERFACE */
203         if (!(message = dbus_message_new_method_call (DBUS_INTERFACE, DBUS_PATH, DBUS_INTERFACE, "acquireTgt"))) {
204                 g_object_unref (bus);
205                 return FALSE;
206         }
207         /* Appends the data as an argument to the message */
208         if (strchr(name, '\\'))
209                 name = strchr(name, '\\');
210         dbus_message_append_args (message,
211                                   DBUS_TYPE_STRING, &name,
212                                   DBUS_TYPE_INVALID);
213         dbus_error_init(&dbus_error);
214
215         /* Sends the message: Have a 300 sec wait timeout  */
216         reply = dbus_connection_send_with_reply_and_block (bus, message, 300 * 1000, &dbus_error);
217
218         if (dbus_error_is_set(&dbus_error))
219                 g_warning ("%s: %s\n", dbus_error.name, dbus_error.message);
220         dbus_error_free(&dbus_error);
221
222         if (reply)
223         {
224                 dbus_error_init(&dbus_error);
225                 dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_BOOLEAN, &success, DBUS_TYPE_INVALID);
226                 dbus_error_free(&dbus_error);
227                 dbus_message_unref(reply);
228         }
229
230         /* Free the message */
231         dbus_message_unref (message);
232         dbus_connection_unref (bus);
233
234         return success;
235 }
236
237 /* END DBus stuff */
238
239 static GByteArray *
240 sasl_gssapi_challenge (CamelSasl *sasl,
241                        GByteArray *token,
242                        CamelException *ex)
243 {
244         CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv;
245         CamelService *service;
246         OM_uint32 major, minor, flags, time;
247         gss_buffer_desc inbuf, outbuf;
248         GByteArray *challenge = NULL;
249         gss_buffer_t input_token;
250         gint conf_state;
251         gss_qop_t qop;
252         gss_OID mech;
253         gchar *str;
254         struct addrinfo *ai, hints;
255         const gchar *service_name;
256
257         service = camel_sasl_get_service (sasl);
258         service_name = camel_sasl_get_service_name (sasl);
259
260         switch (priv->state) {
261         case GSSAPI_STATE_INIT:
262                 memset(&hints, 0, sizeof(hints));
263                 hints.ai_flags = AI_CANONNAME;
264                 ai = camel_getaddrinfo(service->url->host?service->url->host:"localhost", NULL, &hints, ex);
265                 if (ai == NULL)
266                         return NULL;
267
268                 str = g_strdup_printf("%s@%s", service_name, ai->ai_canonname);
269                 camel_freeaddrinfo(ai);
270
271                 inbuf.value = str;
272                 inbuf.length = strlen (str);
273                 major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
274                 g_free (str);
275
276                 if (major != GSS_S_COMPLETE) {
277                         gssapi_set_exception (major, minor, ex);
278                         return NULL;
279                 }
280
281                 input_token = GSS_C_NO_BUFFER;
282
283                 goto challenge;
284                 break;
285         case GSSAPI_STATE_CONTINUE_NEEDED:
286                 if (token == NULL) {
287                         camel_exception_set (
288                                 ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
289                                 _("Bad authentication response from server."));
290                         return NULL;
291                 }
292
293                 inbuf.value = token->data;
294                 inbuf.length = token->len;
295                 input_token = &inbuf;
296
297         challenge:
298                 major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
299                                               GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
300                                               GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
301                                               0, GSS_C_NO_CHANNEL_BINDINGS,
302                                               input_token, &mech, &outbuf, &flags, &time);
303
304                 switch (major) {
305                 case GSS_S_COMPLETE:
306                         priv->state = GSSAPI_STATE_COMPLETE;
307                         break;
308                 case GSS_S_CONTINUE_NEEDED:
309                         priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
310                         break;
311                 default:
312                         if (major == (OM_uint32)GSS_S_FAILURE &&
313                             (minor == (OM_uint32)KRB5KRB_AP_ERR_TKT_EXPIRED ||
314                              minor == (OM_uint32)KRB5KDC_ERR_NEVER_VALID)) {
315                                 if (send_dbus_message (service->url->user))
316                                         goto challenge;
317                         } else
318                                 gssapi_set_exception (major, minor, ex);
319                         return NULL;
320                 }
321
322                 challenge = g_byte_array_new ();
323                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
324 #ifndef HAVE_HEIMDAL_KRB5
325                 gss_release_buffer (&minor, &outbuf);
326 #endif
327                 break;
328         case GSSAPI_STATE_COMPLETE:
329                 if (token == NULL) {
330                         camel_exception_set (
331                                 ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
332                                 _("Bad authentication response from server."));
333                         return NULL;
334                 }
335
336                 inbuf.value = token->data;
337                 inbuf.length = token->len;
338
339                 major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
340                 if (major != GSS_S_COMPLETE) {
341                         gssapi_set_exception (major, minor, ex);
342                         return NULL;
343                 }
344
345                 if (outbuf.length < 4) {
346                         camel_exception_set (
347                                 ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
348                                 _("Bad authentication response from server."));
349 #ifndef HAVE_HEIMDAL_KRB5
350                         gss_release_buffer (&minor, &outbuf);
351 #endif
352                         return NULL;
353                 }
354
355                 /* check that our desired security layer is supported */
356                 if ((((guchar *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
357                         camel_exception_set (
358                                 ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
359                                 _("Unsupported security layer."));
360 #ifndef HAVE_HEIMDAL_KRB5
361                         gss_release_buffer (&minor, &outbuf);
362 #endif
363                         return NULL;
364                 }
365
366                 inbuf.length = 4 + strlen (service->url->user);
367                 inbuf.value = str = g_malloc (inbuf.length);
368                 memcpy (inbuf.value, outbuf.value, 4);
369                 str[0] = DESIRED_SECURITY_LAYER;
370                 memcpy (str + 4, service->url->user, inbuf.length - 4);
371
372 #ifndef HAVE_HEIMDAL_KRB5
373                 gss_release_buffer (&minor, &outbuf);
374 #endif
375
376                 major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
377                 if (major != GSS_S_COMPLETE) {
378                         gssapi_set_exception (major, minor, ex);
379                         g_free (str);
380                         return NULL;
381                 }
382
383                 g_free (str);
384                 challenge = g_byte_array_new ();
385                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
386
387 #ifndef HAVE_HEIMDAL_KRB5
388                 gss_release_buffer (&minor, &outbuf);
389 #endif
390
391                 priv->state = GSSAPI_STATE_AUTHENTICATED;
392
393                 camel_sasl_set_authenticated (sasl, TRUE);
394                 break;
395         default:
396                 return NULL;
397         }
398
399         return challenge;
400 }
401
402 static void
403 camel_sasl_gssapi_class_init (CamelSaslGssapiClass *class)
404 {
405         CamelSaslClass *sasl_class;
406
407         parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
408
409         sasl_class = CAMEL_SASL_CLASS (class);
410         sasl_class->challenge = sasl_gssapi_challenge;
411 }
412
413 static void
414 camel_sasl_gssapi_init (CamelSaslGssapi *sasl)
415 {
416         sasl->priv = g_new (struct _CamelSaslGssapiPrivate, 1);
417
418         sasl->priv->state = GSSAPI_STATE_INIT;
419         sasl->priv->ctx = GSS_C_NO_CONTEXT;
420         sasl->priv->target = GSS_C_NO_NAME;
421 }
422
423 CamelType
424 camel_sasl_gssapi_get_type (void)
425 {
426         static CamelType type = CAMEL_INVALID_TYPE;
427
428         if (type == CAMEL_INVALID_TYPE) {
429                 type = camel_type_register (
430                         camel_sasl_get_type (),
431                         "CamelSaslGssapi",
432                         sizeof (CamelSaslGssapi),
433                         sizeof (CamelSaslGssapiClass),
434                         (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init,
435                         NULL,
436                         (CamelObjectInitFunc) camel_sasl_gssapi_init,
437                         (CamelObjectFinalizeFunc) sasl_gssapi_finalize);
438         }
439
440         return type;
441 }
442
443 #endif /* HAVE_KRB5 */