security: allow / and \ as Kerberos realm seperators
[siplcs.git] / src / core / sip-sec-krb5.c
blob934a105ec35082e3d693663d2c54e146780f520d
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 *username,
48 const char *password);
50 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
52 /**
53 * Depending on Single Sign-On flag (sso),
54 * obtains existing credentials stored in credentials cash in case of Kerberos,
55 * or attemps to obtain TGT on its own first.
57 static sip_uint32
58 sip_sec_acquire_cred__krb5(SipSecContext context,
59 SIPE_UNUSED_PARAMETER const char *domain,
60 const char *username,
61 const char *password)
63 OM_uint32 ret;
64 OM_uint32 minor;
65 gss_cred_id_t credentials;
67 if (!context->sso) {
68 /* Do not use default credentials, obtain a new one and store it in cache */
69 sip_sec_krb5_obtain_tgt(username, password);
72 /* Acquire default user credentials */
73 ret = gss_acquire_cred(&minor,
74 GSS_C_NO_NAME,
75 GSS_C_INDEFINITE,
76 GSS_C_NO_OID_SET,
77 GSS_C_INITIATE,
78 &credentials,
79 NULL,
80 NULL);
82 if (GSS_ERROR(ret)) {
83 sip_sec_krb5_print_gss_error("gss_acquire_cred", ret, minor);
84 SIPE_DEBUG_ERROR("sip_sec_acquire_cred__krb5: failed to acquire credentials (ret=%d)", (int)ret);
85 return SIP_SEC_E_INTERNAL_ERROR;
86 } else {
87 ((context_krb5)context)->cred_krb5 = credentials;
88 return SIP_SEC_E_OK;
92 static sip_uint32
93 sip_sec_init_sec_context__krb5(SipSecContext context,
94 SipSecBuffer in_buff,
95 SipSecBuffer *out_buff,
96 const char *service_name)
98 OM_uint32 ret;
99 OM_uint32 minor, minor_ignore;
100 OM_uint32 expiry;
101 gss_buffer_desc input_token;
102 gss_buffer_desc output_token;
103 gss_buffer_desc input_name_buffer;
104 gss_name_t target_name;
105 context_krb5 ctx = (context_krb5) context;
107 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_init_sec_context__krb5: started");
109 /* Delete old context first */
110 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
111 ret = gss_delete_sec_context(&minor,
112 &(ctx->ctx_krb5),
113 GSS_C_NO_BUFFER);
114 if (GSS_ERROR(ret)) {
115 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
116 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
118 ctx->ctx_krb5 = GSS_C_NO_CONTEXT;
121 input_name_buffer.value = (void *)service_name;
122 input_name_buffer.length = strlen(input_name_buffer.value) + 1;
124 ret = gss_import_name(&minor,
125 &input_name_buffer,
126 (gss_OID) GSS_KRB5_NT_PRINCIPAL_NAME,
127 &target_name);
128 if (GSS_ERROR(ret)) {
129 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
130 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to construct target name (ret=%d)", (int)ret);
131 return SIP_SEC_E_INTERNAL_ERROR;
134 input_token.length = in_buff.length;
135 input_token.value = in_buff.value;
137 output_token.length = 0;
138 output_token.value = NULL;
140 /* context takes ownership of input_name_buffer? */
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);
155 if (GSS_ERROR(ret)) {
156 gss_release_buffer(&minor_ignore, &output_token);
157 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
158 SIPE_DEBUG_ERROR("sip_sec_init_sec_context__krb5: failed to initialize context (ret=%d)", (int)ret);
159 return SIP_SEC_E_INTERNAL_ERROR;
162 out_buff->length = output_token.length;
163 out_buff->value = g_memdup(output_token.value, output_token.length);
164 gss_release_buffer(&minor_ignore, &output_token);
166 context->expires = (int)expiry;
168 /* Authentication is completed */
169 context->is_ready = TRUE;
171 return SIP_SEC_E_OK;
175 * @param message a NULL terminated string to sign
177 static sip_uint32
178 sip_sec_make_signature__krb5(SipSecContext context,
179 const char *message,
180 SipSecBuffer *signature)
182 OM_uint32 ret;
183 OM_uint32 minor;
184 gss_buffer_desc input_message;
185 gss_buffer_desc output_token;
187 input_message.value = (void *)message;
188 input_message.length = strlen(input_message.value);
190 ret = gss_get_mic(&minor,
191 ((context_krb5)context)->ctx_krb5,
192 GSS_C_QOP_DEFAULT,
193 &input_message,
194 &output_token);
196 if (GSS_ERROR(ret)) {
197 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
198 SIPE_DEBUG_ERROR("sip_sec_make_signature__krb5: failed to make signature (ret=%d)", (int)ret);
199 return SIP_SEC_E_INTERNAL_ERROR;
200 } else {
201 signature->length = output_token.length;
202 signature->value = g_memdup(output_token.value,
203 output_token.length);
204 gss_release_buffer(&minor, &output_token);
205 return SIP_SEC_E_OK;
210 * @param message a NULL terminated string to check signature of
212 static sip_uint32
213 sip_sec_verify_signature__krb5(SipSecContext context,
214 const char *message,
215 SipSecBuffer signature)
217 OM_uint32 ret;
218 OM_uint32 minor;
219 gss_buffer_desc input_message;
220 gss_buffer_desc input_token;
222 input_message.value = (void *)message;
223 input_message.length = strlen(input_message.value);
225 input_token.value = signature.value;
226 input_token.length = signature.length;
228 ret = gss_verify_mic(&minor,
229 ((context_krb5)context)->ctx_krb5,
230 &input_message,
231 &input_token,
232 NULL);
234 if (GSS_ERROR(ret)) {
235 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
236 SIPE_DEBUG_ERROR("sip_sec_verify_signature__krb5: failed to make signature (ret=%d)", (int)ret);
237 return SIP_SEC_E_INTERNAL_ERROR;
238 } else {
239 return SIP_SEC_E_OK;
243 static void
244 sip_sec_destroy_sec_context__krb5(SipSecContext context)
246 OM_uint32 ret;
247 OM_uint32 minor;
248 context_krb5 ctx = (context_krb5) context;
250 if (ctx->cred_krb5) {
251 ret = gss_release_cred(&minor, &(ctx->cred_krb5));
252 if (GSS_ERROR(ret)) {
253 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
254 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to release credentials (ret=%d)", (int)ret);
258 if (ctx->ctx_krb5 != GSS_C_NO_CONTEXT) {
259 ret = gss_delete_sec_context(&minor, &(ctx->ctx_krb5), GSS_C_NO_BUFFER);
260 if (GSS_ERROR(ret)) {
261 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
262 SIPE_DEBUG_ERROR("sip_sec_destroy_sec_context__krb5: failed to delete security context (ret=%d)", (int)ret);
266 g_free(ctx);
269 SipSecContext
270 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER guint type)
272 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
273 if (!context) return(NULL);
275 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
276 context->common.init_context_func = sip_sec_init_sec_context__krb5;
277 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
278 context->common.make_signature_func = sip_sec_make_signature__krb5;
279 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
281 context->ctx_krb5 = GSS_C_NO_CONTEXT;
283 return((SipSecContext) context);
286 gboolean sip_sec_password__krb5(void)
288 /* Kerberos supports Single-Sign On */
289 return(FALSE);
292 static void
293 sip_sec_krb5_print_gss_error0(char *func,
294 OM_uint32 status,
295 int type)
297 OM_uint32 minor;
298 OM_uint32 message_context = 0;
299 gss_buffer_desc status_string;
301 do {
302 gss_display_status(&minor,
303 status,
304 type,
305 GSS_C_NO_OID,
306 &message_context,
307 &status_string);
309 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);
310 gss_release_buffer(&minor, &status_string);
311 } while (message_context != 0);
315 * Prints out errors of GSS-API function invocation
317 static void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
319 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
320 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
324 * Prints out errors of Kerberos 5 function invocation
326 static void
327 sip_sec_krb5_print_error(const char *func,
328 krb5_context context,
329 krb5_error_code ret);
332 * Obtains Kerberos TGT and stores it in default credentials cache.
333 * Similar what kinit util would do.
334 * Can be checked with klist util.
336 * kinit would require the following name:
337 * alice@ATLANTA.LOCAL
338 * where 'alice' is a username and
339 * 'ATLANTA.LOCAL' is a realm (domain) .
341 static void
342 sip_sec_krb5_obtain_tgt(const char *username_in,
343 const char *password)
345 krb5_context context;
346 krb5_principal principal = NULL;
347 krb5_creds credentials;
348 krb5_ccache ccdef;
349 krb5_error_code ret;
350 char *realm;
351 char *username;
352 gchar **domain_user;
353 gchar **user_realm;
355 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: started");
357 memset(&credentials, 0, sizeof(krb5_creds));
359 /* extracts realm as domain part of username
360 * either before '/' & '\' or after '@'
362 domain_user = g_strsplit_set(username_in, "/\\", 2);
363 if (domain_user[1]) {
364 realm = g_ascii_strup(domain_user[0], -1);
365 username = g_strdup(domain_user[1]);
366 } else {
367 realm = g_strdup("");
368 username = g_strdup(username_in);
370 g_strfreev(domain_user);
372 user_realm = g_strsplit(username, "@", 2);
373 if (user_realm && user_realm[1]) {
374 g_free(username);
375 g_free(realm);
376 username = g_strdup(user_realm[0]);
377 realm = g_ascii_strup(user_realm[1], -1);
379 g_strfreev(user_realm);
381 /* Obtait TGT */
382 if ((ret = krb5_init_context(&context))) {
383 sip_sec_krb5_print_error("krb5_init_context", context, ret);
386 if (!ret && (ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL))) {
387 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
389 g_free(username);
390 g_free(realm);
392 if (!ret && (ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)password, NULL, NULL, 0, NULL, NULL))) {
393 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
396 if (!ret) {
397 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT obtained");
401 /* Store TGT in default credential cache */
402 if (!ret && (ret = krb5_cc_default(context, &ccdef))) {
403 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
406 if (!ret && (ret = krb5_cc_initialize(context, ccdef, credentials.client))) {
407 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
410 if (!ret && (ret = krb5_cc_store_cred(context, ccdef, &credentials))) {
411 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
414 if (!ret) {
415 SIPE_DEBUG_INFO_NOFORMAT("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache");
419 if (principal)
420 krb5_free_principal(context, principal);
422 if (context)
423 krb5_free_context(context);
426 static void
427 sip_sec_krb5_print_error(const char *func,
428 krb5_context context,
429 krb5_error_code ret)
431 const char *error_message = krb5_get_error_message(context, ret);
432 SIPE_DEBUG_ERROR("Kerberos 5 ERROR in %s: %s", func, error_message);
433 krb5_free_error_message(context, error_message);
437 Local Variables:
438 mode: c
439 c-file-style: "bsd"
440 indent-tabs-mode: t
441 tab-width: 8
442 End: