Mercurial > pidgin.yaz
diff src/protocols/jabber/auth.c @ 7014:67c4e9d39242
[gaim-migrate @ 7577]
Here it is, the bulk of the new Jabber prpl.
Left to do:
- Implement registration
- Implement password changing
- Keep track of conversation threads (since I apparently have to)
- Fix the bugs that always magically appear in code after I commit
committer: Tailor Script <tailor@pidgin.im>
author | Nathan Walp <nwalp@pidgin.im> |
---|---|
date | Mon, 29 Sep 2003 15:23:19 +0000 |
parents | |
children | db6bd3e794d8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/jabber/auth.c Mon Sep 29 15:23:19 2003 +0000 @@ -0,0 +1,378 @@ +/* + * gaim - Jabber Protocol Plugin + * + * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "internal.h" + +#include "jutil.h" +#include "auth.h" +#include "xmlnode.h" +#include "jabber.h" +#include "iq.h" +#include "sha.h" + +#include "debug.h" +#include "md5.h" +#include "util.h" +#include "sslconn.h" + + +void +jabber_auth_start(JabberStream *js, xmlnode *packet) +{ + xmlnode *mechs, *mechnode; + xmlnode *starttls; + + gboolean digest_md5 = FALSE; + + if(gaim_ssl_is_supported() && + (starttls = xmlnode_get_child(packet, "starttls"))) { + jabber_send_raw(js, + "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); + return; + } + + mechs = xmlnode_get_child(packet, "mechanisms"); + + if(!mechs) { + gaim_connection_error(js->gc, _("Invalid response from server")); + return; + } + + for(mechnode = mechs->child; mechnode; mechnode = mechnode->next) + { + if(mechnode->type == NODE_TYPE_TAG) { + char *mech_name = xmlnode_get_data(mechnode); + if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) + digest_md5 = TRUE; + g_free(mech_name); + } + } + + if(digest_md5) { + jabber_send_raw(js, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" + " mechanism='DIGEST-MD5' />"); + } else { + gaim_connection_error(js->gc, + _("Server does not use any supported authentication method")); + } +} + +static void auth_old_result_cb(JabberStream *js, xmlnode *packet) +{ + const char *type = xmlnode_get_attrib(packet, "type"); + + if(type && !strcmp(type, "error")) { + xmlnode *error = xmlnode_get_child(packet, "error"); + const char *err_code; + char *err_text; + char *buf; + + err_code = xmlnode_get_attrib(error, "code"); + err_text = xmlnode_get_data(error); + + if(!err_code) + err_code = ""; + if(!err_text) + err_text = g_strdup(_("Unknown")); + + if(!strcmp(err_code, "401")) + js->gc->wants_to_die = TRUE; + + buf = g_strdup_printf("Error %s: %s", + err_code, err_text); + + gaim_connection_error(js->gc, buf); + g_free(err_text); + g_free(buf); + } + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); +} + +static void auth_old_cb(JabberStream *js, xmlnode *packet) +{ + JabberIq *iq; + xmlnode *query, *x; + gboolean digest = FALSE; + const char *pw = gaim_account_get_password(js->gc->account); + + query = xmlnode_get_child(packet, "query"); + if(js->stream_id && xmlnode_get_child(query, "digest")) { + digest = TRUE; + } else if(!xmlnode_get_child(query, "password")) { + gaim_connection_error(js->gc, + _("Server does not use any supported authentication method")); + return; + } + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + + if(digest) { + unsigned char hashval[20]; + char *s, h[41], *p; + int i; + + x = xmlnode_new_child(query, "digest"); + s = g_strdup_printf("%s%s", js->stream_id, pw); + shaBlock((unsigned char *)s, strlen(s), hashval); + p = h; + for(i=0; i<20; i++, p+=2) + snprintf(p, 3, "%02x", hashval[i]); + xmlnode_insert_data(x, h, -1); + g_free(s); + } else { + x = xmlnode_new_child(query, "password"); + xmlnode_insert_data(x, pw, -1); + } + + jabber_iq_set_callback(iq, auth_old_result_cb); + + jabber_iq_send(iq); +} + +void jabber_auth_start_old(JabberStream *js) +{ + JabberIq *iq; + xmlnode *query, *username; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth"); + + query = xmlnode_get_child(iq->node, "query"); + username = xmlnode_new_child(query, "username"); + xmlnode_insert_data(username, js->user->node, -1); + + jabber_iq_set_callback(iq, auth_old_cb); + + jabber_iq_send(iq); +} + +static GHashTable* parse_challenge(const char *challenge) +{ + GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + char **pairs; + int i; + + pairs = g_strsplit(challenge, ",", -1); + + for(i=0; pairs[i]; i++) { + char **keyval = g_strsplit(pairs[i], "=", 2); + if(keyval[0] && keyval[1]) { + if(keyval[1][0] == '"' && keyval[1][strlen(keyval[1])-1] == '"') + g_hash_table_replace(ret, g_strdup(keyval[0]), g_strndup(keyval[1]+1, strlen(keyval[1])-2)); + else + g_hash_table_replace(ret, g_strdup(keyval[0]), g_strdup(keyval[1])); + } + g_strfreev(keyval); + } + + g_strfreev(pairs); + + return ret; +} + +static unsigned char* +generate_response_value(JabberID *jid, const char *passwd, const char *nonce, + const char *cnonce, const char *a2) +{ + md5_state_t ctx; + md5_byte_t result[16]; + + char *x, *y, *a1, *ha1, *ha2, *kd, *z; + + x = g_strdup_printf("%s:%s:%s", jid->node, jid->domain, passwd); + md5_init(&ctx); + md5_append(&ctx, x, strlen(x)); + md5_finish(&ctx, result); + + y = g_strndup(result, 16); + + a1 = g_strdup_printf("%s:%s:%s:%s@%s/%s", y, nonce, cnonce, jid->node, + jid->domain, jid->resource); + + md5_init(&ctx); + md5_append(&ctx, a1, strlen(a1)); + md5_finish(&ctx, result); + + ha1 = tobase16(result, 16); + + md5_init(&ctx); + md5_append(&ctx, a2, strlen(a2)); + md5_finish(&ctx, result); + + ha2 = tobase16(result, 16); + + kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); + + md5_init(&ctx); + md5_append(&ctx, kd, strlen(kd)); + md5_finish(&ctx, result); + + z = tobase16(result, 16); + + g_free(x); + g_free(y); + g_free(a1); + g_free(ha1); + g_free(ha2); + g_free(kd); + + return z; +} + +void +jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) +{ + char *enc_in = xmlnode_get_data(packet); + char *dec_in; + char *enc_out; + GHashTable *parts; + + frombase64(enc_in, &dec_in, NULL); + + parts = parse_challenge(dec_in); + + /* we're actually supposed to prompt the user for a realm if + * the server doesn't send one, but that really complicates things, + * so i'm not gonna worry about it until is poses a problem to someone, + * or I get really bored */ + + if(g_hash_table_lookup(parts, "realm")) { + /* assemble a response, and send it */ + /* see RFC 2831 */ + GString *response = g_string_new(""); + char *a2; + char *auth_resp; + char *buf; + char *cnonce; + char *realm; + char *nonce; + + cnonce = g_strdup_printf("%p%u%p", js, (int)time(NULL), packet); + nonce = g_hash_table_lookup(parts, "nonce"); + realm = g_hash_table_lookup(parts, "realm"); + + a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); + auth_resp = generate_response_value(js->user, + gaim_account_get_password(js->gc->account), nonce, cnonce, a2); + g_free(a2); + + a2 = g_strdup_printf(":xmpp/%s", realm); + js->expected_rspauth = generate_response_value(js->user, + gaim_account_get_password(js->gc->account), nonce, cnonce, a2); + g_free(a2); + + + g_string_append_printf(response, "username=\"%s\"", js->user->node); + g_string_append_printf(response, ",realm=\"%s\"", realm); + g_string_append_printf(response, ",nonce=\"%s\"", nonce); + g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); + g_string_append_printf(response, ",nc=00000001"); + g_string_append_printf(response, ",qop=auth"); + g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); + g_string_append_printf(response, ",response=%s", auth_resp); + g_string_append_printf(response, ",charset=utf-8"); + g_string_append_printf(response, ",authzid=\"%s\"", + gaim_account_get_username(js->gc->account)); + + g_free(auth_resp); + g_free(cnonce); + + enc_out = tobase64(response->str, response->len); + + gaim_debug(GAIM_DEBUG_MISC, "jabber", "decoded response (%d): %s\n", response->len, response->str); + + buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out); + + jabber_send_raw(js, buf); + + g_free(buf); + + g_free(enc_out); + + g_string_free(response, TRUE); + } else if (g_hash_table_lookup(parts, "rspauth")) { + char *rspauth = g_hash_table_lookup(parts, "rspauth"); + + + if(rspauth && !strcmp(rspauth, js->expected_rspauth)) { + jabber_send_raw(js, + "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />"); + } else { + gaim_connection_error(js->gc, _("Invalid challenge from server")); + } + g_free(js->expected_rspauth); + } + + g_free(enc_in); + g_free(dec_in); + g_hash_table_destroy(parts); +} + +void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) +{ + const char *ns = xmlnode_get_attrib(packet, "xmlns"); + + if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + gaim_connection_error(js->gc, _("Invalid response from server")); + return; + } + + jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING); +} + +void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) +{ + const char *ns = xmlnode_get_attrib(packet, "xmlns"); + + if(!ns) + gaim_connection_error(js->gc, _("Invalid response from server")); + else if(!strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if(xmlnode_get_child(packet, "bad-protocol")) { + gaim_connection_error(js->gc, _("Bad Protocol")); + } else if(xmlnode_get_child(packet, "encryption-required")) { + js->gc->wants_to_die = TRUE; + gaim_connection_error(js->gc, _("Encryption Required")); + } else if(xmlnode_get_child(packet, "invalid-authzid")) { + js->gc->wants_to_die = TRUE; + gaim_connection_error(js->gc, _("Invalid authzid")); + } else if(xmlnode_get_child(packet, "invalid-mechanism")) { + js->gc->wants_to_die = TRUE; + gaim_connection_error(js->gc, _("Invalid Mechanism")); + } else if(xmlnode_get_child(packet, "invalid-realm")) { + gaim_connection_error(js->gc, _("Invalid Realm")); + } else if(xmlnode_get_child(packet, "mechanism-too-weak")) { + js->gc->wants_to_die = TRUE; + gaim_connection_error(js->gc, _("Mechanism Too Weak")); + } else if(xmlnode_get_child(packet, "not-authorized")) { + js->gc->wants_to_die = TRUE; + gaim_connection_error(js->gc, _("Not Authorized")); + } else if(xmlnode_get_child(packet, "temporary-auth-failure")) { + gaim_connection_error(js->gc, + _("Temporary Authentication Failure")); + } else { + gaim_connection_error(js->gc, _("Authentication Failure")); + } + } +}