2007-09-07 mcrha Fix for bug #473880
[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 2003 Ximian, Inc. (www.ximian.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
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <errno.h>
29
30 #ifdef HAVE_KRB5
31 #include <netdb.h>
32 #include <string.h>
33 #include <sys/socket.h>
34 #include <sys/types.h>
35 #ifdef HAVE_ET_COM_ERR_H
36 #include <et/com_err.h>
37 #else
38 #include <com_err.h>
39 #endif
40 #ifdef HAVE_MIT_KRB5
41 #include <gssapi/gssapi.h>
42 #include <gssapi/gssapi_generic.h>
43 #endif
44 #ifdef HAVE_HEIMDAL_KRB5
45 #include <gssapi.h>
46 #else
47 #ifdef  HAVE_SUN_KRB5 
48 #include <gssapi/gssapi.h>
49 #include <gssapi/gssapi_ext.h>
50 extern gss_OID gss_nt_service_name;
51 #endif
52 #endif
53
54 #ifndef GSS_C_OID_KRBV5_DES
55 #define GSS_C_OID_KRBV5_DES GSS_C_NO_OID
56 #endif
57
58 #include <glib/gi18n-lib.h>
59
60 #include "camel-net-utils.h"
61 #include "camel-sasl-gssapi.h"
62
63 CamelServiceAuthType camel_sasl_gssapi_authtype = {
64         N_("GSSAPI"),
65         
66         N_("This option will connect to the server using "
67            "Kerberos 5 authentication."),
68         
69         "GSSAPI",
70         FALSE
71 };
72
73 enum {
74         GSSAPI_STATE_INIT,
75         GSSAPI_STATE_CONTINUE_NEEDED,
76         GSSAPI_STATE_COMPLETE,
77         GSSAPI_STATE_AUTHENTICATED
78 };
79
80 #define GSSAPI_SECURITY_LAYER_NONE       (1 << 0)
81 #define GSSAPI_SECURITY_LAYER_INTEGRITY  (1 << 1)
82 #define GSSAPI_SECURITY_LAYER_PRIVACY    (1 << 2)
83
84 #define DESIRED_SECURITY_LAYER  GSSAPI_SECURITY_LAYER_NONE
85
86 struct _CamelSaslGssapiPrivate {
87         int state;
88         gss_ctx_id_t ctx;
89         gss_name_t target;
90 };
91
92
93 static GByteArray *gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex);
94
95
96 static CamelSaslClass *parent_class = NULL;
97
98
99 static void
100 camel_sasl_gssapi_class_init (CamelSaslGssapiClass *klass)
101 {
102         CamelSaslClass *camel_sasl_class = CAMEL_SASL_CLASS (klass);
103         
104         parent_class = CAMEL_SASL_CLASS (camel_type_get_global_classfuncs (camel_sasl_get_type ()));
105         
106         /* virtual method overload */
107         camel_sasl_class->challenge = gssapi_challenge;
108 }
109
110 static void
111 camel_sasl_gssapi_init (gpointer object, gpointer klass)
112 {
113         CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
114         
115         gssapi->priv = g_new (struct _CamelSaslGssapiPrivate, 1);
116         gssapi->priv->state = GSSAPI_STATE_INIT;
117         gssapi->priv->ctx = GSS_C_NO_CONTEXT;
118         gssapi->priv->target = GSS_C_NO_NAME;
119 }
120
121 static void
122 camel_sasl_gssapi_finalize (CamelObject *object)
123 {
124         CamelSaslGssapi *gssapi = CAMEL_SASL_GSSAPI (object);
125         guint32 status;
126         
127         if (gssapi->priv->ctx != GSS_C_NO_CONTEXT)
128                 gss_delete_sec_context (&status, &gssapi->priv->ctx, GSS_C_NO_BUFFER);
129         
130         if (gssapi->priv->target != GSS_C_NO_NAME)
131                 gss_release_name (&status, &gssapi->priv->target);
132         
133         g_free (gssapi->priv);
134 }
135
136
137 CamelType
138 camel_sasl_gssapi_get_type (void)
139 {
140         static CamelType type = CAMEL_INVALID_TYPE;
141         
142         if (type == CAMEL_INVALID_TYPE) {
143                 type = camel_type_register (
144                         camel_sasl_get_type (),
145                         "CamelSaslGssapi",
146                         sizeof (CamelSaslGssapi),
147                         sizeof (CamelSaslGssapiClass),
148                         (CamelObjectClassInitFunc) camel_sasl_gssapi_class_init,
149                         NULL,
150                         (CamelObjectInitFunc) camel_sasl_gssapi_init,
151                         (CamelObjectFinalizeFunc) camel_sasl_gssapi_finalize);
152         }
153         
154         return type;
155 }
156
157 static void
158 gssapi_set_exception (OM_uint32 major, OM_uint32 minor, CamelException *ex)
159 {
160         const char *str;
161         
162         switch (major) {
163         case GSS_S_BAD_MECH:
164                 str = _("The specified mechanism is not supported by the "
165                         "provided credential, or is unrecognized by the "
166                         "implementation.");
167                 break;
168         case GSS_S_BAD_NAME:
169                 str = _("The provided target_name parameter was ill-formed.");
170                 break;
171         case GSS_S_BAD_NAMETYPE:
172                 str = _("The provided target_name parameter contained an "
173                         "invalid or unsupported type of name.");
174                 break;
175         case GSS_S_BAD_BINDINGS:
176                 str = _("The input_token contains different channel "
177                         "bindings to those specified via the "
178                         "input_chan_bindings parameter.");
179                 break;
180         case GSS_S_BAD_SIG:
181                 str = _("The input_token contains an invalid signature, or a "
182                         "signature that could not be verified.");
183                 break;
184         case GSS_S_NO_CRED:
185                 str = _("The supplied credentials were not valid for context "
186                         "initiation, or the credential handle did not "
187                         "reference any credentials.");
188                 break;
189         case GSS_S_NO_CONTEXT:
190                 str = _("The supplied context handle did not refer to a valid context.");
191                 break;
192         case GSS_S_DEFECTIVE_TOKEN:
193                 str = _("The consistency checks performed on the input_token failed.");
194                 break;
195         case GSS_S_DEFECTIVE_CREDENTIAL:
196                 str = _("The consistency checks performed on the credential failed.");
197                 break;
198         case GSS_S_CREDENTIALS_EXPIRED:
199                 str = _("The referenced credentials have expired.");
200                 break;
201         case GSS_S_FAILURE:
202                 str = error_message (minor);
203                 break;
204         default:
205                 str = _("Bad authentication response from server.");
206         }
207         
208         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, str);
209 }
210
211 static GByteArray *
212 gssapi_challenge (CamelSasl *sasl, GByteArray *token, CamelException *ex)
213 {
214         struct _CamelSaslGssapiPrivate *priv = CAMEL_SASL_GSSAPI (sasl)->priv;
215         OM_uint32 major, minor, flags, time;
216         gss_buffer_desc inbuf, outbuf;
217         GByteArray *challenge = NULL;
218         gss_buffer_t input_token;
219         int conf_state;
220         gss_qop_t qop;
221         gss_OID mech;
222         char *str;
223         struct addrinfo *ai, hints;
224         
225         switch (priv->state) {
226         case GSSAPI_STATE_INIT:
227                 memset(&hints, 0, sizeof(hints));
228                 hints.ai_flags = AI_CANONNAME;
229                 ai = camel_getaddrinfo(sasl->service->url->host?sasl->service->url->host:"localhost", NULL, &hints, ex);
230                 if (ai == NULL)
231                         return NULL;
232                 
233                 str = g_strdup_printf("%s@%s", sasl->service_name, ai->ai_canonname);
234                 camel_freeaddrinfo(ai);
235                 
236                 inbuf.value = str;
237                 inbuf.length = strlen (str);
238                 major = gss_import_name (&minor, &inbuf, GSS_C_NT_HOSTBASED_SERVICE, &priv->target);
239                 g_free (str);
240                 
241                 if (major != GSS_S_COMPLETE) {
242                         gssapi_set_exception (major, minor, ex);
243                         return NULL;
244                 }
245                 
246                 input_token = GSS_C_NO_BUFFER;
247                 
248                 goto challenge;
249                 break;
250         case GSSAPI_STATE_CONTINUE_NEEDED:
251                 if (token == NULL) {
252                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
253                                              _("Bad authentication response from server."));
254                         return NULL;
255                 }
256                 
257                 inbuf.value = token->data;
258                 inbuf.length = token->len;
259                 input_token = &inbuf;
260                 
261         challenge:
262                 major = gss_init_sec_context (&minor, GSS_C_NO_CREDENTIAL, &priv->ctx, priv->target,
263                                               GSS_C_OID_KRBV5_DES, GSS_C_MUTUAL_FLAG |
264                                               GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG,
265                                               0, GSS_C_NO_CHANNEL_BINDINGS,
266                                               input_token, &mech, &outbuf, &flags, &time);
267                 
268                 switch (major) {
269                 case GSS_S_COMPLETE:
270                         priv->state = GSSAPI_STATE_COMPLETE;
271                         break;
272                 case GSS_S_CONTINUE_NEEDED:
273                         priv->state = GSSAPI_STATE_CONTINUE_NEEDED;
274                         break;
275                 default:
276                         gssapi_set_exception (major, minor, ex);
277                         return NULL;
278                 }
279                 
280                 challenge = g_byte_array_new ();
281                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
282 #ifndef HAVE_HEIMDAL_KRB5
283                 gss_release_buffer (&minor, &outbuf);
284 #endif
285                 break;
286         case GSSAPI_STATE_COMPLETE:
287                 if (token == NULL) {
288                         camel_exception_set (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                 
296                 major = gss_unwrap (&minor, priv->ctx, &inbuf, &outbuf, &conf_state, &qop);
297                 if (major != GSS_S_COMPLETE) {
298                         gssapi_set_exception (major, minor, ex);
299                         return NULL;
300                 }
301                 
302                 if (outbuf.length < 4) {
303                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
304                                              _("Bad authentication response from server."));
305 #ifndef HAVE_HEIMDAL_KRB5
306                         gss_release_buffer (&minor, &outbuf);
307 #endif
308                         return NULL;
309                 }
310                 
311                 /* check that our desired security layer is supported */
312                 if ((((unsigned char *) outbuf.value)[0] & DESIRED_SECURITY_LAYER) != DESIRED_SECURITY_LAYER) {
313                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
314                                              _("Unsupported security layer."));
315 #ifndef HAVE_HEIMDAL_KRB5
316                         gss_release_buffer (&minor, &outbuf);
317 #endif
318                         return NULL;
319                 }
320                 
321                 inbuf.length = 4 + strlen (sasl->service->url->user);
322                 inbuf.value = str = g_malloc (inbuf.length);
323                 memcpy (inbuf.value, outbuf.value, 4);
324                 str[0] = DESIRED_SECURITY_LAYER;
325                 memcpy (str + 4, sasl->service->url->user, inbuf.length - 4);
326                 
327 #ifndef HAVE_HEIMDAL_KRB5
328                 gss_release_buffer (&minor, &outbuf);
329 #endif
330                 
331                 major = gss_wrap (&minor, priv->ctx, FALSE, qop, &inbuf, &conf_state, &outbuf);
332                 if (major != GSS_S_COMPLETE) {
333                         gssapi_set_exception (major, minor, ex);
334                         g_free (str);
335                         return NULL;
336                 }
337                 
338                 g_free (str);
339                 challenge = g_byte_array_new ();
340                 g_byte_array_append (challenge, outbuf.value, outbuf.length);
341                 
342 #ifndef HAVE_HEIMDAL_KRB5
343                 gss_release_buffer (&minor, &outbuf);
344 #endif
345                 
346                 priv->state = GSSAPI_STATE_AUTHENTICATED;
347                 
348                 sasl->authenticated = TRUE;
349                 break;
350         default:
351                 return NULL;
352         }
353         
354         return challenge;
355 }
356
357 #endif /* HAVE_KRB5 */