security: fix target name memory leak in Kerberos
[siplcs.git] / src / core / sip-sec-krb5.c
blob1e4e7d3ac309446b2032492fed9cd0a89dd484a8
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 } *context_krb5;
44 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor);
46 static void
47 sip_sec_krb5_obtain_tgt(const char *domain,
48 const char *username,
49 const char *password);
51 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
53 /**
54 * Depending on Single Sign-On flag (sso),
55 * obtains existing credentials stored in credentials cash in case of Kerberos,
56 * or attemps to obtain TGT on its own first.
58 static sip_uint32
59 sip_sec_acquire_cred__krb5(SipSecContext context,
60 const char *domain,
61 const char *username,
62 const char *password)
64 OM_uint32 ret;
65 OM_uint32 minor;
66 gss_cred_id_t credentials;
68 if (!context->sso) {
69 /* Do not use default credentials, obtain a new one and store it in cache */
70 sip_sec_krb5_obtain_tgt(domain, username, password);
73 /* Acquire default user credentials */
74 ret = gss_acquire_cred(&minor,
75 GSS_C_NO_NAME,
76 GSS_C_INDEFINITE,
77 GSS_C_NO_OID_SET,
78 GSS_C_INITIATE,
79 &credentials,
80 NULL,
81 NULL);
83 if (GSS_ERROR(ret)) {
84 sip_sec_krb5_print_gss_error("gss_acquire_cred", ret, minor);
85 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__krb5: failed to acquire credentials (ret=%d)", (int)ret);
86 return SIP_SEC_E_INTERNAL_ERROR;
87 } else {
88 ((context_krb5)context)->cred_krb5 = credentials;
89 return SIP_SEC_E_OK;
93 static sip_uint32
94 sip_sec_init_sec_context__krb5(SipSecContext context,
95 SipSecBuffer in_buff,
96 SipSecBuffer *out_buff,
97 const char *service_name)
99 OM_uint32 ret;
100 OM_uint32 minor, minor_ignore;
101 OM_uint32 expiry;
102 gss_buffer_desc input_token;
103 gss_buffer_desc output_token;
104 gss_buffer_desc input_name_buffer;
105 gss_name_t target_name;
106 context_krb5 ctx = (context_krb5) context;
108 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
110 /* Delete old context first */
111 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
112 ret = gss_delete_sec_context(&minor,
113 &(ctx->ctx_krb5),
114 GSS_C_NO_BUFFER);
115 if (GSS_ERROR(ret)) {
116 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
117 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
119 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
122 input_name_buffer.value = (void *) service_name;
123 input_name_buffer.length = strlen(service_name) + 1;
125 ret = gss_import_name(&minor,
126 &input_name_buffer,
127 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
128 &target_name);
129 if (GSS_ERROR(ret)) {
130 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
131 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to construct target name (ret=%d)", (int)ret);
132 return SIP_SEC_E_INTERNAL_ERROR;
135 input_token.length = in_buff.length;
136 input_token.value = in_buff.value;
138 output_token.length = 0;
139 output_token.value = NULL;
141 ret = gss_init_sec_context(&minor,
142 ctx->cred_krb5,
143 &(ctx->ctx_krb5),
144 target_name,
145 GSS_C_NO_OID,
146 GSS_C_INTEG_FLAG,
147 GSS_C_INDEFINITE,
148 GSS_C_NO_CHANNEL_BINDINGS,
149 &input_token,
150 NULL,
151 &output_token,
152 NULL,
153 &expiry);
154 gss_release_name(&minor_ignore, &target_name);
156 if (GSS_ERROR(ret)) {
157 gss_release_buffer(&minor_ignore, &output_token);
158 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
159 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to initialize context (ret=%d)", (int)ret);
160 return SIP_SEC_E_INTERNAL_ERROR;
163 out_buff->length = output_token.length;
164 out_buff->value = g_memdup(output_token.value, output_token.length);
165 gss_release_buffer(&minor_ignore, &output_token);
167 context->expires = (int)expiry;
169 /* Authentication is completed */
170 context->is_ready = TRUE;
172 return SIP_SEC_E_OK;
176 * @param message a NULL terminated string to sign
178 static sip_uint32
179 sip_sec_make_signature__krb5(SipSecContext context,
180 const char *message,
181 SipSecBuffer *signature)
183 OM_uint32 ret;
184 OM_uint32 minor;
185 gss_buffer_desc input_message;
186 gss_buffer_desc output_token;
188 input_message.value = (void *)message;
189 input_message.length = strlen(input_message.value);
191 ret = gss_get_mic(&minor,
192 ((context_krb5)context)->ctx_krb5,
193 GSS_C_QOP_DEFAULT,
194 &input_message,
195 &output_token);
197 if (GSS_ERROR(ret)) {
198 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
199 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
200 return SIP_SEC_E_INTERNAL_ERROR;
201 } else {
202 signature->length = output_token.length;
203 signature->value = g_memdup(output_token.value,
204 output_token.length);
205 gss_release_buffer(&minor, &output_token);
206 return SIP_SEC_E_OK;
211 * @param message a NULL terminated string to check signature of
213 static sip_uint32
214 sip_sec_verify_signature__krb5(SipSecContext context,
215 const char *message,
216 SipSecBuffer signature)
218 OM_uint32 ret;
219 OM_uint32 minor;
220 gss_buffer_desc input_message;
221 gss_buffer_desc input_token;
223 input_message.value = (void *)message;
224 input_message.length = strlen(input_message.value);
226 input_token.value = signature.value;
227 input_token.length = signature.length;
229 ret = gss_verify_mic(&minor,
230 ((context_krb5)context)->ctx_krb5,
231 &input_message,
232 &input_token,
233 NULL);
235 if (GSS_ERROR(ret)) {
236 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
237 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
238 return SIP_SEC_E_INTERNAL_ERROR;
239 } else {
240 return SIP_SEC_E_OK;
244 static void
245 sip_sec_destroy_sec_context__krb5(SipSecContext context)
247 OM_uint32 ret;
248 OM_uint32 minor;
249 context_krb5 ctx = (context_krb5) context;
251 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
252 ret = gss_delete_sec_context(&minor, &(ctx->ctx_krb5), GSS_C_NO_BUFFER);
253 if (GSS_ERROR(ret)) {
254 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
255 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
259 if (ctx->cred_krb5) {
260 ret = gss_release_cred(&minor, &(ctx->cred_krb5));
261 if (GSS_ERROR(ret)) {
262 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
263 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to release credentials (ret=%d)", (int)ret);
267 g_free(ctx);
270 SipSecContext
271 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
273 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
274 if (!context) return(NULL);
276 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
277 context->common.init_context_func = sip_sec_init_sec_context__krb5;
278 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
279 context->common.make_signature_func = sip_sec_make_signature__krb5;
280 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
282 context->ctx_krb5 = GSS_C_NO_CONTEXT;
284 return((SipSecContext) context);
287 gboolean sip_sec_password__krb5(void)
289 /* Kerberos supports Single-Sign On */
290 return(FALSE);
293 static void
294 sip_sec_krb5_print_gss_error0(char *func,
295 OM_uint32 status,
296 int type)
298 OM_uint32 minor;
299 OM_uint32 message_context = 0;
300 gss_buffer_desc status_string;
302 do {
303 gss_display_status(&minor,
304 status,
305 type,
306 GSS_C_NO_OID,
307 &message_context,
308 &status_string);
310 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);
311 gss_release_buffer(&minor, &status_string);
312 } while (message_context != 0);
316 * Prints out errors of GSS-API function invocation
318 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
320 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
321 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
325 * Prints out errors of Kerberos 5 function invocation
327 static void
328 sip_sec_krb5_print_error(const char *func,
329 krb5_context context,
330 krb5_error_code ret);
333 * Obtains Kerberos TGT and stores it in default credentials cache.
334 * Similar what kinit util would do.
335 * Can be checked with klist util.
337 * kinit would require the following name:
338 * alice@ATLANTA.LOCAL
339 * where 'alice' is a username and
340 * 'ATLANTA.LOCAL' is a realm (domain) .
342 static void
343 sip_sec_krb5_obtain_tgt(const char *domain_in,
344 const char *username_in,
345 const char *password)
347 krb5_context context;
348 krb5_principal principal = NULL;
349 krb5_creds credentials;
350 krb5_ccache ccdef;
351 krb5_error_code ret;
352 char *realm;
353 char *username;
354 gchar **user_realm;
356 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
358 memset(&credentials, 0, sizeof(krb5_creds));
360 user_realm = g_strsplit(username_in, "@", 2);
361 if (user_realm && user_realm[1]) {
362 /* "user@domain" -> use domain as realm */
363 realm = g_ascii_strup(user_realm[1], -1);
364 username = g_strdup(user_realm[0]);
365 } else {
366 /* use provided domain as realm */
367 realm = g_ascii_strup(domain_in ? domain_in : "", -1);
368 username = g_strdup(username_in);
370 g_strfreev(user_realm);
372 /* Obtait TGT */
373 if ((ret = krb5_init_context(&context))) {
374 sip_sec_krb5_print_error("krb5_init_context", context, ret);
377 if (!ret && (ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL))) {
378 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
380 g_free(username);
381 g_free(realm);
383 if (!ret && (ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)password, NULL, NULL, 0, NULL, NULL))) {
384 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
387 if (!ret) {
388 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT obtained");
392 /* Store TGT in default credential cache */
393 if (!ret && (ret = krb5_cc_default(context, &ccdef))) {
394 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
397 if (!ret && (ret = krb5_cc_initialize(context, ccdef, credentials.client))) {
398 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
401 if (!ret && (ret = krb5_cc_store_cred(context, ccdef, &credentials))) {
402 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
405 if (!ret) {
406 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache");
410 if (principal)
411 krb5_free_principal(context, principal);
413 if (context)
414 krb5_free_context(context);
417 static void
418 sip_sec_krb5_print_error(const char *func,
419 krb5_context context,
420 krb5_error_code ret)
422 const char *error_message = krb5_get_error_message(context, ret);
423 SIPE_DEBUG_ERROR("Kerberos 5 ERROR in %s: %s", func, error_message);
424 krb5_free_error_message(context, error_message);
428 Local Variables:
429 mode: c
430 c-file-style: "bsd"
431 indent-tabs-mode: t
432 tab-width: 8
433 End: