| 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; |
| 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) |