Mercurial > pidgin
comparison 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 |
comparison
equal
deleted
inserted
replaced
12507:5bf6c0c908b2 | 12508:5cfc53ead482 |
---|---|
112 static void disallow_plaintext_auth(GaimAccount *account) | 112 static void disallow_plaintext_auth(GaimAccount *account) |
113 { | 113 { |
114 gaim_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream")); | 114 gaim_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream")); |
115 } | 115 } |
116 | 116 |
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 | |
117 void | 289 void |
118 jabber_auth_start(JabberStream *js, xmlnode *packet) | 290 jabber_auth_start(JabberStream *js, xmlnode *packet) |
119 { | 291 { |
292 #ifdef HAVE_CYRUS_SASL | |
293 int id; | |
294 #else | |
295 gboolean digest_md5 = FALSE, plain=FALSE; | |
296 #endif | |
297 | |
120 xmlnode *mechs, *mechnode; | 298 xmlnode *mechs, *mechnode; |
121 | |
122 gboolean digest_md5 = FALSE, plain=FALSE; | |
123 | 299 |
124 | 300 |
125 if(js->registration) { | 301 if(js->registration) { |
126 jabber_register_start(js); | 302 jabber_register_start(js); |
127 return; | 303 return; |
131 | 307 |
132 if(!mechs) { | 308 if(!mechs) { |
133 gaim_connection_error(js->gc, _("Invalid response from server.")); | 309 gaim_connection_error(js->gc, _("Invalid response from server.")); |
134 return; | 310 return; |
135 } | 311 } |
312 | |
313 #ifdef HAVE_CYRUS_SASL | |
314 js->sasl_mechs = g_string_new(""); | |
315 #endif | |
136 | 316 |
137 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; | 317 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; |
138 mechnode = xmlnode_get_next_twin(mechnode)) | 318 mechnode = xmlnode_get_next_twin(mechnode)) |
139 { | 319 { |
140 char *mech_name = xmlnode_get_data(mechnode); | 320 char *mech_name = xmlnode_get_data(mechnode); |
321 #ifdef HAVE_CYRUS_SASL | |
322 g_string_append(js->sasl_mechs, mech_name); | |
323 g_string_append_c(js->sasl_mechs,' '); | |
324 #else | |
141 if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) | 325 if(mech_name && !strcmp(mech_name, "DIGEST-MD5")) |
142 digest_md5 = TRUE; | 326 digest_md5 = TRUE; |
143 else if(mech_name && !strcmp(mech_name, "PLAIN")) | 327 else if(mech_name && !strcmp(mech_name, "PLAIN")) |
144 plain = TRUE; | 328 plain = TRUE; |
329 #endif | |
145 g_free(mech_name); | 330 g_free(mech_name); |
146 } | 331 } |
147 | 332 |
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 | |
148 | 366 |
149 if(digest_md5) { | 367 if(digest_md5) { |
150 xmlnode *auth; | 368 xmlnode *auth; |
151 | 369 |
152 js->auth_type = JABBER_AUTH_DIGEST_MD5; | 370 js->auth_type = JABBER_AUTH_DIGEST_MD5; |
170 finish_plaintext_authentication(js); | 388 finish_plaintext_authentication(js); |
171 } else { | 389 } else { |
172 gaim_connection_error(js->gc, | 390 gaim_connection_error(js->gc, |
173 _("Server does not use any supported authentication method")); | 391 _("Server does not use any supported authentication method")); |
174 } | 392 } |
393 #endif | |
175 } | 394 } |
176 | 395 |
177 static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) | 396 static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data) |
178 { | 397 { |
179 const char *type = xmlnode_get_attrib(packet, "type"); | 398 const char *type = xmlnode_get_attrib(packet, "type"); |
461 | 680 |
462 g_free(enc_in); | 681 g_free(enc_in); |
463 g_free(dec_in); | 682 g_free(dec_in); |
464 g_hash_table_destroy(parts); | 683 g_hash_table_destroy(parts); |
465 } | 684 } |
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 | |
466 } | 716 } |
467 | 717 |
468 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) | 718 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) |
469 { | 719 { |
470 const char *ns = xmlnode_get_attrib(packet, "xmlns"); | 720 const char *ns = xmlnode_get_attrib(packet, "xmlns"); |
721 #ifdef HAVE_CYRUS_SASL | |
722 int *x; | |
723 #endif | |
471 | 724 |
472 if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { | 725 if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { |
473 gaim_connection_error(js->gc, _("Invalid response from server.")); | 726 gaim_connection_error(js->gc, _("Invalid response from server.")); |
474 return; | 727 return; |
475 } | 728 } |
729 | |
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 | |
476 | 748 |
477 jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING); | 749 jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING); |
478 } | 750 } |
479 | 751 |
480 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) | 752 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) |