fix for #2892842 - interference of conf invite to normal IM dialog
[siplcs.git] / src / sip-sec-krb5.c
blob646f58c6b6ed224b1c0bafd778532b39cdec3b18
1 /**
2 * @file sip-sec-krb5.c
4 * pidgin-sipe
6 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <glib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <gssapi/gssapi.h>
28 #include <gssapi/gssapi_krb5.h>
30 #include "sip-sec.h"
31 #include "sip-sec-mech.h"
32 #include "sip-sec-krb5.h"
34 /* Security context for Kerberos */
35 typedef struct _context_krb5 {
36 struct sip_sec_context common;
37 gss_cred_id_t cred_krb5;
38 gss_ctx_id_t ctx_krb5;
39 } *context_krb5;
41 void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor);
43 void
44 sip_sec_krb5_obtain_tgt(const char *realm,
45 const char *username,
46 const char *password);
48 /* sip-sec-mech.h API implementation for Kerberos/GSS-API */
50 /**
51 * Depending on Single Sign-On flag (sso),
52 * obtains existing credentials stored in credentials cash in case of Kerberos,
53 * or attemps to obtain TGT on its own first.
55 static sip_uint32
56 sip_sec_acquire_cred__krb5(SipSecContext context,
57 const char *domain,
58 const char *username,
59 const char *password)
61 OM_uint32 ret;
62 OM_uint32 minor;
63 OM_uint32 expiry;
64 gss_cred_id_t credentials;
66 if (!context->sso) {
67 /* Do not use default credentials, obtain a new one and store it in cache */
68 sip_sec_krb5_obtain_tgt(g_ascii_strup(domain, -1), username, password);
71 /* Acquire default user credentials */
72 ret = gss_acquire_cred(&minor,
73 GSS_C_NO_NAME,
74 GSS_C_INDEFINITE,
75 GSS_C_NO_OID_SET,
76 GSS_C_INITIATE,
77 &credentials,
78 NULL,
79 &expiry);
81 if (GSS_ERROR(ret)) {
82 sip_sec_krb5_print_gss_error("gss_acquire_cred", ret, minor);
83 printf("ERROR: sip_sec_acquire_cred0__krb5: failed to acquire credentials. ret=%d\n", (int)ret);
84 return SIP_SEC_E_INTERNAL_ERROR;
85 } else {
86 ((context_krb5)context)->cred_krb5 = credentials;
87 return SIP_SEC_E_OK;
91 static sip_uint32
92 sip_sec_init_sec_context__krb5(SipSecContext context,
93 SipSecBuffer in_buff,
94 SipSecBuffer *out_buff,
95 const char *service_name)
97 OM_uint32 ret;
98 OM_uint32 minor;
99 OM_uint32 expiry;
100 OM_uint32 request_flags;
101 OM_uint32 response_flags;
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 input_name_buffer.value = (void *)service_name;
109 input_name_buffer.length = strlen(input_name_buffer.value) + 1;
111 ret = gss_import_name(&minor,
112 &input_name_buffer,
113 (const gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME,
114 &target_name);
115 if (GSS_ERROR(ret)) {
116 sip_sec_krb5_print_gss_error("gss_import_name", ret, minor);
117 printf("ERROR: sip_sec_init_sec_context__krb5: failed to construct target name. Returned. ret=%d\n", (int)ret);
118 return SIP_SEC_E_INTERNAL_ERROR;
121 request_flags = GSS_C_INTEG_FLAG;
123 input_token.length = in_buff.length;
124 input_token.value = in_buff.value;
126 output_token.length = 0;
127 output_token.value = NULL;
129 ret = gss_init_sec_context(&minor,
130 ctx->cred_krb5,
131 &(ctx->ctx_krb5),
132 target_name,
133 GSS_C_NO_OID,
134 request_flags,
135 GSS_C_INDEFINITE,
136 GSS_C_NO_CHANNEL_BINDINGS,
137 &input_token,
138 NULL,
139 &output_token,
140 &response_flags,
141 &expiry);
143 if (GSS_ERROR(ret)) {
144 sip_sec_krb5_print_gss_error("gss_init_sec_context", ret, minor);
145 printf("ERROR: sip_sec_init_sec_context__krb5: failed to initialize context. ret=%d\n", (int)ret);
146 return SIP_SEC_E_INTERNAL_ERROR;
147 } else {
148 ret = gss_release_cred(&minor, &(ctx->cred_krb5));
149 if (GSS_ERROR(ret)) {
150 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
151 printf("ERROR: sip_sec_init_sec_context__krb5: failed to release credentials. ret=%d\n", (int)ret);
154 input_token.value = NULL;
155 input_token.length = 0;
157 out_buff->length = output_token.length;
158 out_buff->value = output_token.value;
160 context->expires = (int)expiry;
161 return SIP_SEC_E_OK;
166 * @param message a NULL terminated string to sign
168 static sip_uint32
169 sip_sec_make_signature__krb5(SipSecContext context,
170 const char *message,
171 SipSecBuffer *signature)
173 OM_uint32 ret;
174 OM_uint32 minor;
175 gss_buffer_desc input_message;
176 gss_buffer_desc output_token;
178 input_message.value = (void *)message;
179 input_message.length = strlen(input_message.value);
181 ret = gss_get_mic(&minor,
182 ((context_krb5)context)->ctx_krb5,
183 GSS_C_QOP_DEFAULT,
184 &input_message,
185 &output_token);
187 if (GSS_ERROR(ret)) {
188 sip_sec_krb5_print_gss_error("gss_get_mic", ret, minor);
189 printf("ERROR: sip_ssp_make_signature: failed to make signature. ret=%d\n", (int)ret);
190 return SIP_SEC_E_INTERNAL_ERROR;
191 } else {
192 signature->value = output_token.value;
193 signature->length = output_token.length;
195 return SIP_SEC_E_OK;
200 * @param message a NULL terminated string to check signature of
202 static sip_uint32
203 sip_sec_verify_signature__krb5(SipSecContext context,
204 const char *message,
205 SipSecBuffer signature)
207 OM_uint32 ret;
208 OM_uint32 minor;
209 gss_qop_t qop_state;
210 gss_buffer_desc input_message;
211 gss_buffer_desc input_token;
213 input_message.value = (void *)message;
214 input_message.length = strlen(input_message.value);
216 input_token.value = signature.value;
217 input_token.length = signature.length;
219 ret = gss_verify_mic(&minor,
220 ((context_krb5)context)->ctx_krb5,
221 &input_message,
222 &input_token,
223 &qop_state);
225 if (GSS_ERROR(ret)) {
226 sip_sec_krb5_print_gss_error("gss_verify_mic", ret, minor);
227 printf("ERROR: sip_sec_verify_signature__krb5: failed to make signature. ret=%d\n", (int)ret);
228 return SIP_SEC_E_INTERNAL_ERROR;
229 } else {
230 return SIP_SEC_E_OK;
234 static void
235 sip_sec_destroy_sec_context__krb5(SipSecContext context)
237 OM_uint32 ret;
238 OM_uint32 minor;
239 context_krb5 ctx = (context_krb5) context;
241 if (ctx->cred_krb5) {
242 ret = gss_release_cred(&minor, &(ctx->cred_krb5));
243 if (GSS_ERROR(ret)) {
244 sip_sec_krb5_print_gss_error("gss_release_cred", ret, minor);
245 printf("ERROR: sip_sec_destroy_sec_context__krb5: failed to release credentials. ret=%d\n", (int)ret);
249 if (ctx->ctx_krb5) {
250 ret = gss_delete_sec_context(&minor, &(ctx->ctx_krb5), GSS_C_NO_BUFFER);
251 if (GSS_ERROR(ret)) {
252 sip_sec_krb5_print_gss_error("gss_delete_sec_context", ret, minor);
253 printf("ERROR: sip_sec_destroy_sec_context__krb5: failed to delete security context. ret=%d\n", (int)ret);
257 g_free(ctx);
260 SipSecContext
261 sip_sec_create_context__krb5(SIPE_UNUSED_PARAMETER SipSecAuthType type)
263 context_krb5 context = g_malloc0(sizeof(struct _context_krb5));
264 if (!context) return(NULL);
266 context->common.acquire_cred_func = sip_sec_acquire_cred__krb5;
267 context->common.init_context_func = sip_sec_init_sec_context__krb5;
268 context->common.destroy_context_func = sip_sec_destroy_sec_context__krb5;
269 context->common.make_signature_func = sip_sec_make_signature__krb5;
270 context->common.verify_signature_func = sip_sec_verify_signature__krb5;
272 return((SipSecContext) context);
276 static void
277 sip_sec_krb5_print_gss_error0(char *func,
278 OM_uint32 status,
279 int type)
281 OM_uint32 ret;
282 OM_uint32 minor;
283 OM_uint32 message_context = 0;
284 gss_buffer_desc status_string;
286 do {
287 ret = gss_display_status(&minor,
288 status,
289 type,
290 GSS_C_NO_OID,
291 &message_context,
292 &status_string);
294 printf("GSS-API error in %s (%s): %s\n", func, (type == GSS_C_GSS_CODE ? "GSS" : "Mech"), (char *)status_string.value);
295 gss_release_buffer(&minor, &status_string);
296 } while (message_context != 0);
300 * Prints out errors of GSS-API function invocation
302 void sip_sec_krb5_print_gss_error(char *func, OM_uint32 ret, OM_uint32 minor)
304 sip_sec_krb5_print_gss_error0(func, ret, GSS_C_GSS_CODE);
305 sip_sec_krb5_print_gss_error0(func, minor, GSS_C_MECH_CODE);
309 * Prints out errors of Kerberos 5 function invocation
311 void
312 sip_sec_krb5_print_error(const char *func,
313 krb5_context context,
314 krb5_error_code ret);
317 * Obtains Kerberos TGT and stores it in default credentials cache.
318 * Similar what kinit util would do.
319 * Can be checked with klist util.
321 * kinit would require the following name:
322 * alice@ATLANTA.LOCAL
323 * where 'alice' is a username and
324 * 'ATLANTA.LOCAL' is a realm (domain) .
326 void
327 sip_sec_krb5_obtain_tgt(SIPE_UNUSED_PARAMETER const char *realm_in,
328 const char *username_in,
329 const char *password)
331 krb5_context context;
332 krb5_principal principal;
333 krb5_creds credentials;
334 krb5_ccache ccdef;
335 krb5_error_code ret;
336 char *realm;
337 char *username;
338 gchar **domain_user;
339 gchar **user_realm;
341 printf("sip_sec_krb5_obtain_tgt started\n");
343 memset(&credentials, 0, sizeof(krb5_creds));
345 /* extracts realm as domain part of username
346 * either before '\' or after '@'
348 domain_user = g_strsplit(username_in, "\\", 2);
349 if (domain_user && domain_user[1]) {
350 realm = g_ascii_strup(domain_user[0], -1);
351 username = g_strdup(domain_user[1]);
352 } else {
353 realm = g_strdup("");
354 username = g_strdup(username_in);
356 g_strfreev(domain_user);
358 user_realm = g_strsplit(username, "@", 2);
359 if (user_realm && user_realm[1]) {
360 g_free(username);
361 g_free(realm);
362 username = g_strdup(user_realm[0]);
363 realm = g_ascii_strup(user_realm[1], -1);
365 g_strfreev(user_realm);
367 /* Obtait TGT */
368 if ((ret = krb5_init_context(&context))) {
369 sip_sec_krb5_print_error("krb5_init_context", context, ret);
372 if (!ret && (ret = krb5_build_principal(context, &principal, strlen(realm), realm, username, NULL))) {
373 sip_sec_krb5_print_error("krb5_build_principal", context, ret);
375 g_free(username);
376 g_free(realm);
378 if (!ret && (ret = krb5_get_init_creds_password(context, &credentials, principal, (char *)password, NULL, NULL, 0, NULL, NULL))) {
379 sip_sec_krb5_print_error("krb5_get_init_creds_password", context, ret);
382 if (!ret) {
383 printf("sip_sec_krb5_obtain_tgt: new TGT obtained.\n");
387 /* Store TGT in default credential cache */
388 if (!ret && (ret = krb5_cc_default(context, &ccdef))) {
389 sip_sec_krb5_print_error("krb5_cc_default", context, ret);
392 if (!ret && (ret = krb5_cc_initialize(context, ccdef, credentials.client))) {
393 sip_sec_krb5_print_error("krb5_cc_initialize", context, ret);
396 if (!ret && (ret = krb5_cc_store_cred(context, ccdef, &credentials))) {
397 sip_sec_krb5_print_error("krb5_cc_store_cred", context, ret);
400 if (!ret) {
401 printf("sip_sec_krb5_obtain_tgt: new TGT stored in default credentials cache.\n");
405 if (principal)
406 krb5_free_principal(context, principal);
408 if (context)
409 krb5_free_context(context);
412 void
413 sip_sec_krb5_print_error(const char *func,
414 krb5_context context,
415 krb5_error_code ret)
417 #if defined(HAVE_KRB5_GET_ERROR_MESSAGE)
418 const char *error_message = krb5_get_error_message(context, ret);
419 printf("Kerberos 5 ERROR in %s: %s\n", func, error_message);
420 krb5_free_error_message(context, error_message);
421 #endif
422 printf("Kerberos 5 ERROR in %s: %s\n", func, "unknown error");
426 Local Variables:
427 mode: c
428 c-file-style: "bsd"
429 indent-tabs-mode: t
430 tab-width: 8
431 End: