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