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)