Mercurial > pidgin
annotate src/protocols/jabber/auth.c @ 8314:db1123eb56b0
[gaim-migrate @ 9038]
wow
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Sun, 22 Feb 2004 01:25:54 +0000 |
parents | dd6fe7d965aa |
children | b63debdf5a92 |
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 #include "sha.h" | |
29 | |
30 #include "debug.h" | |
31 #include "md5.h" | |
32 #include "util.h" | |
33 #include "sslconn.h" | |
34 | |
8296 | 35 gboolean |
36 jabber_process_starttls(JabberStream *js, xmlnode *packet) | |
7014 | 37 { |
38 xmlnode *starttls; | |
39 | |
7157 | 40 if((starttls = xmlnode_get_child(packet, "starttls"))) { |
7630 | 41 if(gaim_account_get_bool(js->gc->account, "use_tls", TRUE) && |
42 gaim_ssl_is_supported()) { | |
7157 | 43 jabber_send_raw(js, |
7642 | 44 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1); |
8296 | 45 return TRUE; |
7157 | 46 } else if(xmlnode_get_child(starttls, "required")) { |
47 gaim_connection_error(js->gc, _("Server requires SSL for login")); | |
8296 | 48 return TRUE; |
7157 | 49 } |
7014 | 50 } |
51 | |
8296 | 52 return FALSE; |
53 } | |
54 | |
55 void | |
56 jabber_auth_start(JabberStream *js, xmlnode *packet) | |
57 { | |
58 xmlnode *mechs, *mechnode; | |
59 xmlnode *auth; | |
60 | |
61 gboolean digest_md5 = FALSE, plain=FALSE; | |
62 | |
63 | |
8016 | 64 if(js->registration) { |
65 jabber_register_start(js); | |
66 return; | |
67 } | |
68 | |
7014 | 69 mechs = xmlnode_get_child(packet, "mechanisms"); |
70 | |
71 if(!mechs) { | |
7981 | 72 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 73 return; |
74 } | |
75 | |
8135 | 76 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; |
77 mechnode = xmlnode_get_next_twin(mechnode)) | |
7014 | 78 { |
8135 | 79 char *mech_name = xmlnode_get_data(mechnode); |
80 if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) | |
81 digest_md5 = TRUE; | |
82 else if(mech_name && !strcmp(mech_name, "PLAIN")) | |
83 plain = TRUE; | |
84 g_free(mech_name); | |
7014 | 85 } |
86 | |
7291 | 87 auth = xmlnode_new("auth"); |
88 xmlnode_set_attrib(auth, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); | |
7703 | 89 |
7645 | 90 if(digest_md5) { |
7291 | 91 xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); |
92 js->auth_type = JABBER_AUTH_DIGEST_MD5; | |
8086 | 93 } else if(plain) { |
94 GString *response; | |
7703 | 95 char *enc_out; |
96 | |
8086 | 97 if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) { |
98 /* XXX: later, make this yes/no so they can just click to enable it */ | |
99 gaim_connection_error(js->gc, | |
100 _("Server requires plaintext authentication over an unencrypted stream")); | |
101 xmlnode_free(auth); | |
102 return; | |
103 } | |
104 | |
105 response = g_string_new(""); | |
7703 | 106 response = g_string_append_len(response, "\0", 1); |
107 response = g_string_append(response, js->user->node); | |
108 response = g_string_append_len(response, "\0", 1); | |
109 response = g_string_append(response, | |
110 gaim_account_get_password(js->gc->account)); | |
111 | |
112 enc_out = gaim_base64_encode(response->str, response->len); | |
113 | |
7642 | 114 xmlnode_set_attrib(auth, "mechanism", "PLAIN"); |
7703 | 115 xmlnode_insert_data(auth, enc_out, -1); |
116 g_free(enc_out); | |
8086 | 117 g_string_free(response, TRUE); |
7703 | 118 |
7291 | 119 js->auth_type = JABBER_AUTH_PLAIN; |
7014 | 120 } else { |
121 gaim_connection_error(js->gc, | |
122 _("Server does not use any supported authentication method")); | |
7291 | 123 xmlnode_free(auth); |
124 return; | |
7014 | 125 } |
7703 | 126 |
7291 | 127 jabber_send(js, auth); |
128 xmlnode_free(auth); | |
7014 | 129 } |
130 | |
7395 | 131 static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) |
7014 | 132 { |
133 const char *type = xmlnode_get_attrib(packet, "type"); | |
134 | |
7730 | 135 if(type && !strcmp(type, "result")) { |
136 jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); | |
137 } else { | |
7014 | 138 xmlnode *error = xmlnode_get_child(packet, "error"); |
7730 | 139 const char *err_code = NULL; |
140 char *err_text = NULL; | |
7014 | 141 char *buf; |
142 | |
7730 | 143 if(error) { |
144 err_code = xmlnode_get_attrib(error, "code"); | |
145 err_text = xmlnode_get_data(error); | |
146 } | |
7014 | 147 |
148 if(!err_code) | |
149 err_code = ""; | |
150 if(!err_text) | |
151 err_text = g_strdup(_("Unknown")); | |
152 | |
153 if(!strcmp(err_code, "401")) | |
154 js->gc->wants_to_die = TRUE; | |
155 | |
156 buf = g_strdup_printf("Error %s: %s", | |
157 err_code, err_text); | |
158 | |
159 gaim_connection_error(js->gc, buf); | |
160 g_free(err_text); | |
161 g_free(buf); | |
162 } | |
163 } | |
164 | |
7395 | 165 static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) |
7014 | 166 { |
167 JabberIq *iq; | |
168 xmlnode *query, *x; | |
169 gboolean digest = FALSE; | |
7514 | 170 const char *type = xmlnode_get_attrib(packet, "type"); |
7014 | 171 const char *pw = gaim_account_get_password(js->gc->account); |
172 | |
7514 | 173 if(!type) { |
7981 | 174 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 175 return; |
7515 | 176 } else if(!strcmp(type, "error")) { |
7644 | 177 /* XXX: still need to handle XMPP-style errors */ |
178 xmlnode *error; | |
179 char *buf, *err_txt = NULL; | |
180 const char *code = NULL; | |
181 if((error = xmlnode_get_child(packet, "error"))) { | |
182 code = xmlnode_get_attrib(error, "code"); | |
183 err_txt = xmlnode_get_data(error); | |
184 } | |
185 buf = g_strdup_printf("%s%s%s", code ? code : "", code ? ": " : "", | |
186 err_txt ? err_txt : _("Unknown Error")); | |
187 gaim_connection_error(js->gc, buf); | |
188 if(err_txt) | |
189 g_free(err_txt); | |
190 g_free(buf); | |
7515 | 191 } else if(!strcmp(type, "result")) { |
7514 | 192 query = xmlnode_get_child(packet, "query"); |
193 if(js->stream_id && xmlnode_get_child(query, "digest")) { | |
194 digest = TRUE; | |
8086 | 195 } else if(xmlnode_get_child(query, "password")) { |
196 if(js->gsc == NULL && !gaim_account_get_bool(js->gc->account, | |
197 "auth_plain_in_clear", FALSE)) { | |
198 /* XXX: later, make this yes/no so they can just click to enable it */ | |
199 gaim_connection_error(js->gc, | |
200 _("Server requires plaintext authentication over an unencrypted stream")); | |
201 return; | |
202 } | |
203 } else { | |
7514 | 204 gaim_connection_error(js->gc, |
205 _("Server does not use any supported authentication method")); | |
206 return; | |
207 } | |
7014 | 208 |
7514 | 209 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); |
210 query = xmlnode_get_child(iq->node, "query"); | |
211 x = xmlnode_new_child(query, "username"); | |
212 xmlnode_insert_data(x, js->user->node, -1); | |
213 x = xmlnode_new_child(query, "resource"); | |
214 xmlnode_insert_data(x, js->user->resource, -1); | |
7014 | 215 |
7514 | 216 if(digest) { |
217 unsigned char hashval[20]; | |
218 char *s, h[41], *p; | |
219 int i; | |
7014 | 220 |
7514 | 221 x = xmlnode_new_child(query, "digest"); |
222 s = g_strdup_printf("%s%s", js->stream_id, pw); | |
223 shaBlock((unsigned char *)s, strlen(s), hashval); | |
224 p = h; | |
225 for(i=0; i<20; i++, p+=2) | |
226 snprintf(p, 3, "%02x", hashval[i]); | |
227 xmlnode_insert_data(x, h, -1); | |
228 g_free(s); | |
229 } else { | |
230 x = xmlnode_new_child(query, "password"); | |
231 xmlnode_insert_data(x, pw, -1); | |
232 } | |
233 | |
234 jabber_iq_set_callback(iq, auth_old_result_cb, NULL); | |
235 | |
236 jabber_iq_send(iq); | |
7014 | 237 } |
238 } | |
239 | |
240 void jabber_auth_start_old(JabberStream *js) | |
241 { | |
242 JabberIq *iq; | |
243 xmlnode *query, *username; | |
244 | |
245 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth"); | |
246 | |
247 query = xmlnode_get_child(iq->node, "query"); | |
248 username = xmlnode_new_child(query, "username"); | |
249 xmlnode_insert_data(username, js->user->node, -1); | |
250 | |
7395 | 251 jabber_iq_set_callback(iq, auth_old_cb, NULL); |
7014 | 252 |
253 jabber_iq_send(iq); | |
254 } | |
255 | |
256 static GHashTable* parse_challenge(const char *challenge) | |
257 { | |
258 GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, | |
259 g_free, g_free); | |
260 char **pairs; | |
261 int i; | |
262 | |
263 pairs = g_strsplit(challenge, ",", -1); | |
264 | |
265 for(i=0; pairs[i]; i++) { | |
266 char **keyval = g_strsplit(pairs[i], "=", 2); | |
267 if(keyval[0] && keyval[1]) { | |
268 if(keyval[1][0] == '"' && keyval[1][strlen(keyval[1])-1] == '"') | |
269 g_hash_table_replace(ret, g_strdup(keyval[0]), g_strndup(keyval[1]+1, strlen(keyval[1])-2)); | |
270 else | |
271 g_hash_table_replace(ret, g_strdup(keyval[0]), g_strdup(keyval[1])); | |
272 } | |
273 g_strfreev(keyval); | |
274 } | |
275 | |
276 g_strfreev(pairs); | |
277 | |
278 return ret; | |
279 } | |
280 | |
281 static unsigned char* | |
282 generate_response_value(JabberID *jid, const char *passwd, const char *nonce, | |
7267 | 283 const char *cnonce, const char *a2, const char *realm) |
7014 | 284 { |
285 md5_state_t ctx; | |
286 md5_byte_t result[16]; | |
287 | |
288 char *x, *y, *a1, *ha1, *ha2, *kd, *z; | |
289 | |
7267 | 290 x = g_strdup_printf("%s:%s:%s", jid->node, realm, passwd); |
7014 | 291 md5_init(&ctx); |
292 md5_append(&ctx, x, strlen(x)); | |
293 md5_finish(&ctx, result); | |
294 | |
295 y = g_strndup(result, 16); | |
296 | |
8223 | 297 a1 = g_strdup_printf("%s:%s:%s", y, nonce, cnonce); |
7014 | 298 |
299 md5_init(&ctx); | |
300 md5_append(&ctx, a1, strlen(a1)); | |
301 md5_finish(&ctx, result); | |
302 | |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
303 ha1 = gaim_base16_encode(result, 16); |
7014 | 304 |
305 md5_init(&ctx); | |
306 md5_append(&ctx, a2, strlen(a2)); | |
307 md5_finish(&ctx, result); | |
308 | |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
309 ha2 = gaim_base16_encode(result, 16); |
7014 | 310 |
311 kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); | |
312 | |
313 md5_init(&ctx); | |
314 md5_append(&ctx, kd, strlen(kd)); | |
315 md5_finish(&ctx, result); | |
316 | |
7106
db6bd3e794d8
[gaim-migrate @ 7671]
Christian Hammond <chipx86@chipx86.com>
parents:
7014
diff
changeset
|
317 z = gaim_base16_encode(result, 16); |
7014 | 318 |
319 g_free(x); | |
320 g_free(y); | |
321 g_free(a1); | |
322 g_free(ha1); | |
323 g_free(ha2); | |
324 g_free(kd); | |
325 | |
326 return z; | |
327 } | |
328 | |
329 void | |
330 jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) | |
331 { | |
332 | |
7703 | 333 if(js->auth_type == JABBER_AUTH_DIGEST_MD5) { |
7291 | 334 char *enc_in = xmlnode_get_data(packet); |
335 char *dec_in; | |
336 char *enc_out; | |
337 GHashTable *parts; | |
7014 | 338 |
7395 | 339 if(!enc_in) { |
7981 | 340 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7395 | 341 return; |
342 } | |
343 | |
7291 | 344 gaim_base64_decode(enc_in, &dec_in, NULL); |
7395 | 345 gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded challenge (%d): %s\n", |
346 strlen(dec_in), dec_in); | |
7291 | 347 |
348 parts = parse_challenge(dec_in); | |
7014 | 349 |
350 | |
7291 | 351 if (g_hash_table_lookup(parts, "rspauth")) { |
352 char *rspauth = g_hash_table_lookup(parts, "rspauth"); | |
7014 | 353 |
354 | |
7291 | 355 if(rspauth && js->expected_rspauth && |
356 !strcmp(rspauth, js->expected_rspauth)) { | |
357 jabber_send_raw(js, | |
7642 | 358 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", |
359 -1); | |
7291 | 360 } else { |
361 gaim_connection_error(js->gc, _("Invalid challenge from server")); | |
362 } | |
363 g_free(js->expected_rspauth); | |
364 } else { | |
365 /* assemble a response, and send it */ | |
366 /* see RFC 2831 */ | |
367 GString *response = g_string_new(""); | |
368 char *a2; | |
369 char *auth_resp; | |
370 char *buf; | |
371 char *cnonce; | |
372 char *realm; | |
373 char *nonce; | |
7014 | 374 |
7291 | 375 /* we're actually supposed to prompt the user for a realm if |
376 * the server doesn't send one, but that really complicates things, | |
377 * so i'm not gonna worry about it until is poses a problem to | |
378 * someone, or I get really bored */ | |
379 realm = g_hash_table_lookup(parts, "realm"); | |
380 if(!realm) | |
381 realm = js->user->domain; | |
7014 | 382 |
7291 | 383 cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), |
384 g_random_int()); | |
385 nonce = g_hash_table_lookup(parts, "nonce"); | |
7014 | 386 |
387 | |
7291 | 388 a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); |
389 auth_resp = generate_response_value(js->user, | |
390 gaim_account_get_password(js->gc->account), nonce, cnonce, a2, realm); | |
391 g_free(a2); | |
392 | |
393 a2 = g_strdup_printf(":xmpp/%s", realm); | |
394 js->expected_rspauth = generate_response_value(js->user, | |
395 gaim_account_get_password(js->gc->account), nonce, cnonce, a2, realm); | |
396 g_free(a2); | |
397 | |
398 | |
399 g_string_append_printf(response, "username=\"%s\"", js->user->node); | |
400 g_string_append_printf(response, ",realm=\"%s\"", realm); | |
401 g_string_append_printf(response, ",nonce=\"%s\"", nonce); | |
402 g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); | |
403 g_string_append_printf(response, ",nc=00000001"); | |
404 g_string_append_printf(response, ",qop=auth"); | |
405 g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); | |
406 g_string_append_printf(response, ",response=%s", auth_resp); | |
407 g_string_append_printf(response, ",charset=utf-8"); | |
408 | |
409 g_free(auth_resp); | |
410 g_free(cnonce); | |
411 | |
412 enc_out = gaim_base64_encode(response->str, response->len); | |
413 | |
414 gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded response (%d): %s\n", response->len, response->str); | |
415 | |
416 buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out); | |
417 | |
7642 | 418 jabber_send_raw(js, buf, -1); |
7291 | 419 |
420 g_free(buf); | |
421 | |
422 g_free(enc_out); | |
423 | |
424 g_string_free(response, TRUE); | |
7014 | 425 } |
7291 | 426 |
427 g_free(enc_in); | |
428 g_free(dec_in); | |
429 g_hash_table_destroy(parts); | |
7014 | 430 } |
431 } | |
432 | |
433 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) | |
434 { | |
435 const char *ns = xmlnode_get_attrib(packet, "xmlns"); | |
436 | |
437 if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { | |
7981 | 438 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 439 return; |
440 } | |
441 | |
442 jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING); | |
443 } | |
444 | |
445 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) | |
446 { | |
447 const char *ns = xmlnode_get_attrib(packet, "xmlns"); | |
448 | |
449 if(!ns) | |
7981 | 450 gaim_connection_error(js->gc, _("Invalid response from server.")); |
7014 | 451 else if(!strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { |
452 if(xmlnode_get_child(packet, "bad-protocol")) { | |
453 gaim_connection_error(js->gc, _("Bad Protocol")); | |
454 } else if(xmlnode_get_child(packet, "encryption-required")) { | |
455 js->gc->wants_to_die = TRUE; | |
456 gaim_connection_error(js->gc, _("Encryption Required")); | |
457 } else if(xmlnode_get_child(packet, "invalid-authzid")) { | |
458 js->gc->wants_to_die = TRUE; | |
459 gaim_connection_error(js->gc, _("Invalid authzid")); | |
460 } else if(xmlnode_get_child(packet, "invalid-mechanism")) { | |
461 js->gc->wants_to_die = TRUE; | |
462 gaim_connection_error(js->gc, _("Invalid Mechanism")); | |
463 } else if(xmlnode_get_child(packet, "invalid-realm")) { | |
464 gaim_connection_error(js->gc, _("Invalid Realm")); | |
465 } else if(xmlnode_get_child(packet, "mechanism-too-weak")) { | |
466 js->gc->wants_to_die = TRUE; | |
467 gaim_connection_error(js->gc, _("Mechanism Too Weak")); | |
468 } else if(xmlnode_get_child(packet, "not-authorized")) { | |
469 js->gc->wants_to_die = TRUE; | |
470 gaim_connection_error(js->gc, _("Not Authorized")); | |
471 } else if(xmlnode_get_child(packet, "temporary-auth-failure")) { | |
472 gaim_connection_error(js->gc, | |
473 _("Temporary Authentication Failure")); | |
474 } else { | |
475 gaim_connection_error(js->gc, _("Authentication Failure")); | |
476 } | |
477 } | |
478 } |