configure: add --enable-gssapi-only option
[siplcs.git] / src / core / sip-sec-krb5.c
blob4788291c4742f9a6e64000525423ab2b1bd0f54b
1 /**
2 * @file sip-sec-krb5.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <glib.h>
30 #include <string.h>
31 #include <gssapi/gssapi.h>
32 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
33 #include <gssapi/gssapi_ext.h>
34 #endif
35 #include <gssapi/gssapi_krb5.h>
37 #include "sipe-common.h"
38 #include "sip-sec.h"
39 #include "sip-sec-mech.h"
40 #include "sip-sec-krb5.h"
41 #include "sipe-backend.h"
42 #include "sipe-utils.h"
44 /* Security context for Kerberos */
45 typedef struct _context_krb5 {
46 struct sip_sec_context common;
47 gss_cred_id_t cred_krb5;
48 gss_ctx_id_t ctx_krb5;
49 } *context_krb5;
51 static void sip_sec_krb5_print_gss_error0(char *func,
52 OM_uint32 status,
53 int type)
55 OM_uint32 minor;
56 OM_uint32 message_context = 0;
57 gss_buffer_desc status_string;
59 do {
60 gss_display_status(&minor,
61 status,
62 type,
63 GSS_C_NO_OID,
64 &message_context,
65 &status_string);
67 SIPE_DEBUG_ERROR("sip_sec_krb5: GSSAPI error in %s (%s): %s",
68 func,
69 (type == GSS_C_GSS_CODE ? "GSS" : "Mech"),
70 (gchar *) status_string.value);
71 gss_release_buffer(&minor, &status_string);
72 } while (message_context != 0);
75 /* Prints out errors of GSSAPI function invocation */
76 static void sip_sec_krb5_print_gss_error(char *func,
77 OM_uint32 ret,
78 OM_uint32 minor)
80 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
81 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
84 /* sip-sec-mech.h API implementation for Kerberos/GSSAPI */
86 static gboolean
87 sip_sec_acquire_cred__krb5(SipSecContext context,
88 const gchar *domain,
89 const gchar *username,
90 const gchar *password)
92 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__krb5: started");
94 /* With SSO we use the default credentials */
95 if ((context->flags & SIP_SEC_FLAG_COMMON_SSO) == 0) {
96 #ifdef HAVE_GSSAPI_PASSWORD_SUPPORT
97 gchar *username_new;
98 OM_uint32 ret;
99 OM_uint32 minor, minor_ignore;
100 gss_cred_id_t credentials;
101 gss_buffer_desc input_name_buffer;
102 gss_name_t user_name;
104 /* Without SSO we need user name and password */
105 if (!username || !password) {
106 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__krb5: no valid authentication information provided");
107 return(FALSE);
110 /* Construct user name to acquire credentials for */
111 if (!is_empty(domain)) {
112 /* User specified a domain */
113 gchar *realm = g_ascii_strup(domain, -1);
115 username_new = g_strdup_printf("%s@%s",
116 username,
117 realm);
118 g_free(realm);
120 } else if (strchr(username, '@')) {
121 /* No domain, username matches XXX@YYY */
122 gchar **user_realm = g_strsplit(username, "@", 2);
123 gchar *realm = g_ascii_strup(user_realm[1], -1);
126 * We should escape the "@" to generate a enterprise
127 * principal, i.e. XXX\@YYY
129 * But krb5 libraries currently don't support this:
131 * http://krbdev.mit.edu/rt/Ticket/Display.html?id=7729
133 * username_new = g_strdup_printf("%s\\@%s",
135 username_new = g_strdup_printf("%s@%s",
136 user_realm[0],
137 realm);
138 g_free(realm);
139 g_strfreev(user_realm);
140 } else {
141 /* Otherwise use username as is */
142 username_new = g_strdup(username);
144 SIPE_DEBUG_INFO("sip_sec_acquire_cred__krb5: username '%s'",
145 username_new);
147 /* Import user name into GSS format */
148 input_name_buffer.value = (void *) username_new;
149 input_name_buffer.length = strlen(username_new) + 1;
151 ret = gss_import_name(&minor,
152 &input_name_buffer,
153 (gss_OID) GSS_C_NT_USER_NAME,
154 &user_name);
155 g_free(username_new);
157 if (GSS_ERROR(ret)) {
158 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
159 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__krb5: failed to construct user name (ret=%d)", (int)ret);
160 return(FALSE);
163 /* Acquire user credentials with password */
164 input_name_buffer.value = (void *) password;
165 input_name_buffer.length = strlen(password) + 1;
166 ret = gss_acquire_cred_with_password(&minor,
167 user_name,
168 &input_name_buffer,
169 GSS_C_INDEFINITE,
170 GSS_C_NO_OID_SET,
171 GSS_C_INITIATE,
172 &credentials,
173 NULL,
174 NULL);
175 gss_release_name(&minor_ignore, &user_name);
177 if (GSS_ERROR(ret)) {
178 sip_sec_krb5_print_gss_error("gss_acquire_cred_with_password", ret, minor);
179 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__krb5: failed to acquire credentials (ret=%d)", (int)ret);
180 return(FALSE);
181 } else {
182 ((context_krb5) context)->cred_krb5 = credentials;
183 return(TRUE);
185 #else
187 * non-SSO support requires gss_acquire_cred_with_password()
188 * which is not available on older GSSAPI releases.
190 (void) domain; /* keep compiler happy */
191 (void) username; /* keep compiler happy */
192 (void) password; /* keep compiler happy */
193 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_acquire_cred__krb5: non-SSO mode not supported");
194 return(FALSE);
195 #endif
198 return(TRUE);
201 static gboolean
202 sip_sec_init_sec_context__krb5(SipSecContext context,
203 SipSecBuffer in_buff,
204 SipSecBuffer *out_buff,
205 const gchar *service_name)
207 context_krb5 ctx = (context_krb5) context;
208 OM_uint32 ret;
209 OM_uint32 minor, minor_ignore;
210 OM_uint32 expiry;
211 gss_buffer_desc input_token;
212 gss_buffer_desc output_token;
213 gss_name_t target_name;
215 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
218 * If authentication was already completed, then this mean a new
219 * authentication handshake has started on the existing connection.
220 * We must throw away the old context, because we need a new one.
222 if ((context->flags & SIP_SEC_FLAG_COMMON_READY) &&
223 (ctx->ctx_krb5 != GSS_C_NO_CONTEXT)) {
224 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: dropping old context");
225 ret = gss_delete_sec_context(&minor,
226 &(ctx->ctx_krb5),
227 GSS_C_NO_BUFFER);
228 if (GSS_ERROR(ret)) {
229 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
230 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
232 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
233 context->flags &= ~SIP_SEC_FLAG_COMMON_READY;
236 /* Import service name to GSS */
237 input_token.value = (void *) service_name;
238 input_token.length = strlen(service_name) + 1;
240 ret = gss_import_name(&minor,
241 &input_token,
242 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
243 &target_name);
244 if (GSS_ERROR(ret)) {
245 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
246 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to construct target name (ret=%d)", (int)ret);
247 return(FALSE);
250 /* Create context */
251 input_token.length = in_buff.length;
252 input_token.value = in_buff.value;
254 output_token.length = 0;
255 output_token.value = NULL;
257 ret = gss_init_sec_context(&minor,
258 ctx->cred_krb5,
259 &(ctx->ctx_krb5),
260 target_name,
261 (gss_OID) gss_mech_krb5,
262 GSS_C_INTEG_FLAG,
263 GSS_C_INDEFINITE,
264 GSS_C_NO_CHANNEL_BINDINGS,
265 &input_token,
266 NULL,
267 &output_token,
268 NULL,
269 &expiry);
270 gss_release_name(&minor_ignore, &target_name);
272 if (GSS_ERROR(ret)) {
273 gss_release_buffer(&minor_ignore, &output_token);
274 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
275 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to initialize context (ret=%d)", (int)ret);
276 return(FALSE);
279 out_buff->length = output_token.length;
280 out_buff->value = g_memdup(output_token.value, output_token.length);
281 gss_release_buffer(&minor_ignore, &output_token);
283 context->expires = (int)expiry;
285 /* Authentication is completed */
286 context->flags |= SIP_SEC_FLAG_COMMON_READY;
288 return(TRUE);
292 * @param message a NULL terminated string to sign
294 static gboolean
295 sip_sec_make_signature__krb5(SipSecContext context,
296 const gchar *message,
297 SipSecBuffer *signature)
299 OM_uint32 ret;
300 OM_uint32 minor;
301 gss_buffer_desc input_message;
302 gss_buffer_desc output_token;
304 input_message.value = (void *)message;
305 input_message.length = strlen(input_message.value);
307 ret = gss_get_mic(&minor,
308 ((context_krb5)context)->ctx_krb5,
309 GSS_C_QOP_DEFAULT,
310 &input_message,
311 &output_token);
313 if (GSS_ERROR(ret)) {
314 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
315 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
316 return FALSE;
317 } else {
318 signature->length = output_token.length;
319 signature->value = g_memdup(output_token.value,
320 output_token.length);
321 gss_release_buffer(&minor, &output_token);
322 return TRUE;
327 * @param message a NULL terminated string to check signature of
329 static gboolean
330 sip_sec_verify_signature__krb5(SipSecContext context,
331 const gchar *message,
332 SipSecBuffer signature)
334 OM_uint32 ret;
335 OM_uint32 minor;
336 gss_buffer_desc input_message;
337 gss_buffer_desc input_token;
339 input_message.value = (void *)message;
340 input_message.length = strlen(input_message.value);
342 input_token.value = signature.value;
343 input_token.length = signature.length;
345 ret = gss_verify_mic(&minor,
346 ((context_krb5)context)->ctx_krb5,
347 &input_message,
348 &input_token,
349 NULL);
351 if (GSS_ERROR(ret)) {
352 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
353 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
354 return FALSE;
355 } else {
356 return TRUE;
360 static void
361 sip_sec_destroy_sec_context__krb5(SipSecContext context)
363 context_krb5 ctx = (context_krb5) context;
364 OM_uint32 ret;
365 OM_uint32 minor;
367 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
368 ret = gss_delete_sec_context(&minor, &(ctx->ctx_krb5), GSS_C_NO_BUFFER);
369 if (GSS_ERROR(ret)) {
370 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
371 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
373 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
376 if (ctx->cred_krb5 != GSS_C_NO_CREDENTIAL) {
377 ret = gss_release_cred(&minor, &(ctx->cred_krb5));
378 if (GSS_ERROR(ret)) {
379 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
380 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to release credentials (ret=%d)", (int)ret);
382 ctx->cred_krb5 = GSS_C_NO_CREDENTIAL;
385 g_free(context);
388 static const gchar *
389 sip_sec_context_name__krb5(SIPE_UNUSED_PARAMETER SipSecContext context)
391 return("Kerberos");
394 SipSecContext
395 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
397 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
398 if (!context) return(NULL);
400 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
401 context->common.init_context_func = sip_sec_init_sec_context__krb5;
402 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
403 context->common.make_signature_func = sip_sec_make_signature__krb5;
404 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
405 context->common.context_name_func = sip_sec_context_name__krb5;
407 context->cred_krb5 = GSS_C_NO_CREDENTIAL;
408 context->ctx_krb5 = GSS_C_NO_CONTEXT;
410 return((SipSecContext) context);
413 gboolean sip_sec_password__krb5(void)
415 /* Kerberos supports Single-Sign On */
416 return(FALSE);
420 Local Variables:
421 mode: c
422 c-file-style: "bsd"
423 indent-tabs-mode: t
424 tab-width: 8
425 End: