security: obtain ticket on acquire credential failure
[siplcs.git] / src / core / sip-sec-krb5.c
blob1302e45c84b6b6d7ff15fcc315106d7a98dffe38
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 #include <glib.h>
26 #include <string.h>
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_krb5.h>
29 #include <krb5.h>
31 #include "sipe-common.h"
32 #include "sip-sec.h"
33 #include "sip-sec-mech.h"
34 #include "sip-sec-krb5.h"
35 #include "sipe-backend.h"
37 /* Security context for Kerberos */
38 typedef struct _context_krb5 {
39 struct sip_sec_context common;
40 gss_cred_id_t cred_krb5;
41 gss_ctx_id_t ctx_krb5;
42 const gchar *domain;
43 const gchar *username;
44 const gchar *password;
45 } *context_krb5;
47 #define SIP_SEC_FLAG_KRB5_RETRY_AUTH 0x00010000
49 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor);
51 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 context);
53 static void sip_sec_krb5_destroy_context(context_krb5 context)
55 OM_uint32 ret;
56 OM_uint32 minor;
58 if (context->ctx_krb5 != GSS_C_NO_CONTEXT) {
59 ret = gss_delete_sec_context(&minor, &(context->ctx_krb5), GSS_C_NO_BUFFER);
60 if (GSS_ERROR(ret)) {
61 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
62 SIPE_DEBUG_ERROR("sip_sec_krb5_destroy_context: failed to delete security context (ret=%d)", (int)ret);
64 context->ctx_krb5 = GSS_C_NO_CONTEXT;
67 if (context->cred_krb5) {
68 ret = gss_release_cred(&minor, &(context->cred_krb5));
69 if (GSS_ERROR(ret)) {
70 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
71 SIPE_DEBUG_ERROR("sip_sec_krb5_destroy_context: failed to release credentials (ret=%d)", (int)ret);
73 context->cred_krb5 = NULL;
77 static gboolean sip_sec_krb5_acquire_credentials(context_krb5 context)
79 OM_uint32 ret;
80 OM_uint32 minor;
81 gss_cred_id_t credentials;
83 /* Acquire default user credentials */
84 ret = gss_acquire_cred(&minor,
85 GSS_C_NO_NAME,
86 GSS_C_INDEFINITE,
87 GSS_C_NO_OID_SET,
88 GSS_C_INITIATE,
89 &credentials,
90 NULL,
91 NULL);
93 if (GSS_ERROR(ret)) {
94 sip_sec_krb5_print_gss_error("gss_acquire_cred", ret, minor);
95 SIPE_DEBUG_ERROR("sip_sec_krb5_acquire_credentials: failed to acquire credentials (ret=%d)", (int)ret);
96 return(FALSE);
97 } else {
98 context->cred_krb5 = credentials;
99 return(TRUE);
103 static gboolean sip_sec_krb5_initialize_context(context_krb5 context,
104 SipSecBuffer in_buff,
105 SipSecBuffer *out_buff,
106 const gchar *service_name)
108 OM_uint32 ret;
109 OM_uint32 minor, minor_ignore;
110 OM_uint32 expiry;
111 gss_buffer_desc input_token;
112 gss_buffer_desc output_token;
113 gss_buffer_desc input_name_buffer;
114 gss_name_t target_name;
116 input_name_buffer.value = (void *) service_name;
117 input_name_buffer.length = strlen(service_name) + 1;
119 ret = gss_import_name(&minor,
120 &input_name_buffer,
121 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
122 &target_name);
123 if (GSS_ERROR(ret)) {
124 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
125 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to construct target name (ret=%d)", (int)ret);
126 return(FALSE);
129 input_token.length = in_buff.length;
130 input_token.value = in_buff.value;
132 output_token.length = 0;
133 output_token.value = NULL;
135 ret = gss_init_sec_context(&minor,
136 context->cred_krb5,
137 &(context->ctx_krb5),
138 target_name,
139 GSS_C_NO_OID,
140 GSS_C_INTEG_FLAG,
141 GSS_C_INDEFINITE,
142 GSS_C_NO_CHANNEL_BINDINGS,
143 &input_token,
144 NULL,
145 &output_token,
146 NULL,
147 &expiry);
148 gss_release_name(&minor_ignore, &target_name);
150 if (GSS_ERROR(ret)) {
151 gss_release_buffer(&minor_ignore, &output_token);
152 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
153 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to initialize context (ret=%d)", (int)ret);
154 return(FALSE);
157 out_buff->length = output_token.length;
158 out_buff->value = g_memdup(output_token.value, output_token.length);
159 gss_release_buffer(&minor_ignore, &output_token);
161 context->common.expires = (int)expiry;
163 /* Authentication is completed */
164 context->common.flags |= SIP_SEC_FLAG_COMMON_READY;
166 return(TRUE);
169 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
171 static gboolean
172 sip_sec_acquire_cred__krb5(SipSecContext context,
173 const gchar *domain,
174 const gchar *username,
175 const gchar *password)
177 context_krb5 ctx = (context_krb5) context;
178 gboolean result;
180 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__krb5: started");
182 /* remember authentication information */
183 ctx->domain = domain ? domain : "";
184 ctx->username = username;
185 ctx->password = password;
188 * This will be TRUE for SIP, which is the first time when we'll try
189 * to authenticate with Kerberos. We'll allow one retry with the
190 * authentication information provided by the user (see above).
192 * This will be FALSE for HTTP connections. If Kerberos authentication
193 * succeeded for SIP already, then there is no need to retry.
195 if ((context->flags & SIP_SEC_FLAG_COMMON_HTTP) == 0)
196 context->flags |= SIP_SEC_FLAG_KRB5_RETRY_AUTH;
198 result = sip_sec_krb5_acquire_credentials(ctx);
201 * If acquire credentials fails then retry after trying to obtaining
202 * a TGT. This will will only succeed if we have been provided with
203 * valid authentication information by the user.
205 if (!result && (context->flags & SIP_SEC_FLAG_KRB5_RETRY_AUTH)) {
206 result = sip_sec_krb5_obtain_tgt(ctx) &&
207 sip_sec_krb5_acquire_credentials(ctx);
209 /* Only retry once */
210 context->flags &= ~SIP_SEC_FLAG_KRB5_RETRY_AUTH;
213 return(result);
216 static gboolean
217 sip_sec_init_sec_context__krb5(SipSecContext context,
218 SipSecBuffer in_buff,
219 SipSecBuffer *out_buff,
220 const gchar *service_name)
222 OM_uint32 ret;
223 OM_uint32 minor;
224 context_krb5 ctx = (context_krb5) context;
225 gboolean result;
227 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
229 /* Delete old context first */
230 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
231 ret = gss_delete_sec_context(&minor,
232 &(ctx->ctx_krb5),
233 GSS_C_NO_BUFFER);
234 if (GSS_ERROR(ret)) {
235 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
236 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
238 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
241 result = sip_sec_krb5_initialize_context(ctx,
242 in_buff,
243 out_buff,
244 service_name);
247 * If context initialization fails then retry after trying to obtaining
248 * a TGT. This will will only succeed if we have been provided with
249 * valid authentication information by the user.
251 if (!result && (context->flags & SIP_SEC_FLAG_KRB5_RETRY_AUTH)) {
252 sip_sec_krb5_destroy_context(ctx);
253 result = sip_sec_krb5_obtain_tgt(ctx) &&
254 sip_sec_krb5_acquire_credentials(ctx) &&
255 sip_sec_krb5_initialize_context(ctx,
256 in_buff,
257 out_buff,
258 service_name);
261 /* Only retry once */
262 context->flags &= ~SIP_SEC_FLAG_KRB5_RETRY_AUTH;
264 return(result);
268 * @param message a NULL terminated string to sign
270 static gboolean
271 sip_sec_make_signature__krb5(SipSecContext context,
272 const gchar *message,
273 SipSecBuffer *signature)
275 OM_uint32 ret;
276 OM_uint32 minor;
277 gss_buffer_desc input_message;
278 gss_buffer_desc output_token;
280 input_message.value = (void *)message;
281 input_message.length = strlen(input_message.value);
283 ret = gss_get_mic(&minor,
284 ((context_krb5)context)->ctx_krb5,
285 GSS_C_QOP_DEFAULT,
286 &input_message,
287 &output_token);
289 if (GSS_ERROR(ret)) {
290 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
291 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
292 return FALSE;
293 } else {
294 signature->length = output_token.length;
295 signature->value = g_memdup(output_token.value,
296 output_token.length);
297 gss_release_buffer(&minor, &output_token);
298 return TRUE;
303 * @param message a NULL terminated string to check signature of
305 static gboolean
306 sip_sec_verify_signature__krb5(SipSecContext context,
307 const gchar *message,
308 SipSecBuffer signature)
310 OM_uint32 ret;
311 OM_uint32 minor;
312 gss_buffer_desc input_message;
313 gss_buffer_desc input_token;
315 input_message.value = (void *)message;
316 input_message.length = strlen(input_message.value);
318 input_token.value = signature.value;
319 input_token.length = signature.length;
321 ret = gss_verify_mic(&minor,
322 ((context_krb5)context)->ctx_krb5,
323 &input_message,
324 &input_token,
325 NULL);
327 if (GSS_ERROR(ret)) {
328 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
329 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
330 return FALSE;
331 } else {
332 return TRUE;
336 static void
337 sip_sec_destroy_sec_context__krb5(SipSecContext context)
339 sip_sec_krb5_destroy_context((context_krb5) context);
340 g_free(context);
343 SipSecContext
344 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
346 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
347 if (!context) return(NULL);
349 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
350 context->common.init_context_func = sip_sec_init_sec_context__krb5;
351 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
352 context->common.make_signature_func = sip_sec_make_signature__krb5;
353 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
355 context->ctx_krb5 = GSS_C_NO_CONTEXT;
357 return((SipSecContext) context);
360 gboolean sip_sec_password__krb5(void)
362 /* Kerberos supports Single-Sign On */
363 return(FALSE);
366 static void
367 sip_sec_krb5_print_gss_error0(char *func,
368 OM_uint32 status,
369 int type)
371 OM_uint32 minor;
372 OM_uint32 message_context = 0;
373 gss_buffer_desc status_string;
375 do {
376 gss_display_status(&minor,
377 status,
378 type,
379 GSS_C_NO_OID,
380 &message_context,
381 &status_string);
383 SIPE_DEBUG_ERROR("sip_sec_krb5: GSS-API error in %s (%s): %s", func, (type == GSS_C_GSS_CODE ? "GSS" : "Mech"), (char *)status_string.value);
384 gss_release_buffer(&minor, &status_string);
385 } while (message_context != 0);
389 * Prints out errors of GSS-API function invocation
391 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
393 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
394 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
398 * Prints out errors of Kerberos 5 function invocation
400 static void
401 sip_sec_krb5_print_error(const gchar *func,
402 krb5_context context,
403 krb5_error_code ret);
406 * Obtains Kerberos TGT and stores it in default credentials cache.
407 * Similar what kinit util would do.
408 * Can be checked with klist util.
410 * kinit would require the following name:
411 * alice@ATLANTA.LOCAL
412 * where 'alice' is a username and
413 * 'ATLANTA.LOCAL' is a realm (domain) .
415 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 ctx)
417 krb5_context context = NULL;
418 krb5_error_code ret;
419 char *realm;
420 char *username;
421 gchar **user_realm;
423 if (!ctx->username && !ctx->password) {
424 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_krb5_obtain_tgt: no valid authentication information provided");
425 return(FALSE);
428 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
430 user_realm = g_strsplit(ctx->username, "@", 2);
431 if (user_realm[1]) {
432 /* "user@domain" -> use domain as realm */
433 realm = g_ascii_strup(user_realm[1], -1);
434 username = g_strdup(user_realm[0]);
435 } else {
436 /* use provided domain as realm */
437 realm = g_ascii_strup(ctx->domain, -1);
438 username = g_strdup(ctx->username);
440 g_strfreev(user_realm);
442 /* Obtait TGT */
443 ret = krb5_init_context(&context);
444 if (ret) {
445 sip_sec_krb5_print_error("krb5_init_context", context, ret);
446 } else {
447 krb5_principal principal = NULL;
449 ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL);
450 if (ret) {
451 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
452 } else {
453 krb5_creds credentials;
455 memset(&credentials, 0, sizeof(krb5_creds));
457 ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)ctx->password, NULL, NULL, 0, NULL, NULL);
458 if (ret) {
459 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
460 } else {
461 krb5_ccache ccdef = NULL;
463 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT obtained");
465 /* Store TGT in default credential cache */
466 ret = krb5_cc_default(context, &ccdef);
467 if (ret) {
468 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
469 } else {
470 /* First try without initializing */
471 ret = krb5_cc_store_cred(context, ccdef, &credentials);
472 if (ret) {
473 ret = krb5_cc_initialize(context, ccdef, credentials.client);
474 if (ret) {
475 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
476 } else {
477 /* Second try after initializing the default credential cache */
478 ret = krb5_cc_store_cred(context, ccdef, &credentials);
479 if (ret) {
480 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
481 } else {
482 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache");
486 krb5_cc_close(context, ccdef);
488 krb5_free_cred_contents(context, &credentials);
490 krb5_free_principal(context, principal);
492 krb5_free_context(context);
494 g_free(username);
495 g_free(realm);
497 return(!ret);
500 static void
501 sip_sec_krb5_print_error(const gchar *func,
502 krb5_context context,
503 krb5_error_code ret)
505 const gchar *error_message = krb5_get_error_message(context, ret);
506 SIPE_DEBUG_ERROR("Kerberos 5 ERROR in %s: %s", func, error_message);
507 krb5_free_error_message(context, error_message);
511 Local Variables:
512 mode: c
513 c-file-style: "bsd"
514 indent-tabs-mode: t
515 tab-width: 8
516 End: