Mercurial > pidgin
annotate src/protocols/jabber/auth.c @ 12508:5cfc53ead482
[gaim-migrate @ 14820]
patch from Simon Wilkinson to add Cyrus SASL support for jabber
Give him credit if it works flawlessly. Blame me if it doesn't, as the
patch was against 1.3.1 (yeah, I've been sitting on it for that long), and
I had to merge it to HEAD, and clean up a bunch of warnings
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Sat, 17 Dec 2005 02:24:05 +0000 |
parents | 8dca96cbcd64 |
children | d85c2bfb2ea2 |
rev | line source |
---|---|
7014 | 1 /* |
2 * gaim - Jabber Protocol Plugin | |
3 * | |
4 * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> | |
5 * | |
6 * This program is free software; you can redistribute it and/or modify | |
7 * it under the terms of the GNU General Public License as published by | |
8 * the Free Software Foundation; either version 2 of the License, or | |
9 * (at your option) any later version. | |
10 * | |
11 * This program is distributed in the hope that it will be useful, | |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 * GNU General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU General Public License | |
17 * along with this program; if not, write to the Free Software | |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 * | |
20 */ | |
21 #include "internal.h" | |
22 | |
23 #include "jutil.h" | |
24 #include "auth.h" | |
25 #include "xmlnode.h" | |
26 #include "jabber.h" | |
27 #include "iq.h" | |
28 | |
29 #include "debug.h" | |
30 #include "util.h" | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
31 #include "cipher.h" |
7014 | 32 #include "sslconn.h" |
8397 | 33 #include "request.h" |
34 | |
35 static void auth_old_result_cb(JabberStream *js, xmlnode *packet, | |
36 gpointer data); | |
7014 | 37 |
8296 | 38 gboolean |
39 jabber_process_starttls(JabberStream *js, xmlnode *packet) | |
7014 | 40 { |
41 xmlnode *starttls; | |
42 | |
7157 | 43 if((starttls = xmlnode_get_child(packet, "starttls"))) { |
7630 | 44 if(gaim_account_get_bool(js->gc->account, "use_tls", TRUE) && |
45 gaim_ssl_is_supported()) { | |
7157 | 46 jabber_send_raw(js, |
7642 | 47 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1); |
8296 | 48 return TRUE; |
7157 | 49 } else if(xmlnode_get_child(starttls, "required")) { |
10441 | 50 if(gaim_ssl_is_supported()) { |
10496 | 51 gaim_connection_error(js->gc, _("Server requires TLS/SSL for login. Select \"Use TLS if available\" in account properties")); |
10441 | 52 } else { |
53 gaim_connection_error(js->gc, _("Server requires TLS/SSL for login. No TLS/SSL support found.")); | |
54 } | |
8296 | 55 return TRUE; |
7157 | 56 } |
7014 | 57 } |
58 | |
8296 | 59 return FALSE; |
60 } | |
61 | |
8397 | 62 static void finish_plaintext_authentication(JabberStream *js) |
63 { | |
64 if(js->auth_type == JABBER_AUTH_PLAIN) { | |
65 xmlnode *auth; | |
66 GString *response; | |
11127 | 67 gchar *enc_out; |
8397 | 68 |
69 auth = xmlnode_new("auth"); | |
70 xmlnode_set_attrib(auth, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); | |
71 | |
72 response = g_string_new(""); | |
73 response = g_string_append_len(response, "\0", 1); | |
74 response = g_string_append(response, js->user->node); | |
75 response = g_string_append_len(response, "\0", 1); | |
76 response = g_string_append(response, | |
10740 | 77 gaim_connection_get_password(js->gc)); |
8397 | 78 |
11137 | 79 enc_out = gaim_base64_encode((guchar *)response->str, response->len); |
8397 | 80 |
81 xmlnode_set_attrib(auth, "mechanism", "PLAIN"); | |
82 xmlnode_insert_data(auth, enc_out, -1); | |
83 g_free(enc_out); | |
84 g_string_free(response, TRUE); | |
85 | |
86 jabber_send(js, auth); | |
87 xmlnode_free(auth); | |
88 } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { | |
89 JabberIq *iq; | |
90 xmlnode *query, *x; | |
91 | |
92 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); | |
93 query = xmlnode_get_child(iq->node, "query"); | |
94 x = xmlnode_new_child(query, "username"); | |
95 xmlnode_insert_data(x, js->user->node, -1); | |
96 x = xmlnode_new_child(query, "resource"); | |
97 xmlnode_insert_data(x, js->user->resource, -1); | |
98 x = xmlnode_new_child(query, "password"); | |
10740 | 99 xmlnode_insert_data(x, gaim_connection_get_password(js->gc), -1); |
8397 | 100 jabber_iq_set_callback(iq, auth_old_result_cb, NULL); |
101 jabber_iq_send(iq); | |
102 } | |
103 } | |
104 | |
105 static void allow_plaintext_auth(GaimAccount *account) | |
106 { | |
107 gaim_account_set_bool(account, "auth_plain_in_clear", TRUE); | |
108 | |
109 finish_plaintext_authentication(account->gc->proto_data); | |
110 } | |
111 | |
112 static void disallow_plaintext_auth(GaimAccount *account) | |
113 { | |
114 gaim_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream")); | |
115 } | |
116 | |
12508 | 117 #ifdef HAVE_CYRUS_SASL |
118 | |
119 static void jabber_auth_start_cyrus(JabberStream *); | |
120 | |
121 /* Callbacks for Cyrus SASL */ | |
122 | |
123 static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result) | |
124 { | |
125 JabberStream *js = (JabberStream *)ctx; | |
126 | |
127 if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM; | |
128 | |
129 *result = js->user->domain; | |
130 | |
131 return SASL_OK; | |
132 } | |
133 | |
134 static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len) | |
135 { | |
136 JabberStream *js = (JabberStream *)ctx; | |
137 | |
138 switch(id) { | |
139 case SASL_CB_AUTHNAME: | |
140 *res = js->user->node; | |
141 break; | |
142 case SASL_CB_USER: | |
143 *res = js->user->node; | |
144 break; | |
145 default: | |
146 return SASL_BADPARAM; | |
147 } | |
148 if (len) *len = strlen((char *)*res); | |
149 return SASL_OK; | |
150 } | |
151 | |
152 static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret) | |
153 { | |
154 JabberStream *js = (JabberStream *)ctx; | |
155 const char *pw = gaim_account_get_password(js->gc->account); | |
156 size_t len; | |
157 static sasl_secret_t *x = NULL; | |
158 | |
159 if (!conn || !secret || id != SASL_CB_PASS) | |
160 return SASL_BADPARAM; | |
161 | |
162 len = strlen(pw); | |
163 x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); | |
164 | |
165 if (!x) | |
166 return SASL_NOMEM; | |
167 | |
168 x->len = len; | |
169 strcpy((char*)x->data, pw); | |
170 | |
171 *secret = x; | |
172 return SASL_OK; | |
173 } | |
174 | |
175 static void allow_cyrus_plaintext_auth(GaimAccount *account) | |
176 { | |
177 gaim_account_set_bool(account, "auth_plain_in_clear", TRUE); | |
178 | |
179 jabber_auth_start_cyrus(account->gc->proto_data); | |
180 } | |
181 | |
182 static void jabber_auth_start_cyrus(JabberStream *js) | |
183 { | |
184 const char *clientout, *mech; | |
185 char *enc_out; | |
186 unsigned coutlen; | |
187 xmlnode *auth; | |
188 sasl_security_properties_t secprops; | |
189 gboolean again; | |
190 gboolean plaintext = TRUE; | |
191 | |
192 /* Set up security properties and options */ | |
193 secprops.min_ssf = 0; | |
194 secprops.security_flags = SASL_SEC_NOANONYMOUS; | |
195 | |
196 if (!js->gsc) { | |
197 plaintext = gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE); | |
198 if (!plaintext) | |
199 secprops.security_flags |= SASL_SEC_NOPLAINTEXT; | |
200 secprops.max_ssf = -1; | |
201 secprops.maxbufsize = 4096; | |
202 } else { | |
203 plaintext = FALSE; | |
204 secprops.max_ssf = 0; | |
205 secprops.maxbufsize = 0; | |
206 } | |
207 secprops.property_names = 0; | |
208 secprops.property_values = 0; | |
209 | |
210 do { | |
211 again = FALSE; | |
212 /* Use the user's domain for compatibility with the old | |
213 * DIGESTMD5 code. Note that this may cause problems where | |
214 * the user's domain doesn't match the FQDN of the jabber | |
215 * service | |
216 */ | |
217 | |
218 js->sasl_state = sasl_client_new("xmpp", js->user->domain, NULL, NULL, js->sasl_cb, 0, &js->sasl); | |
219 if (js->sasl_state==SASL_OK) { | |
220 sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops); | |
221 js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &mech); | |
222 } | |
223 switch (js->sasl_state) { | |
224 /* Success */ | |
225 case SASL_CONTINUE: | |
226 break; | |
227 case SASL_NOMECH: | |
228 /* No mechanisms do what we want. See if we can add | |
229 * plaintext ones to the list. */ | |
230 | |
231 if (!gaim_account_get_password(js->gc->account)) { | |
232 gaim_connection_error(js->gc, _("Server couldn't authenticate you without a password")); | |
233 return; | |
234 } else if (!plaintext) { | |
235 gaim_request_yes_no(js->gc, _("Plaintext Authentication"), | |
236 _("Plaintext Authentication"), | |
237 _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), | |
238 2, js->gc->account, | |
239 allow_cyrus_plaintext_auth, | |
240 disallow_plaintext_auth); | |
241 return; | |
242 } else { | |
243 gaim_connection_error(js->gc, _("Server does not use any supported authentication method")); | |
244 return; | |
245 } | |
246 /* not reached */ | |
247 break; | |
248 | |
249 /* Fatal errors. Give up and go home */ | |
250 case SASL_BADPARAM: | |
251 case SASL_NOMEM: | |
252 break; | |
253 | |
254 /* For everything else, fail the mechanism and try again */ | |
255 default: | |
256 if (strlen(mech)>0) { | |
257 char *pos; | |
258 pos = strstr(js->sasl_mechs->str,mech); | |
259 g_assert(pos!=NULL); | |
260 g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str,strlen(mech)); | |
261 } | |
262 sasl_dispose(&js->sasl); | |
263 again=TRUE; | |
264 } | |
265 } while (again); | |
266 | |
267 if (js->sasl_state == SASL_CONTINUE) { | |
268 auth = xmlnode_new("auth"); | |
269 xmlnode_set_attrib(auth, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); | |
270 xmlnode_set_attrib(auth,"mechanism", mech); | |
271 if (clientout) { | |
272 if (coutlen == 0) { | |
273 xmlnode_insert_data(auth, "=", -1); | |
274 } else { | |
275 enc_out = gaim_base64_encode((unsigned char*)clientout, coutlen); | |
276 xmlnode_insert_data(auth, enc_out, -1); | |
277 g_free(enc_out); | |
278 } | |
279 } | |
280 jabber_send(js, auth); | |
281 xmlnode_free(auth); | |
282 } else { | |
283 gaim_connection_error(js->gc, "SASL authentication failed\n"); | |
284 } | |
285 } | |
286 | |
287 #endif | |
288 | |
8296 | 289 void |
290 jabber_auth_start(JabberStream *js, xmlnode *packet) | |
291 { | |
12508 | 292 #ifdef HAVE_CYRUS_SASL |
293 int id; | |
294 #else | |
295 gboolean digest_md5 = FALSE, plain=FALSE; | |
296 #endif | |
8296 | 297 |
12508 | 298 xmlnode *mechs, *mechnode; |
8296 | 299 |
300 | |
8016 | 301 if(js->registration) { |
302 jabber_register_start(js); | |
303 return; | |
304 } | |
305 | |
7014 | 306 mechs = xmlnode_get_child(packet, "mechanisms"); |
307 | |
308 if(!mechs) { | |
7981 | 309 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 310 return; |
311 } | |
312 | |
12508 | 313 #ifdef HAVE_CYRUS_SASL |
314 js->sasl_mechs = g_string_new(""); | |
315 #endif | |
316 | |
8135 | 317 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; |
318 mechnode = xmlnode_get_next_twin(mechnode)) | |
7014 | 319 { |
8135 | 320 char *mech_name = xmlnode_get_data(mechnode); |
12508 | 321 #ifdef HAVE_CYRUS_SASL |
322 g_string_append(js->sasl_mechs, mech_name); | |
323 g_string_append_c(js->sasl_mechs,' '); | |
324 #else | |
8135 | 325 if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) |
326 digest_md5 = TRUE; | |
327 else if(mech_name && !strcmp(mech_name, "PLAIN")) | |
328 plain = TRUE; | |
12508 | 329 #endif |
8135 | 330 g_free(mech_name); |
7014 | 331 } |
332 | |
12508 | 333 #ifdef HAVE_CYRUS_SASL |
334 js->auth_type = JABBER_AUTH_CYRUS; | |
335 | |
336 /* Set up our callbacks structure */ | |
337 js->sasl_cb = g_new0(sasl_callback_t,5); | |
338 | |
339 id = 0; | |
340 js->sasl_cb[id].id = SASL_CB_GETREALM; | |
341 js->sasl_cb[id].proc = jabber_sasl_cb_realm; | |
342 js->sasl_cb[id].context = (void *)js; | |
343 id++; | |
344 | |
345 js->sasl_cb[id].id = SASL_CB_AUTHNAME; | |
346 js->sasl_cb[id].proc = jabber_sasl_cb_simple; | |
347 js->sasl_cb[id].context = (void *)js; | |
348 id++; | |
349 | |
350 js->sasl_cb[id].id = SASL_CB_USER; | |
351 js->sasl_cb[id].proc = jabber_sasl_cb_simple; | |
352 js->sasl_cb[id].context = (void *)js; | |
353 id++; | |
354 | |
355 if (gaim_account_get_password(js->gc->account)) { | |
356 js->sasl_cb[id].id = SASL_CB_PASS; | |
357 js->sasl_cb[id].proc = jabber_sasl_cb_secret; | |
358 js->sasl_cb[id].context = (void *)js; | |
359 id++; | |
360 } | |
361 | |
362 js->sasl_cb[id].id = SASL_CB_LIST_END; | |
363 | |
364 jabber_auth_start_cyrus(js); | |
365 #else | |
7703 | 366 |
7645 | 367 if(digest_md5) { |
8397 | 368 xmlnode *auth; |
369 | |
370 js->auth_type = JABBER_AUTH_DIGEST_MD5; | |
371 auth = xmlnode_new("auth"); | |
372 xmlnode_set_attrib(auth, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); | |
7291 | 373 xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); |
8397 | 374 |
375 jabber_send(js, auth); | |
376 xmlnode_free(auth); | |
8086 | 377 } else if(plain) { |
8397 | 378 js->auth_type = JABBER_AUTH_PLAIN; |
7703 | 379 |
8086 | 380 if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) { |
8397 | 381 gaim_request_yes_no(js->gc, _("Plaintext Authentication"), |
382 _("Plaintext Authentication"), | |
383 _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), | |
384 2, js->gc->account, allow_plaintext_auth, | |
385 disallow_plaintext_auth); | |
8086 | 386 return; |
387 } | |
8397 | 388 finish_plaintext_authentication(js); |
7014 | 389 } else { |
390 gaim_connection_error(js->gc, | |
391 _("Server does not use any supported authentication method")); | |
392 } | |
12508 | 393 #endif |
7014 | 394 } |
395 | |
7395 | 396 static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) |
7014 | 397 { |
398 const char *type = xmlnode_get_attrib(packet, "type"); | |
399 | |
7730 | 400 if(type && !strcmp(type, "result")) { |
401 jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); | |
402 } else { | |
8401 | 403 char *msg = jabber_parse_error(js, packet); |
404 xmlnode *error; | |
405 const char *err_code; | |
7014 | 406 |
8401 | 407 if((error = xmlnode_get_child(packet, "error")) && |
408 (err_code = xmlnode_get_attrib(error, "code")) && | |
409 !strcmp(err_code, "401")) { | |
410 js->gc->wants_to_die = TRUE; | |
7730 | 411 } |
7014 | 412 |
8401 | 413 gaim_connection_error(js->gc, msg); |
414 g_free(msg); | |
7014 | 415 } |
416 } | |
417 | |
7395 | 418 static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) |
7014 | 419 { |
420 JabberIq *iq; | |
421 xmlnode *query, *x; | |
7514 | 422 const char *type = xmlnode_get_attrib(packet, "type"); |
10740 | 423 const char *pw = gaim_connection_get_password(js->gc); |
7014 | 424 |
7514 | 425 if(!type) { |
7981 | 426 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 427 return; |
7515 | 428 } else if(!strcmp(type, "error")) { |
8401 | 429 char *msg = jabber_parse_error(js, packet); |
430 gaim_connection_error(js->gc, msg); | |
431 g_free(msg); | |
7515 | 432 } else if(!strcmp(type, "result")) { |
7514 | 433 query = xmlnode_get_child(packet, "query"); |
434 if(js->stream_id && xmlnode_get_child(query, "digest")) { | |
435 unsigned char hashval[20]; | |
436 char *s, h[41], *p; | |
437 int i; | |
7014 | 438 |
8397 | 439 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); |
440 query = xmlnode_get_child(iq->node, "query"); | |
441 x = xmlnode_new_child(query, "username"); | |
442 xmlnode_insert_data(x, js->user->node, -1); | |
443 x = xmlnode_new_child(query, "resource"); | |
444 xmlnode_insert_data(x, js->user->resource, -1); | |
445 | |
7514 | 446 x = xmlnode_new_child(query, "digest"); |
447 s = g_strdup_printf("%s%s", js->stream_id, pw); | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
448 |
11183 | 449 gaim_cipher_digest_region("sha1", (guchar *)s, strlen(s), |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
450 sizeof(hashval), hashval, NULL); |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
451 |
7514 | 452 p = h; |
453 for(i=0; i<20; i++, p+=2) | |
454 snprintf(p, 3, "%02x", hashval[i]); | |
455 xmlnode_insert_data(x, h, -1); | |
456 g_free(s); | |
8397 | 457 jabber_iq_set_callback(iq, auth_old_result_cb, NULL); |
458 jabber_iq_send(iq); | |
459 | |
460 } else if(xmlnode_get_child(query, "password")) { | |
461 if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account, | |
462 "auth_plain_in_clear", FALSE)) { | |
463 gaim_request_yes_no(js->gc, _("Plaintext Authentication"), | |
464 _("Plaintext Authentication"), | |
465 _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), | |
466 2, js->gc->account, allow_plaintext_auth, | |
467 disallow_plaintext_auth); | |
468 return; | |
469 } | |
470 finish_plaintext_authentication(js); | |
7514 | 471 } else { |
8397 | 472 gaim_connection_error(js->gc, |
473 _("Server does not use any supported authentication method")); | |
474 return; | |
7514 | 475 } |
7014 | 476 } |
477 } | |
478 | |
479 void jabber_auth_start_old(JabberStream *js) | |
480 { | |
481 JabberIq *iq; | |
482 xmlnode *query, *username; | |
483 | |
484 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth"); | |
485 | |
486 query = xmlnode_get_child(iq->node, "query"); | |
487 username = xmlnode_new_child(query, "username"); | |
488 xmlnode_insert_data(username, js->user->node, -1); | |
489 | |
7395 | 490 jabber_iq_set_callback(iq, auth_old_cb, NULL); |
7014 | 491 |
492 jabber_iq_send(iq); | |
493 } | |
494 | |
495 static GHashTable* parse_challenge(const char *challenge) | |
496 { | |
497 GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, | |
498 g_free, g_free); | |
499 char **pairs; | |
500 int i; | |
501 | |
502 pairs = g_strsplit(challenge, ",", -1); | |
503 | |
504 for(i=0; pairs[i]; i++) { | |
505 char **keyval = g_strsplit(pairs[i], "=", 2); | |
506 if(keyval[0] && keyval[1]) { | |
507 if(keyval[1][0] == '"' && keyval[1][strlen(keyval[1])-1] == '"') | |
508 g_hash_table_replace(ret, g_strdup(keyval[0]), g_strndup(keyval[1]+1, strlen(keyval[1])-2)); | |
509 else | |
510 g_hash_table_replace(ret, g_strdup(keyval[0]), g_strdup(keyval[1])); | |
511 } | |
512 g_strfreev(keyval); | |
513 } | |
514 | |
515 g_strfreev(pairs); | |
516 | |
517 return ret; | |
518 } | |
519 | |
11163 | 520 static char * |
7014 | 521 generate_response_value(JabberID *jid, const char *passwd, const char *nonce, |
7267 | 522 const char *cnonce, const char *a2, const char *realm) |
7014 | 523 { |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
524 GaimCipher *cipher; |
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
525 GaimCipherContext *context; |
11137 | 526 guchar result[16]; |
10136 | 527 size_t a1len; |
7014 | 528 |
11163 | 529 gchar *a1, *convnode, *convpasswd, *ha1, *ha2, *kd, *x, *z; |
7014 | 530 |
10136 | 531 if((convnode = g_convert(jid->node, strlen(jid->node), "iso-8859-1", "utf-8", |
532 NULL, NULL, NULL)) == NULL) { | |
533 convnode = g_strdup(jid->node); | |
534 } | |
535 if((convpasswd = g_convert(passwd, strlen(passwd), "iso-8859-1", "utf-8", | |
536 NULL, NULL, NULL)) == NULL) { | |
537 convpasswd = g_strdup(passwd); | |
538 } | |
539 | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
540 cipher = gaim_ciphers_find_cipher("md5"); |
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
541 context = gaim_cipher_context_new(cipher, NULL); |
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
542 |
10136 | 543 x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd); |
11183 | 544 gaim_cipher_context_append(context, (const guchar *)x, strlen(x)); |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
545 gaim_cipher_context_digest(context, sizeof(result), result, NULL); |
7014 | 546 |
10136 | 547 a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); |
548 a1len = strlen(a1); | |
549 g_memmove(a1, result, 16); | |
7014 | 550 |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
551 gaim_cipher_context_reset(context, NULL); |
11183 | 552 gaim_cipher_context_append(context, (const guchar *)a1, a1len); |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
553 gaim_cipher_context_digest(context, sizeof(result), result, NULL); |
7014 | 554 |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
555 ha1 = gaim_base16_encode(result, 16); |
7014 | 556 |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
557 gaim_cipher_context_reset(context, NULL); |
11183 | 558 gaim_cipher_context_append(context, (const guchar *)a2, strlen(a2)); |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
559 gaim_cipher_context_digest(context, sizeof(result), result, NULL); |
7014 | 560 |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
561 ha2 = gaim_base16_encode(result, 16); |
7014 | 562 |
563 kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); | |
564 | |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
565 gaim_cipher_context_reset(context, NULL); |
11183 | 566 gaim_cipher_context_append(context, (const guchar *)kd, strlen(kd)); |
10687
b256ce6b85b8
[gaim-migrate @ 12235]
Etan Reisner <pidgin@unreliablesource.net>
parents:
10684
diff
changeset
|
567 gaim_cipher_context_digest(context, sizeof(result), result, NULL); |
10684
72a5babfa8b4
[gaim-migrate @ 12231]
Luke Schierer <lschiere@pidgin.im>
parents:
10496
diff
changeset
|
568 gaim_cipher_context_destroy(context); |
7014 | 569 |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
570 z = gaim_base16_encode(result, 16); |
7014 | 571 |
10136 | 572 g_free(convnode); |
573 g_free(convpasswd); | |
7014 | 574 g_free(x); |
575 g_free(a1); | |
576 g_free(ha1); | |
577 g_free(ha2); | |
578 g_free(kd); | |
579 | |
580 return z; | |
581 } | |
582 | |
583 void | |
584 jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) | |
585 { | |
586 | |
7703 | 587 if(js->auth_type == JABBER_AUTH_DIGEST_MD5) { |
7291 | 588 char *enc_in = xmlnode_get_data(packet); |
589 char *dec_in; | |
590 char *enc_out; | |
591 GHashTable *parts; | |
7014 | 592 |
7395 | 593 if(!enc_in) { |
7981 | 594 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7395 | 595 return; |
596 } | |
597 | |
11127 | 598 dec_in = (char *)gaim_base64_decode(enc_in, NULL); |
7395 | 599 gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded challenge (%d): %s\n", |
600 strlen(dec_in), dec_in); | |
7291 | 601 |
602 parts = parse_challenge(dec_in); | |
7014 | 603 |
604 | |
7291 | 605 if (g_hash_table_lookup(parts, "rspauth")) { |
606 char *rspauth = g_hash_table_lookup(parts, "rspauth"); | |
7014 | 607 |
608 | |
7291 | 609 if(rspauth && js->expected_rspauth && |
610 !strcmp(rspauth, js->expected_rspauth)) { | |
611 jabber_send_raw(js, | |
7642 | 612 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", |
613 -1); | |
7291 | 614 } else { |
615 gaim_connection_error(js->gc, _("Invalid challenge from server")); | |
616 } | |
617 g_free(js->expected_rspauth); | |
618 } else { | |
619 /* assemble a response, and send it */ | |
620 /* see RFC 2831 */ | |
621 GString *response = g_string_new(""); | |
622 char *a2; | |
623 char *auth_resp; | |
624 char *buf; | |
625 char *cnonce; | |
626 char *realm; | |
627 char *nonce; | |
7014 | 628 |
7291 | 629 /* we're actually supposed to prompt the user for a realm if |
630 * the server doesn't send one, but that really complicates things, | |
631 * so i'm not gonna worry about it until is poses a problem to | |
632 * someone, or I get really bored */ | |
633 realm = g_hash_table_lookup(parts, "realm"); | |
634 if(!realm) | |
635 realm = js->user->domain; | |
7014 | 636 |
7291 | 637 cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), |
638 g_random_int()); | |
639 nonce = g_hash_table_lookup(parts, "nonce"); | |
7014 | 640 |
641 | |
7291 | 642 a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); |
643 auth_resp = generate_response_value(js->user, | |
10740 | 644 gaim_connection_get_password(js->gc), nonce, cnonce, a2, realm); |
7291 | 645 g_free(a2); |
646 | |
647 a2 = g_strdup_printf(":xmpp/%s", realm); | |
648 js->expected_rspauth = generate_response_value(js->user, | |
10740 | 649 gaim_connection_get_password(js->gc), nonce, cnonce, a2, realm); |
7291 | 650 g_free(a2); |
651 | |
652 | |
653 g_string_append_printf(response, "username=\"%s\"", js->user->node); | |
654 g_string_append_printf(response, ",realm=\"%s\"", realm); | |
655 g_string_append_printf(response, ",nonce=\"%s\"", nonce); | |
656 g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); | |
657 g_string_append_printf(response, ",nc=00000001"); | |
658 g_string_append_printf(response, ",qop=auth"); | |
659 g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); | |
660 g_string_append_printf(response, ",response=%s", auth_resp); | |
661 g_string_append_printf(response, ",charset=utf-8"); | |
662 | |
663 g_free(auth_resp); | |
664 g_free(cnonce); | |
665 | |
11137 | 666 enc_out = gaim_base64_encode((guchar *)response->str, response->len); |
7291 | 667 |
668 gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded response (%d): %s\n", response->len, response->str); | |
669 | |
670 buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out); | |
671 | |
7642 | 672 jabber_send_raw(js, buf, -1); |
7291 | 673 |
674 g_free(buf); | |
675 | |
676 g_free(enc_out); | |
677 | |
678 g_string_free(response, TRUE); | |
7014 | 679 } |
7291 | 680 |
681 g_free(enc_in); | |
682 g_free(dec_in); | |
683 g_hash_table_destroy(parts); | |
7014 | 684 } |
12508 | 685 #ifdef HAVE_CYRUS_SASL |
686 else if (js->auth_type == JABBER_AUTH_CYRUS) { | |
687 char *enc_in = xmlnode_get_data(packet); | |
688 unsigned char *dec_in; | |
689 char *enc_out; | |
690 const char *c_out; | |
691 unsigned int clen,declen; | |
692 xmlnode *response; | |
693 | |
694 dec_in = gaim_base64_decode(enc_in, &declen); | |
695 | |
696 js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, | |
697 NULL, &c_out, &clen); | |
698 g_free(dec_in); | |
699 if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { | |
700 gaim_debug_error("jabber", "Error is %d : %s\n",js->sasl_state,sasl_errdetail(js->sasl)); | |
701 gaim_connection_error(js->gc, _("SASL error")); | |
702 return; | |
703 } else { | |
704 response = xmlnode_new("response"); | |
705 xmlnode_set_attrib(response, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); | |
706 if (c_out) { | |
707 enc_out = gaim_base64_encode((unsigned char*)c_out, clen); | |
708 xmlnode_insert_data(response, enc_out, -1); | |
709 g_free(enc_out); | |
710 } | |
711 jabber_send(js, response); | |
712 xmlnode_free(response); | |
713 } | |
714 } | |
715 #endif | |
7014 | 716 } |
717 | |
718 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) | |
719 { | |
720 const char *ns = xmlnode_get_attrib(packet, "xmlns"); | |
12508 | 721 #ifdef HAVE_CYRUS_SASL |
722 int *x; | |
723 #endif | |
7014 | 724 |
725 if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { | |
7981 | 726 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 727 return; |
728 } | |
729 | |
12508 | 730 #if HAVE_CYRUS_SASL |
731 /* The SASL docs say that if the client hasn't returned OK yet, we | |
732 * should try one more round against it | |
733 */ | |
734 if (js->sasl_state != SASL_OK) { | |
735 js->sasl_state = sasl_client_step(js->sasl, NULL, 0, NULL, NULL, NULL); | |
736 if (js->sasl_state != SASL_OK) { | |
737 /* This should never happen! */ | |
738 gaim_connection_error(js->gc, _("Invalid response from server.")); | |
739 } | |
740 } | |
741 /* If we've negotiated a security layer, we need to enable it */ | |
742 sasl_getprop(js->sasl, SASL_SSF, (const void **)&x); | |
743 if (*x>0) { | |
744 sasl_getprop(js->sasl, SASL_MAXOUTBUF, (const void **)&x); | |
745 js->sasl_maxbuf = *x; | |
746 } | |
747 #endif | |
748 | |
7014 | 749 jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING); |
750 } | |
751 | |
752 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) | |
753 { | |
8401 | 754 char *msg = jabber_parse_error(js, packet); |
7014 | 755 |
8401 | 756 if(!msg) { |
7981 | 757 gaim_connection_error(js->gc, _("Invalid response from server.")); |
8401 | 758 } else { |
759 gaim_connection_error(js->gc, msg); | |
760 g_free(msg); | |
7014 | 761 } |
762 } |