security: drop useless gss_acquire_cred()
[siplcs.git] / src / core / sip-sec-krb5.c
blob00c35ff9a6b6b40d762c156247d82ba07ba9e4fa
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_ctx_id_t ctx_krb5;
41 const gchar *domain;
42 const gchar *username;
43 const gchar *password;
44 } *context_krb5;
46 #define SIP_SEC_FLAG_KRB5_RETRY_AUTH 0x00010000
48 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor);
50 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 context);
52 static void sip_sec_krb5_destroy_context(context_krb5 context)
54 OM_uint32 ret;
55 OM_uint32 minor;
57 if (context->ctx_krb5 != GSS_C_NO_CONTEXT) {
58 ret = gss_delete_sec_context(&minor, &(context->ctx_krb5), GSS_C_NO_BUFFER);
59 if (GSS_ERROR(ret)) {
60 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
61 SIPE_DEBUG_ERROR("sip_sec_krb5_destroy_context: failed to delete security context (ret=%d)", (int)ret);
63 context->ctx_krb5 = GSS_C_NO_CONTEXT;
67 static gboolean sip_sec_krb5_initialize_context(context_krb5 context,
68 SipSecBuffer in_buff,
69 SipSecBuffer *out_buff,
70 const gchar *service_name)
72 OM_uint32 ret;
73 OM_uint32 minor, minor_ignore;
74 OM_uint32 expiry;
75 gss_buffer_desc input_token;
76 gss_buffer_desc output_token;
77 gss_buffer_desc input_name_buffer;
78 gss_name_t target_name;
80 input_name_buffer.value = (void *) service_name;
81 input_name_buffer.length = strlen(service_name) + 1;
83 ret = gss_import_name(&minor,
84 &input_name_buffer,
85 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
86 &target_name);
87 if (GSS_ERROR(ret)) {
88 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
89 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to construct target name (ret=%d)", (int)ret);
90 return(FALSE);
93 input_token.length = in_buff.length;
94 input_token.value = in_buff.value;
96 output_token.length = 0;
97 output_token.value = NULL;
99 ret = gss_init_sec_context(&minor,
100 GSS_C_NO_CREDENTIAL,
101 &(context->ctx_krb5),
102 target_name,
103 GSS_C_NO_OID,
104 GSS_C_INTEG_FLAG,
105 GSS_C_INDEFINITE,
106 GSS_C_NO_CHANNEL_BINDINGS,
107 &input_token,
108 NULL,
109 &output_token,
110 NULL,
111 &expiry);
112 gss_release_name(&minor_ignore, &target_name);
114 if (GSS_ERROR(ret)) {
115 gss_release_buffer(&minor_ignore, &output_token);
116 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
117 SIPE_DEBUG_ERROR("sip_sec_krb5_initialize_context: failed to initialize context (ret=%d)", (int)ret);
118 return(FALSE);
121 out_buff->length = output_token.length;
122 out_buff->value = g_memdup(output_token.value, output_token.length);
123 gss_release_buffer(&minor_ignore, &output_token);
125 context->common.expires = (int)expiry;
127 /* Authentication is completed */
128 context->common.flags |= SIP_SEC_FLAG_COMMON_READY;
130 return(TRUE);
133 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
135 static gboolean
136 sip_sec_acquire_cred__krb5(SipSecContext context,
137 const gchar *domain,
138 const gchar *username,
139 const gchar *password)
141 context_krb5 ctx = (context_krb5) context;
143 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_acquire_cred__krb5: started");
145 /* remember authentication information */
146 ctx->domain = domain ? domain : "";
147 ctx->username = username;
148 ctx->password = password;
151 * This will be TRUE for SIP, which is the first time when we'll try
152 * to authenticate with Kerberos. We'll allow one retry with the
153 * authentication information provided by the user (see above).
155 * This will be FALSE for HTTP connections. If Kerberos authentication
156 * succeeded for SIP already, then there is no need to retry.
158 if ((context->flags & SIP_SEC_FLAG_COMMON_HTTP) == 0)
159 context->flags |= SIP_SEC_FLAG_KRB5_RETRY_AUTH;
161 return(TRUE);
164 static gboolean
165 sip_sec_init_sec_context__krb5(SipSecContext context,
166 SipSecBuffer in_buff,
167 SipSecBuffer *out_buff,
168 const gchar *service_name)
170 OM_uint32 ret;
171 OM_uint32 minor;
172 context_krb5 ctx = (context_krb5) context;
173 gboolean result;
175 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
177 /* Delete old context first */
178 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
179 ret = gss_delete_sec_context(&minor,
180 &(ctx->ctx_krb5),
181 GSS_C_NO_BUFFER);
182 if (GSS_ERROR(ret)) {
183 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
184 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
186 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
189 result = sip_sec_krb5_initialize_context(ctx,
190 in_buff,
191 out_buff,
192 service_name);
195 * If context initialization fails then retry after trying to obtaining
196 * a TGT. This will will only succeed if we have been provided with
197 * valid authentication information by the user.
199 if (!result && (context->flags & SIP_SEC_FLAG_KRB5_RETRY_AUTH)) {
200 sip_sec_krb5_destroy_context(ctx);
201 result = sip_sec_krb5_obtain_tgt(ctx) &&
202 sip_sec_krb5_initialize_context(ctx,
203 in_buff,
204 out_buff,
205 service_name);
208 /* Only retry once */
209 context->flags &= ~SIP_SEC_FLAG_KRB5_RETRY_AUTH;
211 return(result);
215 * @param message a NULL terminated string to sign
217 static gboolean
218 sip_sec_make_signature__krb5(SipSecContext context,
219 const gchar *message,
220 SipSecBuffer *signature)
222 OM_uint32 ret;
223 OM_uint32 minor;
224 gss_buffer_desc input_message;
225 gss_buffer_desc output_token;
227 input_message.value = (void *)message;
228 input_message.length = strlen(input_message.value);
230 ret = gss_get_mic(&minor,
231 ((context_krb5)context)->ctx_krb5,
232 GSS_C_QOP_DEFAULT,
233 &input_message,
234 &output_token);
236 if (GSS_ERROR(ret)) {
237 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
238 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
239 return FALSE;
240 } else {
241 signature->length = output_token.length;
242 signature->value = g_memdup(output_token.value,
243 output_token.length);
244 gss_release_buffer(&minor, &output_token);
245 return TRUE;
250 * @param message a NULL terminated string to check signature of
252 static gboolean
253 sip_sec_verify_signature__krb5(SipSecContext context,
254 const gchar *message,
255 SipSecBuffer signature)
257 OM_uint32 ret;
258 OM_uint32 minor;
259 gss_buffer_desc input_message;
260 gss_buffer_desc input_token;
262 input_message.value = (void *)message;
263 input_message.length = strlen(input_message.value);
265 input_token.value = signature.value;
266 input_token.length = signature.length;
268 ret = gss_verify_mic(&minor,
269 ((context_krb5)context)->ctx_krb5,
270 &input_message,
271 &input_token,
272 NULL);
274 if (GSS_ERROR(ret)) {
275 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
276 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
277 return FALSE;
278 } else {
279 return TRUE;
283 static void
284 sip_sec_destroy_sec_context__krb5(SipSecContext context)
286 sip_sec_krb5_destroy_context((context_krb5) context);
287 g_free(context);
290 SipSecContext
291 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
293 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
294 if (!context) return(NULL);
296 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
297 context->common.init_context_func = sip_sec_init_sec_context__krb5;
298 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
299 context->common.make_signature_func = sip_sec_make_signature__krb5;
300 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
302 context->ctx_krb5 = GSS_C_NO_CONTEXT;
304 return((SipSecContext) context);
307 gboolean sip_sec_password__krb5(void)
309 /* Kerberos supports Single-Sign On */
310 return(FALSE);
313 static void
314 sip_sec_krb5_print_gss_error0(char *func,
315 OM_uint32 status,
316 int type)
318 OM_uint32 minor;
319 OM_uint32 message_context = 0;
320 gss_buffer_desc status_string;
322 do {
323 gss_display_status(&minor,
324 status,
325 type,
326 GSS_C_NO_OID,
327 &message_context,
328 &status_string);
330 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);
331 gss_release_buffer(&minor, &status_string);
332 } while (message_context != 0);
336 * Prints out errors of GSS-API function invocation
338 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
340 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
341 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
345 * Prints out errors of Kerberos 5 function invocation
347 static void
348 sip_sec_krb5_print_error(const gchar *func,
349 krb5_context context,
350 krb5_error_code ret);
353 * Obtains Kerberos TGT and stores it in default credentials cache.
354 * Similar what kinit util would do.
355 * Can be checked with klist util.
357 * kinit would require the following name:
358 * alice@ATLANTA.LOCAL
359 * where 'alice' is a username and
360 * 'ATLANTA.LOCAL' is a realm (domain) .
362 static gboolean sip_sec_krb5_obtain_tgt(context_krb5 ctx)
364 krb5_context context = NULL;
365 krb5_error_code ret;
366 char *realm;
367 char *username;
368 gchar **user_realm;
370 if (!ctx->username && !ctx->password) {
371 SIPE_DEBUG_ERROR_NOFORMAT("sip_sec_krb5_obtain_tgt: no valid authentication information provided");
372 return(FALSE);
375 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
377 user_realm = g_strsplit(ctx->username, "@", 2);
378 if (user_realm[1]) {
379 /* "user@domain" -> use domain as realm */
380 realm = g_ascii_strup(user_realm[1], -1);
381 username = g_strdup(user_realm[0]);
382 } else {
383 /* use provided domain as realm */
384 realm = g_ascii_strup(ctx->domain, -1);
385 username = g_strdup(ctx->username);
387 g_strfreev(user_realm);
389 /* Obtait TGT */
390 ret = krb5_init_context(&context);
391 if (ret) {
392 sip_sec_krb5_print_error("krb5_init_context", context, ret);
393 } else {
394 krb5_principal principal = NULL;
396 ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL);
397 if (ret) {
398 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
399 } else {
400 krb5_creds credentials;
402 memset(&credentials, 0, sizeof(krb5_creds));
404 ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)ctx->password, NULL, NULL, 0, NULL, NULL);
405 if (ret) {
406 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
407 } else {
408 krb5_ccache ccdef = NULL;
410 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT obtained");
412 /* Store TGT in default credential cache */
413 ret = krb5_cc_default(context, &ccdef);
414 if (ret) {
415 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
416 } else {
417 /* First try without initializing */
418 ret = krb5_cc_store_cred(context, ccdef, &credentials);
419 if (ret) {
420 ret = krb5_cc_initialize(context, ccdef, credentials.client);
421 if (ret) {
422 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
423 } else {
424 /* Second try after initializing the default credential cache */
425 ret = krb5_cc_store_cred(context, ccdef, &credentials);
426 if (ret) {
427 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
428 } else {
429 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache");
433 krb5_cc_close(context, ccdef);
435 krb5_free_cred_contents(context, &credentials);
437 krb5_free_principal(context, principal);
439 krb5_free_context(context);
441 g_free(username);
442 g_free(realm);
444 return(!ret);
447 static void
448 sip_sec_krb5_print_error(const gchar *func,
449 krb5_context context,
450 krb5_error_code ret)
452 const gchar *error_message = krb5_get_error_message(context, ret);
453 SIPE_DEBUG_ERROR("Kerberos 5 ERROR in %s: %s", func, error_message);
454 krb5_free_error_message(context, error_message);
458 Local Variables:
459 mode: c
460 c-file-style: "bsd"
461 indent-tabs-mode: t
462 tab-width: 8
463 End: