Mercurial > pidgin.yaz
comparison libpurple/protocols/jabber/google.c @ 26213:ff4212a5268f
propagate from branch 'im.pidgin.pidgin' (head 431618de0f30a6938f7e14d2d61ee5d7738acd59)
to branch 'im.pidgin.pidgin.vv' (head 8df00cb1a28baa69d0a68e0e96af201ec7d87c09)
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Mon, 02 Mar 2009 18:47:27 +0000 |
parents | 8aa7d8bcbc7d a773b465935e |
children | 2ad89aff8d68 |
comparison
equal
deleted
inserted
replaced
25446:52fbda23e398 | 26213:ff4212a5268f |
---|---|
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA | 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
19 */ | 19 */ |
20 | 20 |
21 #include "internal.h" | 21 #include "internal.h" |
22 #include "debug.h" | 22 #include "debug.h" |
23 #include "mediamanager.h" | |
23 #include "util.h" | 24 #include "util.h" |
24 #include "privacy.h" | 25 #include "privacy.h" |
26 #include "dnsquery.h" | |
27 #include "network.h" | |
25 | 28 |
26 #include "buddy.h" | 29 #include "buddy.h" |
27 #include "google.h" | 30 #include "google.h" |
28 #include "jabber.h" | 31 #include "jabber.h" |
29 #include "presence.h" | 32 #include "presence.h" |
30 #include "iq.h" | 33 #include "iq.h" |
34 | |
35 #include "jingle/jingle.h" | |
36 | |
37 #ifdef USE_VV | |
38 | |
39 typedef struct { | |
40 char *id; | |
41 char *initiator; | |
42 } GoogleSessionId; | |
43 | |
44 typedef enum { | |
45 UNINIT, | |
46 SENT_INITIATE, | |
47 RECEIVED_INITIATE, | |
48 IN_PRORESS, | |
49 TERMINATED | |
50 } GoogleSessionState; | |
51 | |
52 typedef struct { | |
53 GoogleSessionId id; | |
54 GoogleSessionState state; | |
55 PurpleMedia *media; | |
56 JabberStream *js; | |
57 char *remote_jid; | |
58 } GoogleSession; | |
59 | |
60 static gboolean | |
61 google_session_id_equal(gconstpointer a, gconstpointer b) | |
62 { | |
63 GoogleSessionId *c = (GoogleSessionId*)a; | |
64 GoogleSessionId *d = (GoogleSessionId*)b; | |
65 | |
66 return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator); | |
67 } | |
68 | |
69 static void | |
70 google_session_destroy(GoogleSession *session) | |
71 { | |
72 g_free(session->id.id); | |
73 g_free(session->id.initiator); | |
74 g_free(session->remote_jid); | |
75 g_free(session); | |
76 } | |
77 | |
78 static xmlnode * | |
79 google_session_create_xmlnode(GoogleSession *session, const char *type) | |
80 { | |
81 xmlnode *node = xmlnode_new("session"); | |
82 xmlnode_set_namespace(node, "http://www.google.com/session"); | |
83 xmlnode_set_attrib(node, "id", session->id.id); | |
84 xmlnode_set_attrib(node, "initiator", session->id.initiator); | |
85 xmlnode_set_attrib(node, "type", type); | |
86 return node; | |
87 } | |
88 | |
89 static void | |
90 google_session_send_terminate(GoogleSession *session) | |
91 { | |
92 xmlnode *sess; | |
93 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
94 | |
95 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
96 sess = google_session_create_xmlnode(session, "terminate"); | |
97 xmlnode_insert_child(iq->node, sess); | |
98 | |
99 jabber_iq_send(iq); | |
100 google_session_destroy(session); | |
101 } | |
102 | |
103 static void | |
104 google_session_send_candidates(PurpleMedia *media, gchar *session_id, | |
105 gchar *participant, GoogleSession *session) | |
106 { | |
107 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
108 GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", | |
109 session->remote_jid); | |
110 PurpleMediaCandidate *transport; | |
111 xmlnode *sess; | |
112 xmlnode *candidate; | |
113 sess = google_session_create_xmlnode(session, "candidates"); | |
114 xmlnode_insert_child(iq->node, sess); | |
115 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
116 | |
117 for (;candidates;candidates = candidates->next) { | |
118 char port[8]; | |
119 char pref[8]; | |
120 transport = (PurpleMediaCandidate*)(candidates->data); | |
121 | |
122 if (!strcmp(transport->ip, "127.0.0.1")) | |
123 continue; | |
124 | |
125 candidate = xmlnode_new("candidate"); | |
126 | |
127 g_snprintf(port, sizeof(port), "%d", transport->port); | |
128 g_snprintf(pref, sizeof(pref), "%d", transport->priority); | |
129 | |
130 xmlnode_set_attrib(candidate, "address", transport->ip); | |
131 xmlnode_set_attrib(candidate, "port", port); | |
132 xmlnode_set_attrib(candidate, "name", "rtp"); | |
133 xmlnode_set_attrib(candidate, "username", transport->username); | |
134 /* | |
135 * As of this writing, Farsight 2 in Google compatibility | |
136 * mode doesn't provide a password. The Gmail client | |
137 * requires this to be set. | |
138 */ | |
139 xmlnode_set_attrib(candidate, "password", | |
140 transport->password != NULL ? | |
141 transport->password : ""); | |
142 xmlnode_set_attrib(candidate, "preference", pref); | |
143 xmlnode_set_attrib(candidate, "protocol", transport->proto == | |
144 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP ? "udp" : "tcp"); | |
145 xmlnode_set_attrib(candidate, "type", transport->type == | |
146 PURPLE_MEDIA_CANDIDATE_TYPE_HOST ? "local" : | |
147 transport->type == | |
148 PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX ? "stun" : | |
149 transport->type == | |
150 PURPLE_MEDIA_CANDIDATE_TYPE_RELAY ? "relay" : NULL); | |
151 xmlnode_set_attrib(candidate, "generation", "0"); | |
152 xmlnode_set_attrib(candidate, "network", "0"); | |
153 xmlnode_insert_child(sess, candidate); | |
154 } | |
155 jabber_iq_send(iq); | |
156 } | |
157 | |
158 static void | |
159 google_session_ready(PurpleMedia *media, gchar *id, | |
160 gchar *participant, GoogleSession *session) | |
161 { | |
162 if (id == NULL && participant == NULL) { | |
163 gchar *me = g_strdup_printf("%s@%s/%s", | |
164 session->js->user->node, | |
165 session->js->user->domain, | |
166 session->js->user->resource); | |
167 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
168 xmlnode *sess, *desc, *payload; | |
169 GList *codecs, *iter; | |
170 | |
171 if (!strcmp(session->id.initiator, me)) { | |
172 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
173 xmlnode_set_attrib(iq->node, "from", session->id.initiator); | |
174 sess = google_session_create_xmlnode(session, "initiate"); | |
175 } else { | |
176 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
177 xmlnode_set_attrib(iq->node, "from", me); | |
178 sess = google_session_create_xmlnode(session, "accept"); | |
179 } | |
180 xmlnode_insert_child(iq->node, sess); | |
181 desc = xmlnode_new_child(sess, "description"); | |
182 xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); | |
183 | |
184 codecs = purple_media_get_codecs(media, "google-voice"); | |
185 | |
186 for (iter = codecs; iter; iter = g_list_next(iter)) { | |
187 PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; | |
188 gchar *id = g_strdup_printf("%d", codec->id); | |
189 gchar *clock_rate = g_strdup_printf("%d", codec->clock_rate); | |
190 payload = xmlnode_new_child(desc, "payload-type"); | |
191 xmlnode_set_attrib(payload, "id", id); | |
192 xmlnode_set_attrib(payload, "name", codec->encoding_name); | |
193 xmlnode_set_attrib(payload, "clockrate", clock_rate); | |
194 g_free(clock_rate); | |
195 g_free(id); | |
196 } | |
197 purple_media_codec_list_free(codecs); | |
198 | |
199 jabber_iq_send(iq); | |
200 | |
201 google_session_send_candidates(session->media, | |
202 "google-voice", session->remote_jid, session); | |
203 } | |
204 } | |
205 | |
206 static void | |
207 google_session_state_changed_cb(PurpleMedia *media, | |
208 PurpleMediaStateChangedType type, | |
209 gchar *sid, gchar *name, GoogleSession *session) | |
210 { | |
211 if (sid == NULL && name == NULL) { | |
212 if (type == PURPLE_MEDIA_STATE_CHANGED_END) { | |
213 google_session_destroy(session); | |
214 } else if (type == PURPLE_MEDIA_STATE_CHANGED_HANGUP) { | |
215 xmlnode *sess; | |
216 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
217 | |
218 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
219 sess = google_session_create_xmlnode(session, "terminate"); | |
220 xmlnode_insert_child(iq->node, sess); | |
221 | |
222 jabber_iq_send(iq); | |
223 } else if (type == PURPLE_MEDIA_STATE_CHANGED_REJECTED) { | |
224 xmlnode *sess; | |
225 JabberIq *iq = jabber_iq_new(session->js, JABBER_IQ_SET); | |
226 | |
227 xmlnode_set_attrib(iq->node, "to", session->remote_jid); | |
228 sess = google_session_create_xmlnode(session, "reject"); | |
229 xmlnode_insert_child(iq->node, sess); | |
230 | |
231 jabber_iq_send(iq); | |
232 } | |
233 | |
234 } | |
235 } | |
236 | |
237 static GParameter * | |
238 jabber_google_session_get_params(JabberStream *js, guint *num) | |
239 { | |
240 guint num_params; | |
241 GParameter *params = jingle_get_params(js, &num_params); | |
242 GParameter *new_params = g_new0(GParameter, num_params + 1); | |
243 | |
244 memcpy(new_params, params, sizeof(GParameter) * num_params); | |
245 | |
246 purple_debug_info("jabber", "setting Google jingle compatibility param\n"); | |
247 new_params[num_params].name = "compatibility-mode"; | |
248 g_value_init(&new_params[num_params].value, G_TYPE_UINT); | |
249 g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */ | |
250 | |
251 g_free(params); | |
252 *num = num_params + 1; | |
253 return new_params; | |
254 } | |
255 | |
256 | |
257 PurpleMedia* | |
258 jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type) | |
259 { | |
260 GoogleSession *session; | |
261 JabberBuddy *jb; | |
262 JabberBuddyResource *jbr; | |
263 gchar *jid; | |
264 GParameter *params; | |
265 guint num_params; | |
266 | |
267 /* construct JID to send to */ | |
268 jb = jabber_buddy_find(js, who, FALSE); | |
269 if (!jb) { | |
270 purple_debug_error("jingle-rtp", | |
271 "Could not find Jabber buddy\n"); | |
272 return NULL; | |
273 } | |
274 jbr = jabber_buddy_find_resource(jb, NULL); | |
275 if (!jbr) { | |
276 purple_debug_error("jingle-rtp", | |
277 "Could not find buddy's resource\n"); | |
278 } | |
279 | |
280 if ((strchr(who, '/') == NULL) && jbr && (jbr->name != NULL)) { | |
281 jid = g_strdup_printf("%s/%s", who, jbr->name); | |
282 } else { | |
283 jid = g_strdup(who); | |
284 } | |
285 | |
286 session = g_new0(GoogleSession, 1); | |
287 session->id.id = jabber_get_next_id(js); | |
288 session->id.initiator = g_strdup_printf("%s@%s/%s", js->user->node, | |
289 js->user->domain, js->user->resource); | |
290 session->state = SENT_INITIATE; | |
291 session->js = js; | |
292 session->remote_jid = jid; | |
293 | |
294 session->media = purple_media_manager_create_media( | |
295 purple_media_manager_get(), js->gc, | |
296 "fsrtpconference", session->remote_jid, TRUE); | |
297 | |
298 purple_media_set_prpl_data(session->media, session); | |
299 | |
300 params = jabber_google_session_get_params(js, &num_params); | |
301 | |
302 if (purple_media_add_stream(session->media, "google-voice", | |
303 session->remote_jid, PURPLE_MEDIA_AUDIO, | |
304 "nice", num_params, params) == FALSE) { | |
305 purple_media_error(session->media, "Error adding stream."); | |
306 purple_media_hangup(session->media); | |
307 google_session_destroy(session); | |
308 g_free(params); | |
309 return NULL; | |
310 } | |
311 | |
312 g_signal_connect(G_OBJECT(session->media), "ready-new", | |
313 G_CALLBACK(google_session_ready), session); | |
314 g_signal_connect(G_OBJECT(session->media), "state-changed", | |
315 G_CALLBACK(google_session_state_changed_cb), session); | |
316 | |
317 g_free(params); | |
318 | |
319 return session->media; | |
320 } | |
321 | |
322 static void | |
323 google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) | |
324 { | |
325 JabberIq *result; | |
326 GList *codecs = NULL; | |
327 xmlnode *desc_element, *codec_element; | |
328 PurpleMediaCodec *codec; | |
329 const char *id, *encoding_name, *clock_rate; | |
330 GParameter *params; | |
331 guint num_params; | |
332 | |
333 if (session->state != UNINIT) { | |
334 purple_debug_error("jabber", "Received initiate for active session.\n"); | |
335 return; | |
336 } | |
337 | |
338 session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc, | |
339 "fsrtpconference", session->remote_jid, FALSE); | |
340 | |
341 purple_media_set_prpl_data(session->media, session); | |
342 | |
343 params = jabber_google_session_get_params(js, &num_params); | |
344 | |
345 if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, | |
346 PURPLE_MEDIA_AUDIO, "nice", num_params, params) == FALSE) { | |
347 purple_media_error(session->media, "Error adding stream."); | |
348 purple_media_hangup(session->media); | |
349 google_session_send_terminate(session); | |
350 g_free(params); | |
351 return; | |
352 } | |
353 | |
354 g_free(params); | |
355 | |
356 desc_element = xmlnode_get_child(sess, "description"); | |
357 | |
358 for (codec_element = xmlnode_get_child(desc_element, "payload-type"); | |
359 codec_element; | |
360 codec_element = xmlnode_get_next_twin(codec_element)) { | |
361 encoding_name = xmlnode_get_attrib(codec_element, "name"); | |
362 id = xmlnode_get_attrib(codec_element, "id"); | |
363 clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); | |
364 | |
365 codec = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_AUDIO, | |
366 clock_rate ? atoi(clock_rate) : 0); | |
367 codecs = g_list_append(codecs, codec); | |
368 } | |
369 | |
370 purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs); | |
371 | |
372 g_signal_connect(G_OBJECT(session->media), "ready-new", | |
373 G_CALLBACK(google_session_ready), session); | |
374 g_signal_connect(G_OBJECT(session->media), "state-changed", | |
375 G_CALLBACK(google_session_state_changed_cb), session); | |
376 | |
377 purple_media_codec_list_free(codecs); | |
378 | |
379 result = jabber_iq_new(js, JABBER_IQ_RESULT); | |
380 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); | |
381 xmlnode_set_attrib(result->node, "to", session->remote_jid); | |
382 jabber_iq_send(result); | |
383 } | |
384 | |
385 static void | |
386 google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) | |
387 { | |
388 JabberIq *result; | |
389 GList *list = NULL; | |
390 xmlnode *cand; | |
391 static int name = 0; | |
392 char n[4]; | |
393 | |
394 for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { | |
395 PurpleMediaCandidate *info; | |
396 g_snprintf(n, sizeof(n), "S%d", name++); | |
397 info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP, | |
398 !strcmp(xmlnode_get_attrib(cand, "type"), "local") ? | |
399 PURPLE_MEDIA_CANDIDATE_TYPE_HOST : | |
400 !strcmp(xmlnode_get_attrib(cand, "type"), "stun") ? | |
401 PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX : | |
402 !strcmp(xmlnode_get_attrib(cand, "type"), "relay") ? | |
403 PURPLE_MEDIA_CANDIDATE_TYPE_RELAY : | |
404 PURPLE_MEDIA_CANDIDATE_TYPE_HOST, | |
405 !strcmp(xmlnode_get_attrib(cand, "protocol"),"udp") ? | |
406 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP : | |
407 PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, | |
408 xmlnode_get_attrib(cand, "address"), | |
409 atoi(xmlnode_get_attrib(cand, "port"))); | |
410 | |
411 info->username = g_strdup(xmlnode_get_attrib(cand, "username")); | |
412 info->password = g_strdup(xmlnode_get_attrib(cand, "password")); | |
413 | |
414 list = g_list_append(list, info); | |
415 } | |
416 | |
417 purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list); | |
418 purple_media_candidate_list_free(list); | |
419 | |
420 result = jabber_iq_new(js, JABBER_IQ_RESULT); | |
421 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); | |
422 xmlnode_set_attrib(result->node, "to", session->remote_jid); | |
423 jabber_iq_send(result); | |
424 } | |
425 | |
426 static void | |
427 google_session_handle_accept(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) | |
428 { | |
429 xmlnode *desc_element = xmlnode_get_child(sess, "description"); | |
430 xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type"); | |
431 GList *codecs = NULL; | |
432 | |
433 for (; codec_element; codec_element = | |
434 xmlnode_get_next_twin(codec_element)) { | |
435 const gchar *encoding_name = | |
436 xmlnode_get_attrib(codec_element, "name"); | |
437 const gchar *id = xmlnode_get_attrib(codec_element, "id"); | |
438 const gchar *clock_rate = | |
439 xmlnode_get_attrib(codec_element, "clockrate"); | |
440 | |
441 PurpleMediaCodec *codec = purple_media_codec_new(atoi(id), | |
442 encoding_name, PURPLE_MEDIA_AUDIO, | |
443 clock_rate ? atoi(clock_rate) : 0); | |
444 codecs = g_list_append(codecs, codec); | |
445 } | |
446 | |
447 purple_media_set_remote_codecs(session->media, "google-voice", | |
448 session->remote_jid, codecs); | |
449 | |
450 purple_media_accept(session->media); | |
451 } | |
452 | |
453 static void | |
454 google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) | |
455 { | |
456 purple_media_end(session->media, NULL, NULL); | |
457 } | |
458 | |
459 static void | |
460 google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *packet, xmlnode *sess) | |
461 { | |
462 purple_media_end(session->media, NULL, NULL); | |
463 } | |
464 | |
465 static void | |
466 google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *packet) | |
467 { | |
468 xmlnode *sess = xmlnode_get_child(packet, "session"); | |
469 const char *type = xmlnode_get_attrib(sess, "type"); | |
470 | |
471 if (!strcmp(type, "initiate")) { | |
472 google_session_handle_initiate(js, session, packet, sess); | |
473 } else if (!strcmp(type, "accept")) { | |
474 google_session_handle_accept(js, session, packet, sess); | |
475 } else if (!strcmp(type, "reject")) { | |
476 google_session_handle_reject(js, session, packet, sess); | |
477 } else if (!strcmp(type, "terminate")) { | |
478 google_session_handle_terminate(js, session, packet, sess); | |
479 } else if (!strcmp(type, "candidates")) { | |
480 google_session_handle_candidates(js, session, packet, sess); | |
481 } | |
482 } | |
483 #endif /* USE_VV */ | |
484 | |
485 void | |
486 jabber_google_session_parse(JabberStream *js, xmlnode *packet) | |
487 { | |
488 #ifdef USE_VV | |
489 GoogleSession *session = NULL; | |
490 GoogleSessionId id; | |
491 | |
492 xmlnode *session_node; | |
493 xmlnode *desc_node; | |
494 | |
495 GList *iter = NULL; | |
496 | |
497 if (strcmp(xmlnode_get_attrib(packet, "type"), "set")) | |
498 return; | |
499 | |
500 session_node = xmlnode_get_child(packet, "session"); | |
501 if (!session_node) | |
502 return; | |
503 | |
504 id.id = (gchar*)xmlnode_get_attrib(session_node, "id"); | |
505 if (!id.id) | |
506 return; | |
507 | |
508 id.initiator = (gchar*)xmlnode_get_attrib(session_node, "initiator"); | |
509 if (!id.initiator) | |
510 return; | |
511 | |
512 iter = purple_media_manager_get_media_by_connection( | |
513 purple_media_manager_get(), js->gc); | |
514 for (; iter; iter = g_list_delete_link(iter, iter)) { | |
515 GoogleSession *gsession = | |
516 purple_media_get_prpl_data(iter->data); | |
517 if (google_session_id_equal(&(gsession->id), &id)) { | |
518 session = gsession; | |
519 break; | |
520 } | |
521 } | |
522 if (iter != NULL) { | |
523 g_list_free(iter); | |
524 } | |
525 | |
526 if (session) { | |
527 google_session_parse_iq(js, session, packet); | |
528 return; | |
529 } | |
530 | |
531 /* If the session doesn't exist, this has to be an initiate message */ | |
532 if (strcmp(xmlnode_get_attrib(session_node, "type"), "initiate")) | |
533 return; | |
534 desc_node = xmlnode_get_child(session_node, "description"); | |
535 if (!desc_node) | |
536 return; | |
537 session = g_new0(GoogleSession, 1); | |
538 session->id.id = g_strdup(id.id); | |
539 session->id.initiator = g_strdup(id.initiator); | |
540 session->state = UNINIT; | |
541 session->js = js; | |
542 session->remote_jid = g_strdup(session->id.initiator); | |
543 | |
544 google_session_parse_iq(js, session, packet); | |
545 #else | |
546 /* TODO: send proper error response */ | |
547 #endif /* USE_VV */ | |
548 } | |
31 | 549 |
32 static void | 550 static void |
33 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) | 551 jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) |
34 { | 552 { |
35 const char *type = xmlnode_get_attrib(packet, "type"); | 553 const char *type = xmlnode_get_attrib(packet, "type"); |
527 char *jabber_google_presence_outgoing(PurpleStatus *tune) | 1045 char *jabber_google_presence_outgoing(PurpleStatus *tune) |
528 { | 1046 { |
529 const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); | 1047 const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); |
530 return attr ? g_strdup_printf("♫ %s", attr) : g_strdup(""); | 1048 return attr ? g_strdup_printf("♫ %s", attr) : g_strdup(""); |
531 } | 1049 } |
1050 | |
1051 static void | |
1052 jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, | |
1053 const char *error_message) | |
1054 { | |
1055 JabberStream *js = (JabberStream *) data; | |
1056 | |
1057 if (error_message) { | |
1058 purple_debug_error("jabber", "Google STUN lookup failed: %s\n", | |
1059 error_message); | |
1060 g_slist_free(hosts); | |
1061 return; | |
1062 } | |
1063 | |
1064 if (hosts && g_slist_next(hosts)) { | |
1065 struct sockaddr *addr = g_slist_next(hosts)->data; | |
1066 char dst[INET6_ADDRSTRLEN]; | |
1067 int port; | |
1068 | |
1069 if (addr->sa_family == AF_INET6) { | |
1070 inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, | |
1071 dst, sizeof(dst)); | |
1072 port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); | |
1073 } else { | |
1074 inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, | |
1075 dst, sizeof(dst)); | |
1076 port = ntohs(((struct sockaddr_in *) addr)->sin_port); | |
1077 } | |
1078 | |
1079 if (js) { | |
1080 if (js->stun_ip) { | |
1081 g_free(js->stun_ip); | |
1082 } | |
1083 js->stun_ip = g_strdup(dst); | |
1084 purple_debug_info("jabber", "set Google STUN IP address: %s\n", dst); | |
1085 js->stun_port = port; | |
1086 purple_debug_info("jabber", "set Google STUN port: %d\n", port); | |
1087 purple_debug_info("jabber", "set Google STUN port: %d\n", port); | |
1088 /* unmark ongoing query */ | |
1089 js->stun_query = NULL; | |
1090 } | |
1091 } | |
1092 | |
1093 g_slist_free(hosts); | |
1094 } | |
1095 | |
1096 static void | |
1097 jabber_google_jingle_info_cb(JabberStream *js, xmlnode *result, | |
1098 gpointer nullus) | |
1099 { | |
1100 if (result) { | |
1101 const xmlnode *query = | |
1102 xmlnode_get_child_with_namespace(result, "query", | |
1103 GOOGLE_JINGLE_INFO_NAMESPACE); | |
1104 | |
1105 if (query) { | |
1106 const xmlnode *stun = xmlnode_get_child(query, "stun"); | |
1107 | |
1108 purple_debug_info("jabber", "got google:jingleinfo\n"); | |
1109 | |
1110 if (stun) { | |
1111 xmlnode *server = xmlnode_get_child(stun, "server"); | |
1112 | |
1113 if (server) { | |
1114 const gchar *host = xmlnode_get_attrib(server, "host"); | |
1115 const gchar *udp = xmlnode_get_attrib(server, "udp"); | |
1116 | |
1117 if (host && udp) { | |
1118 int port = atoi(udp); | |
1119 /* if there, would already be an ongoing query, | |
1120 cancel it */ | |
1121 if (js->stun_query) | |
1122 purple_dnsquery_destroy(js->stun_query); | |
1123 | |
1124 js->stun_query = purple_dnsquery_a(host, port, | |
1125 jabber_google_stun_lookup_cb, js); | |
1126 } | |
1127 } | |
1128 } | |
1129 /* should perhaps handle relays later on, or maybe wait until | |
1130 Google supports a common standard... */ | |
1131 } | |
1132 } | |
1133 } | |
1134 | |
1135 void | |
1136 jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet) | |
1137 { | |
1138 jabber_google_jingle_info_cb(js, packet, NULL); | |
1139 } | |
1140 | |
1141 void | |
1142 jabber_google_send_jingle_info(JabberStream *js) | |
1143 { | |
1144 JabberIq *jingle_info = | |
1145 jabber_iq_new_query(js, JABBER_IQ_GET, GOOGLE_JINGLE_INFO_NAMESPACE); | |
1146 | |
1147 jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb, | |
1148 NULL); | |
1149 purple_debug_info("jabber", "sending google:jingleinfo query\n"); | |
1150 jabber_iq_send(jingle_info); | |
1151 } |