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
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_krb5.h>
31 #include "sipe-common.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
;
43 const gchar
*username
;
44 const gchar
*password
;
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
)
58 if (context
->ctx_krb5
!= GSS_C_NO_CONTEXT
) {
59 ret
= gss_delete_sec_context(&minor
, &(context
->ctx_krb5
), GSS_C_NO_BUFFER
);
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
));
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
)
81 gss_cred_id_t credentials
;
83 /* Acquire default user credentials */
84 ret
= gss_acquire_cred(&minor
,
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
);
98 context
->cred_krb5
= credentials
;
103 static gboolean
sip_sec_krb5_initialize_context(context_krb5 context
,
104 SipSecBuffer in_buff
,
105 SipSecBuffer
*out_buff
,
106 const gchar
*service_name
)
109 OM_uint32 minor
, minor_ignore
;
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
,
121 (gss_OID
) GSS_KRB5_NT_PRINCIPAL_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
);
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
,
137 &(context
->ctx_krb5
),
142 GSS_C_NO_CHANNEL_BINDINGS
,
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
);
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
;
169 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
172 sip_sec_acquire_cred__krb5(SipSecContext context
,
174 const gchar
*username
,
175 const gchar
*password
)
177 context_krb5 ctx
= (context_krb5
) context
;
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
;
217 sip_sec_init_sec_context__krb5(SipSecContext context
,
218 SipSecBuffer in_buff
,
219 SipSecBuffer
*out_buff
,
220 const gchar
*service_name
)
224 context_krb5 ctx
= (context_krb5
) context
;
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
,
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
,
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
,
261 /* Only retry once */
262 context
->flags
&= ~SIP_SEC_FLAG_KRB5_RETRY_AUTH
;
268 * @param message a NULL terminated string to sign
271 sip_sec_make_signature__krb5(SipSecContext context
,
272 const gchar
*message
,
273 SipSecBuffer
*signature
)
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
,
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
);
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
);
303 * @param message a NULL terminated string to check signature of
306 sip_sec_verify_signature__krb5(SipSecContext context
,
307 const gchar
*message
,
308 SipSecBuffer signature
)
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
,
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
);
337 sip_sec_destroy_sec_context__krb5(SipSecContext context
)
339 sip_sec_krb5_destroy_context((context_krb5
) context
);
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 */
367 sip_sec_krb5_print_gss_error0(char *func
,
372 OM_uint32 message_context
= 0;
373 gss_buffer_desc status_string
;
376 gss_display_status(&minor
,
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
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
;
423 if (!ctx
->username
&& !ctx
->password
) {
424 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_krb5_obtain_tgt: no valid authentication information provided");
428 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
430 user_realm
= g_strsplit(ctx
->username
, "@", 2);
432 /* "user@domain" -> use domain as realm */
433 realm
= g_ascii_strup(user_realm
[1], -1);
434 username
= g_strdup(user_realm
[0]);
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
);
443 ret
= krb5_init_context(&context
);
445 sip_sec_krb5_print_error("krb5_init_context", context
, ret
);
447 krb5_principal principal
= NULL
;
449 ret
= krb5_build_principal(context
, &principal
, strlen(realm
), realm
, username
, NULL
);
451 sip_sec_krb5_print_error("krb5_build_principal", context
, ret
);
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
);
459 sip_sec_krb5_print_error("krb5_get_init_creds_password", context
, ret
);
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
);
468 sip_sec_krb5_print_error("krb5_cc_default", context
, ret
);
470 /* First try without initializing */
471 ret
= krb5_cc_store_cred(context
, ccdef
, &credentials
);
473 ret
= krb5_cc_initialize(context
, ccdef
, credentials
.client
);
475 sip_sec_krb5_print_error("krb5_cc_initialize", context
, ret
);
477 /* Second try after initializing the default credential cache */
478 ret
= krb5_cc_store_cred(context
, ccdef
, &credentials
);
480 sip_sec_krb5_print_error("krb5_cc_store_cred", context
, ret
);
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
);
501 sip_sec_krb5_print_error(const gchar
*func
,
502 krb5_context context
,
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
);