Mercurial > pidgin
comparison libpurple/protocols/jabber/auth_digest_md5.c @ 28696:8ada06fb65ed
jabber: Factor the SASL auth methods into their own files.
This works with and without Cyrus SASL, though there's room for cleanup
and de-duplication (some code is now duplicated between auth.c and
auth_cyrus.c).
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Sat, 07 Nov 2009 06:10:17 +0000 |
parents | |
children | cea22db36ffc |
comparison
equal
deleted
inserted
replaced
28525:13e668ef158d | 28696:8ada06fb65ed |
---|---|
1 /* | |
2 * purple - Jabber Protocol Plugin | |
3 * | |
4 * Purple is the legal property of its developers, whose names are too numerous | |
5 * to list here. Please refer to the COPYRIGHT file distributed with this | |
6 * source distribution. | |
7 * | |
8 * This program is free software; you can redistribute it and/or modify | |
9 * it under the terms of the GNU General Public License as published by | |
10 * the Free Software Foundation; either version 2 of the License, or | |
11 * (at your option) any later version. | |
12 * | |
13 * This program is distributed in the hope that it will be useful, | |
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 * GNU General Public License for more details. | |
17 * | |
18 * You should have received a copy of the GNU General Public License | |
19 * along with this program; if not, write to the Free Software | |
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA | |
21 * | |
22 */ | |
23 #include "internal.h" | |
24 | |
25 #include "debug.h" | |
26 #include "cipher.h" | |
27 #include "util.h" | |
28 #include "xmlnode.h" | |
29 | |
30 #include "auth.h" | |
31 #include "jabber.h" | |
32 | |
33 static xmlnode *digest_md5_start(JabberStream *js, xmlnode *packet) | |
34 { | |
35 xmlnode *auth; | |
36 | |
37 auth = xmlnode_new("auth"); | |
38 xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); | |
39 xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); | |
40 | |
41 return auth; | |
42 } | |
43 | |
44 /* Parts of this algorithm are inspired by stuff in libgsasl */ | |
45 static GHashTable* parse_challenge(const char *challenge) | |
46 { | |
47 const char *token_start, *val_start, *val_end, *cur; | |
48 GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, | |
49 g_free, g_free); | |
50 | |
51 cur = challenge; | |
52 while(*cur != '\0') { | |
53 /* Find the end of the token */ | |
54 gboolean in_quotes = FALSE; | |
55 char *name, *value = NULL; | |
56 token_start = cur; | |
57 while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) { | |
58 if (*cur == '"') | |
59 in_quotes = !in_quotes; | |
60 cur++; | |
61 } | |
62 | |
63 /* Find start of value. */ | |
64 val_start = strchr(token_start, '='); | |
65 if (val_start == NULL || val_start > cur) | |
66 val_start = cur; | |
67 | |
68 if (token_start != val_start) { | |
69 name = g_strndup(token_start, val_start - token_start); | |
70 | |
71 if (val_start != cur) { | |
72 val_start++; | |
73 while (val_start != cur && (*val_start == ' ' || *val_start == '\t' | |
74 || *val_start == '\r' || *val_start == '\n' | |
75 || *val_start == '"')) | |
76 val_start++; | |
77 | |
78 val_end = cur; | |
79 while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t' | |
80 || *val_end == '\r' || *val_end == '\n' | |
81 || *val_end == '"' || *val_end == '\0')) | |
82 val_end--; | |
83 | |
84 if (val_start != val_end) | |
85 value = g_strndup(val_start, val_end - val_start + 1); | |
86 } | |
87 | |
88 g_hash_table_replace(ret, name, value); | |
89 } | |
90 | |
91 /* Find the start of the next token, if there is one */ | |
92 if (*cur != '\0') { | |
93 cur++; | |
94 while (*cur == ' ' || *cur == ',' || *cur == '\t' | |
95 || *cur == '\r' || *cur == '\n') | |
96 cur++; | |
97 } | |
98 } | |
99 | |
100 return ret; | |
101 } | |
102 | |
103 static char * | |
104 generate_response_value(JabberID *jid, const char *passwd, const char *nonce, | |
105 const char *cnonce, const char *a2, const char *realm) | |
106 { | |
107 PurpleCipher *cipher; | |
108 PurpleCipherContext *context; | |
109 guchar result[16]; | |
110 size_t a1len; | |
111 | |
112 gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; | |
113 | |
114 if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", | |
115 NULL, NULL, NULL)) == NULL) { | |
116 convnode = g_strdup(jid->node); | |
117 } | |
118 if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", | |
119 "utf-8", NULL, NULL, NULL)) == NULL)) { | |
120 convpasswd = g_strdup(passwd); | |
121 } | |
122 | |
123 cipher = purple_ciphers_find_cipher("md5"); | |
124 context = purple_cipher_context_new(cipher, NULL); | |
125 | |
126 x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : ""); | |
127 purple_cipher_context_append(context, (const guchar *)x, strlen(x)); | |
128 purple_cipher_context_digest(context, sizeof(result), result, NULL); | |
129 | |
130 a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); | |
131 a1len = strlen(a1); | |
132 g_memmove(a1, result, 16); | |
133 | |
134 purple_cipher_context_reset(context, NULL); | |
135 purple_cipher_context_append(context, (const guchar *)a1, a1len); | |
136 purple_cipher_context_digest(context, sizeof(result), result, NULL); | |
137 | |
138 ha1 = purple_base16_encode(result, 16); | |
139 | |
140 purple_cipher_context_reset(context, NULL); | |
141 purple_cipher_context_append(context, (const guchar *)a2, strlen(a2)); | |
142 purple_cipher_context_digest(context, sizeof(result), result, NULL); | |
143 | |
144 ha2 = purple_base16_encode(result, 16); | |
145 | |
146 kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); | |
147 | |
148 purple_cipher_context_reset(context, NULL); | |
149 purple_cipher_context_append(context, (const guchar *)kd, strlen(kd)); | |
150 purple_cipher_context_digest(context, sizeof(result), result, NULL); | |
151 purple_cipher_context_destroy(context); | |
152 | |
153 z = purple_base16_encode(result, 16); | |
154 | |
155 g_free(convnode); | |
156 g_free(convpasswd); | |
157 g_free(x); | |
158 g_free(a1); | |
159 g_free(ha1); | |
160 g_free(ha2); | |
161 g_free(kd); | |
162 | |
163 return z; | |
164 } | |
165 | |
166 static xmlnode *digest_md5_handle_challenge(JabberStream *js, xmlnode *packet) | |
167 { | |
168 xmlnode *reply = NULL; | |
169 char *enc_in = xmlnode_get_data(packet); | |
170 char *dec_in; | |
171 char *enc_out; | |
172 GHashTable *parts; | |
173 | |
174 if (!enc_in) { | |
175 purple_connection_error_reason(js->gc, | |
176 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
177 _("Invalid response from server")); | |
178 return NULL; | |
179 } | |
180 | |
181 dec_in = (char *)purple_base64_decode(enc_in, NULL); | |
182 purple_debug_misc("jabber", "decoded challenge (%" | |
183 G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); | |
184 | |
185 parts = parse_challenge(dec_in); | |
186 | |
187 if (g_hash_table_lookup(parts, "rspauth")) { | |
188 char *rspauth = g_hash_table_lookup(parts, "rspauth"); | |
189 | |
190 if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { | |
191 reply = xmlnode_new("response"); | |
192 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); | |
193 } else { | |
194 purple_connection_error_reason(js->gc, | |
195 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
196 _("Invalid challenge from server")); | |
197 } | |
198 g_free(js->expected_rspauth); | |
199 js->expected_rspauth = NULL; | |
200 } else { | |
201 /* assemble a response, and send it */ | |
202 /* see RFC 2831 */ | |
203 char *realm; | |
204 char *nonce; | |
205 | |
206 /* Make sure the auth string contains everything that should be there. | |
207 This isn't everything in RFC2831, but it is what we need. */ | |
208 | |
209 nonce = g_hash_table_lookup(parts, "nonce"); | |
210 | |
211 /* we're actually supposed to prompt the user for a realm if | |
212 * the server doesn't send one, but that really complicates things, | |
213 * so i'm not gonna worry about it until is poses a problem to | |
214 * someone, or I get really bored */ | |
215 realm = g_hash_table_lookup(parts, "realm"); | |
216 if(!realm) | |
217 realm = js->user->domain; | |
218 | |
219 if (nonce == NULL || realm == NULL) | |
220 purple_connection_error_reason(js->gc, | |
221 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, | |
222 _("Invalid challenge from server")); | |
223 else { | |
224 GString *response = g_string_new(""); | |
225 char *a2; | |
226 char *auth_resp; | |
227 char *cnonce; | |
228 | |
229 cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), | |
230 g_random_int()); | |
231 | |
232 a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); | |
233 auth_resp = generate_response_value(js->user, | |
234 purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); | |
235 g_free(a2); | |
236 | |
237 a2 = g_strdup_printf(":xmpp/%s", realm); | |
238 js->expected_rspauth = generate_response_value(js->user, | |
239 purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); | |
240 g_free(a2); | |
241 | |
242 g_string_append_printf(response, "username=\"%s\"", js->user->node); | |
243 g_string_append_printf(response, ",realm=\"%s\"", realm); | |
244 g_string_append_printf(response, ",nonce=\"%s\"", nonce); | |
245 g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); | |
246 g_string_append_printf(response, ",nc=00000001"); | |
247 g_string_append_printf(response, ",qop=auth"); | |
248 g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); | |
249 g_string_append_printf(response, ",response=%s", auth_resp); | |
250 g_string_append_printf(response, ",charset=utf-8"); | |
251 | |
252 g_free(auth_resp); | |
253 g_free(cnonce); | |
254 | |
255 enc_out = purple_base64_encode((guchar *)response->str, response->len); | |
256 | |
257 purple_debug_misc("jabber", "decoded response (%" | |
258 G_GSIZE_FORMAT "): %s\n", | |
259 response->len, response->str); | |
260 | |
261 reply = xmlnode_new("response"); | |
262 xmlnode_set_namespace(reply, "urn:ietf:params:xml:ns:xmpp-sasl"); | |
263 xmlnode_insert_data(reply, enc_out, -1); | |
264 | |
265 g_free(enc_out); | |
266 | |
267 g_string_free(response, TRUE); | |
268 } | |
269 } | |
270 | |
271 g_free(enc_in); | |
272 g_free(dec_in); | |
273 g_hash_table_destroy(parts); | |
274 | |
275 return reply; | |
276 } | |
277 | |
278 static JabberSaslMech digest_md5_mech = { | |
279 10, /* priority */ | |
280 "DIGEST-MD5", /* name */ | |
281 digest_md5_start, | |
282 digest_md5_handle_challenge, | |
283 NULL, /* handle_success */ | |
284 NULL, /* handle_failure */ | |
285 NULL /* handle_dispose */ | |
286 }; | |
287 | |
288 JabberSaslMech *jabber_auth_get_digest_md5_mech(void) | |
289 { | |
290 return &digest_md5_mech; | |
291 } |