Update Changelog
[siplcs.git] / src / sipkrb5.c
blob01fcd02316001205fc0012f462454a0a59615b8b
1 /*
2 * @file sipkrb5.c
4 * Methods for using Kerberos authentication and signing with SIPE,
5 * implemented with reference to
6 * - MS-SIP: http://msdn.microsoft.com/en-us/library/cc431510.aspx
8 * Authentication is known to be working, but the signing does not work at
9 * all yet.
11 * pidgin-sipe
13 * Copyright (C) 2008 Novell, Inc.
14 * Copyright (C) 2008 Andrew Rechenberg
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
31 #ifdef USE_KERBEROS
33 #include <glib.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <et/com_err.h>
40 #include "debug.h"
41 #include "util.h"
43 #include <krb5.h>
44 #include <gssapi.h>
45 #include <gssapi/gssapi_generic.h>
46 #include <gssapi/gssapi_krb5.h>
48 #include "sipkrb5.h"
49 #include "sipmsg.h"
51 void log_krb5_error(krb5_context ctx, krb5_error_code err, char * msg)
53 const char * err_msg = krb5_get_error_message(ctx, err);
54 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "%s; error: %s\n", msg, err_msg);
55 krb5_free_error_message(ctx, err_msg);
58 /* Taken from krb5's src/tests/gss-threads/gss-misc.c */
60 static void display_status_1(m, code, type)
61 char *m;
62 OM_uint32 code;
63 int type;
65 OM_uint32 maj_stat, min_stat;
66 gss_buffer_desc msg;
67 OM_uint32 msg_ctx;
69 msg_ctx = 0;
70 while (1) {
71 maj_stat = gss_display_status(&min_stat, code,
72 type, GSS_C_NULL_OID,
73 &msg_ctx, &msg);
74 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "GSS-API error %s: %s\n", m, (char *)msg.value);
75 (void) gss_release_buffer(&min_stat, &msg);
77 if (!msg_ctx)
78 break;
83 * Function: display_status
85 * Purpose: displays GSS-API messages
87 * Arguments:
89 * msg a string to be displayed with the message
90 * maj_stat the GSS-API major status code
91 * min_stat the GSS-API minor status code
93 * Effects:
95 * The GSS-API messages associated with maj_stat and min_stat are
96 * displayed on stderr, each preceeded by "GSS-API error <msg>: " and
97 * followed by a newline.
99 void display_status(msg, maj_stat, min_stat)
100 char *msg;
101 OM_uint32 maj_stat;
102 OM_uint32 min_stat;
104 display_status_1(msg, maj_stat, GSS_C_GSS_CODE);
105 display_status_1(msg, min_stat, GSS_C_MECH_CODE);
108 /* End taken from krb5's src/tests/gss-threads/gss-misc.c */
110 void
111 purple_krb5_init_auth(struct sipe_krb5_auth * auth,
112 const char *authuser,
113 const char *realm,
114 char *password,
115 const char *hostname,
116 const char *service)
118 auth->authuser = authuser;
119 auth->realm = realm;
120 auth->password = password;
121 auth->hostname = hostname;
122 auth->service = service;
124 auth->token = NULL;
125 auth->base64_token = NULL;
126 auth->gss_context = NULL;
128 purple_krb5_gen_auth_token(auth);
131 void
132 purple_krb5_gen_auth_token(struct sipe_krb5_auth * auth)
135 * Ideally we will check to see if a Kerberos ticket already exists in the
136 * default Kerberos credential cache. Right now we re-do everything all
137 * the time.
139 * XXX FIXME - Check for ticket already and create a KRB_AP_REQ from creds
140 * we already have.
143 krb5_context context;
144 krb5_principal principal;
145 krb5_creds credentials;
146 krb5_ccache ccdef;
147 krb5_error_code retval;
148 char *progname = 0;
150 memset(&credentials, 0, sizeof(krb5_creds));
152 // Initialize the KRB context
153 if (retval = krb5_init_context(&context)) {
154 log_krb5_error(context, retval, "krb5_init_context");
155 return;
158 // Build a Kerberos principal and get a TGT if there isn't one already
159 if (retval = krb5_build_principal(context, &principal, strlen(auth->realm), auth->realm, auth->authuser, NULL)) {
160 log_krb5_error(context, retval, "krb5_build_principal");
161 goto free_context;
164 if (retval = krb5_get_init_creds_password(context, &credentials, principal, auth->password, NULL, NULL, 0, NULL, NULL)) {
165 log_krb5_error(context, retval, "krb5_get_init_creds_password");
166 goto free_principal;
169 // Initialize default credentials cache
170 if (retval = krb5_cc_default(context, &ccdef)) {
171 log_krb5_error(context, retval, "krb5_cc_default");
172 goto free_principal;
175 if (credentials.client == NULL) {
176 log_krb5_error(context, retval, "credentials.client == NULL");
177 goto free_principal;
180 if (retval = krb5_cc_initialize(context, ccdef, credentials.client)) {
181 log_krb5_error(context, retval, "krb5_cc_initialize");
182 goto free_principal;
185 // Store the TGT
186 if (retval = krb5_cc_store_cred(context, ccdef, &credentials)) {
187 log_krb5_error(context, retval, "krb5_cc_store_cred");
188 goto free_principal;
191 // Prepare the AP-REQ
192 krb5_data inbuf, ap_req;
193 krb5_auth_context auth_context = NULL;
195 inbuf.data = (char *)auth->hostname;
196 inbuf.length = strlen((char *)auth->hostname);
198 if ((retval = krb5_mk_req(context, &auth_context, AP_OPTS_MUTUAL_REQUIRED, (char *)auth->service, (char *)auth->hostname, &inbuf, ccdef, &ap_req))) {
199 log_krb5_error(context, retval, "krb5_mk_req");
200 goto free_principal;
203 auth->token = (char *)ap_req.data;
204 auth->base64_token = purple_base64_encode(auth->token, ap_req.length);
206 // Initialize the GSS layer
207 initialize_gss(auth, &(credentials.server));
208 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "generated krb5 auth token and initialized GSS context\n");
210 // Clean up
211 free_principal:
212 krb5_free_principal(context, principal);
214 free_context:
215 krb5_free_context(context);
218 int initialize_gss(struct sipe_krb5_auth * auth, krb5_principal *principal)
220 OM_uint32* ret_flags;
221 gss_buffer_desc send_tok, recv_tok, *token_ptr;
222 gss_name_t target_name;
223 OM_uint32 maj_stat, min_stat, init_sec_min_stat;
224 int token_flags;
227 * Import the name into target_name. Use send_tok to save
228 * local variable space.
230 //char * service_name = "sip";
231 //char * service_name = "sip/ocs1.ocs.provo.novell.com";
232 char * service_name = "sip@ocs1.ocs.provo.novell.com";
233 send_tok.value = service_name;
234 send_tok.length = strlen(service_name);
235 //maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NT_USER_NAME, &target_name);
236 maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NT_HOSTBASED_SERVICE, &target_name);
238 if (maj_stat != GSS_S_COMPLETE) {
239 display_status("parsing name", maj_stat, min_stat);
240 return -1;
243 /*if (!v1_format) {
244 if (send_token(s, TOKEN_NOOP|TOKEN_CONTEXT_NEXT, empty_token) < 0) {
245 (void) gss_release_name(&min_stat, &target_name);
246 return -1;
251 * Perform the context-establishement loop.
253 * On each pass through the loop, token_ptr points to the token
254 * to send to the server (or GSS_C_NO_BUFFER on the first pass).
255 * Every generated token is stored in send_tok which is then
256 * transmitted to the server; every received token is stored in
257 * recv_tok, which token_ptr is then set to, to be processed by
258 * the next call to gss_init_sec_context.
260 * GSS-API guarantees that send_tok's length will be non-zero
261 * if and only if the server is expecting another token from us,
262 * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if
263 * and only if the server has another token to send us.
266 token_ptr = GSS_C_NO_BUFFER;
267 gss_ctx_id_t gss_no_context = GSS_C_NO_CONTEXT;
268 auth->gss_context = &gss_no_context;
269 //OM_uint32 req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
270 //OM_uint32 req_flags = 0; //GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;// | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
271 OM_uint32 req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;// | GSS_C_SEQUENCE_FLAG | GSS_C_INTEG_FLAG;
273 do {
274 maj_stat = gss_init_sec_context(&init_sec_min_stat,
275 GSS_C_NO_CREDENTIAL,
276 auth->gss_context,
277 target_name,
278 gss_mech_krb5,
279 req_flags,
280 0, // 0 = default = 2 hrs
281 GSS_C_NO_CHANNEL_BINDINGS,
282 token_ptr,
283 NULL, /* ignore mech type */
284 &send_tok,
285 ret_flags, /* output */
286 NULL); /* ignore time_rec */
288 if (token_ptr != GSS_C_NO_BUFFER) {
289 free(recv_tok.value);
292 if (send_tok.length != 0) {
293 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "send_tok.length != 0, but not sending..\n");
294 /*if (verbose) {
295 printf("Sending init_sec_context token (size=%d)...",
296 (int) send_tok.length);
298 /*if (send_token(s, v1_format?0:TOKEN_CONTEXT, &send_tok) < 0) {
299 (void) gss_release_buffer(&min_stat, &send_tok);
300 (void) gss_release_name(&min_stat, &target_name);
301 if (*gss_context != GSS_C_NO_CONTEXT) {
302 gss_delete_sec_context(&min_stat, gss_context,
303 GSS_C_NO_BUFFER);
304 *gss_context = GSS_C_NO_CONTEXT;
306 return -1;
308 } else {
309 //purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "send_tok.length == 0, nothing to send\n");
312 (void) gss_release_buffer(&min_stat, &send_tok);
314 if (maj_stat == GSS_S_COMPLETE && ret_flags != NULL) {
315 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "gss_init_sec_context req_flags = %d ret_flags = %d\n", req_flags, *ret_flags);
318 if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) {
319 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED; maj_stat = %d\n", maj_stat);
320 display_status("initializing context", maj_stat, init_sec_min_stat);
321 (void) gss_release_name(&min_stat, &target_name);
323 if (*(auth->gss_context) != GSS_C_NO_CONTEXT) {
324 gss_delete_sec_context(&min_stat, auth->gss_context, GSS_C_NO_BUFFER);
326 return -1;
329 if (maj_stat == GSS_S_CONTINUE_NEEDED) {
330 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "maj_stat == GSS_S_CONTINUE_NEEDED\n");
331 /*if (verbose) {
332 printf("continue needed...");
334 if (recv_token(s, &token_flags, &recv_tok) < 0) {
335 (void) gss_release_name(&min_stat, &target_name);
336 return -1;
338 token_ptr = &recv_tok;*/
341 /*if (verbose) {
342 printf("\n");
344 } while (maj_stat == GSS_S_CONTINUE_NEEDED);
346 (void) gss_release_name(&min_stat, &target_name);
348 return 0;
352 gchar *
353 purple_krb5_get_mic(struct sipe_krb5_auth * auth, char * msg)
355 if (auth == NULL || auth->gss_context == NULL) {
356 purple_debug(PURPLE_DEBUG_MISC, "sip_krb5", "gss_get_mic auth or auth->gss_context is null\n");
357 return NULL;
360 gchar * mic = NULL;
361 gss_buffer_desc msg_buf;
362 msg_buf.value = msg;
363 msg_buf.length = strlen(msg_buf.value) + 1;
365 gss_buffer_desc mic_buf;
366 OM_uint32 major, minor;
368 if (major = gss_get_mic(&minor, *(auth->gss_context), GSS_C_QOP_DEFAULT, &msg_buf, &mic_buf)) {
369 purple_debug(PURPLE_DEBUG_MISC, "gss_get_mic", "major is %d, minor is %d\n", major, minor);
370 return NULL;
373 if (mic_buf.length > 0) {
374 gchar mic_val [mic_buf.length * 2];
375 guint8 * mic_int_ary = (guint8 *) mic_buf.value;
376 int i, j;
377 for (i = 0, j = 0; i < mic_buf.length; i++, j+=2) {
378 g_sprintf(&mic_val[j], "%02x", mic_int_ary[i]);
380 mic = g_strdup(mic_val);
381 } else {
382 purple_debug(PURPLE_DEBUG_MISC, "sipe", "gss_get_mic MIC is empty\n");
385 gss_release_buffer(&minor, &mic_buf);
387 return mic;
390 gchar *
391 purple_krb5_get_mic_for_msg_breakdown(struct sipe_krb5_auth * auth, struct sipmsg_breakdown * msgbd)
393 if (msgbd->realm == empty_string || msgbd->realm == NULL) {
394 purple_debug(PURPLE_DEBUG_MISC, "sipkrb5", "realm NULL, so returning NULL MIC\n");
395 return NULL;
398 gchar * response_str = msgbd->msg->response != 0 ? g_strdup_printf("<%d>", msgbd->msg->response) : empty_string;
399 gchar * msg = g_strdup_printf(
400 "<%s><%s><%s><%s><%s><%s><%s><%s><%s><%s><%s>" // 1 - 11
401 "<%s>%s", // 12 - 13
402 "Kerberos", msgbd->rand, msgbd->num, msgbd->realm, msgbd->target_name, msgbd->call_id, msgbd->cseq,
403 msgbd->msg->method, msgbd->from_url, msgbd->from_tag, msgbd->to_tag,
404 msgbd->expires ? msgbd->expires : empty_string, response_str
407 gchar * mic = purple_krb5_get_mic(auth, msg);
409 g_free(msg);
410 if (response_str != empty_string) {
411 g_free(response_str);
414 return mic;
417 gchar *
418 purple_krb5_get_mic_for_sipmsg(struct sipe_krb5_auth * auth, struct sipmsg * msg)
420 struct sipmsg_breakdown sipbd;
421 sipbd.msg = msg;
422 sipmsg_breakdown_parse(&sipbd);
424 gchar * mic = purple_krb5_get_mic_for_msg_breakdown(auth, &sipbd);
426 sipmsg_breakdown_free(&sipbd);
427 return mic;
430 #endif /*USE_KERBEROS*/