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 }