Coverage Report

Created: 2026-05-16 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/jingle/jingle.c
Line
Count
Source
1
/*
2
 * @file jingle.c
3
 *
4
 * purple - Jabber Protocol Plugin
5
 *
6
 * Purple is the legal property of its developers, whose names are too numerous
7
 * to list here.  Please refer to the COPYRIGHT file distributed with this
8
 * source distribution.
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program; if not, write to the Free Software
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
23
 *
24
 */
25
26
#include "internal.h"
27
#include "network.h"
28
29
#include "content.h"
30
#include "debug.h"
31
#include "jingle.h"
32
#include "session.h"
33
#include "iceudp.h"
34
#include "rawudp.h"
35
#include "rtp.h"
36
37
#include <string.h>
38
#ifdef USE_VV
39
#include <gst/gst.h>
40
#endif
41
42
GType
43
jingle_get_type(const gchar *type)
44
0
{
45
0
  if (type == NULL)
46
0
    return G_TYPE_NONE;
47
48
0
  if (purple_strequal(type, JINGLE_TRANSPORT_RAWUDP))
49
0
    return JINGLE_TYPE_RAWUDP;
50
0
  else if (purple_strequal(type, JINGLE_TRANSPORT_ICEUDP))
51
0
    return JINGLE_TYPE_ICEUDP;
52
#if 0
53
  else if (purple_strequal(type, JINGLE_TRANSPORT_SOCKS))
54
    return JINGLE_TYPE_SOCKS;
55
  else if (purple_strequal(type, JINGLE_TRANSPORT_IBB))
56
    return JINGLE_TYPE_IBB;
57
#endif
58
#ifdef USE_VV
59
  else if (purple_strequal(type, JINGLE_APP_RTP))
60
    return JINGLE_TYPE_RTP;
61
#endif
62
#if 0
63
  else if (purple_strequal(type, JINGLE_APP_FT))
64
    return JINGLE_TYPE_FT;
65
  else if (purple_strequal(type, JINGLE_APP_XML))
66
    return JINGLE_TYPE_XML;
67
#endif
68
0
  else
69
0
    return G_TYPE_NONE;
70
0
}
71
72
static void
73
jingle_handle_unknown_type(JingleSession *session, xmlnode *jingle)
74
0
{
75
  /* Send error */
76
0
}
77
78
static void
79
jingle_handle_content_accept(JingleSession *session, xmlnode *jingle)
80
0
{
81
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
82
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
83
84
0
  for (; content; content = xmlnode_get_next_twin(content)) {
85
0
    const gchar *name = xmlnode_get_attrib(content, "name");
86
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
87
0
    jingle_session_accept_content(session, name, creator);
88
    /* signal here */
89
0
  }
90
0
}
91
92
static void
93
jingle_handle_content_add(JingleSession *session, xmlnode *jingle)
94
0
{
95
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
96
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
97
98
0
  for (; content; content = xmlnode_get_next_twin(content)) {
99
0
    JingleContent *pending_content =
100
0
        jingle_content_parse(content);
101
0
    if (pending_content == NULL) {
102
0
      purple_debug_error("jingle",
103
0
          "Error parsing \"content-add\" content.\n");
104
0
      jabber_iq_send(jingle_session_terminate_packet(session,
105
0
        "unsupported-applications"));
106
0
    } else {
107
0
      jingle_session_add_pending_content(session,
108
0
          pending_content);
109
0
    }
110
0
  }
111
112
  /* XXX: signal here */
113
0
}
114
115
static void
116
jingle_handle_content_modify(JingleSession *session, xmlnode *jingle)
117
0
{
118
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
119
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
120
121
0
  for (; content; content = xmlnode_get_next_twin(content)) {
122
0
    const gchar *name = xmlnode_get_attrib(content, "name");
123
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
124
0
    JingleContent *local_content = jingle_session_find_content(session, name, creator);
125
126
0
    if (local_content != NULL) {
127
0
      const gchar *senders = xmlnode_get_attrib(content, "senders");
128
0
      gchar *local_senders = jingle_content_get_senders(local_content);
129
0
      if (!purple_strequal(senders, local_senders))
130
0
        jingle_content_modify(local_content, senders);
131
0
      g_free(local_senders);
132
0
    } else {
133
0
      purple_debug_error("jingle", "content_modify: unknown content\n");
134
0
      jabber_iq_send(jingle_session_terminate_packet(session,
135
0
        "unknown-applications"));
136
0
    }
137
0
  }
138
0
}
139
140
static void
141
jingle_handle_content_reject(JingleSession *session, xmlnode *jingle)
142
0
{
143
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
144
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
145
146
0
  for (; content; content = xmlnode_get_next_twin(content)) {
147
0
    const gchar *name = xmlnode_get_attrib(content, "name");
148
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
149
0
    jingle_session_remove_pending_content(session, name, creator);
150
    /* signal here */
151
0
  }
152
0
}
153
154
static void
155
jingle_handle_content_remove(JingleSession *session, xmlnode *jingle)
156
0
{
157
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
158
159
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
160
161
0
  for (; content; content = xmlnode_get_next_twin(content)) {
162
0
    const gchar *name = xmlnode_get_attrib(content, "name");
163
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
164
0
    jingle_session_remove_content(session, name, creator);
165
0
  }
166
0
}
167
168
static void
169
jingle_handle_description_info(JingleSession *session, xmlnode *jingle)
170
0
{
171
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
172
173
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
174
175
0
  jingle_session_accept_session(session);
176
177
0
  for (; content; content = xmlnode_get_next_twin(content)) {
178
0
    const gchar *name = xmlnode_get_attrib(content, "name");
179
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
180
0
    JingleContent *parsed_content =
181
0
        jingle_session_find_content(session, name, creator);
182
0
    if (parsed_content == NULL) {
183
0
      purple_debug_error("jingle", "Error parsing content\n");
184
0
      jabber_iq_send(jingle_session_terminate_packet(session,
185
0
        "unsupported-applications"));
186
0
    } else {
187
0
      jingle_content_handle_action(parsed_content, content,
188
0
          JINGLE_DESCRIPTION_INFO);
189
0
    }
190
0
  }
191
0
}
192
193
static void
194
jingle_handle_security_info(JingleSession *session, xmlnode *jingle)
195
0
{
196
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
197
0
}
198
199
static void
200
jingle_handle_session_accept(JingleSession *session, xmlnode *jingle)
201
0
{
202
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
203
204
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
205
206
0
  jingle_session_accept_session(session);
207
208
0
  for (; content; content = xmlnode_get_next_twin(content)) {
209
0
    const gchar *name = xmlnode_get_attrib(content, "name");
210
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
211
0
    JingleContent *parsed_content =
212
0
        jingle_session_find_content(session, name, creator);
213
0
    if (parsed_content == NULL) {
214
0
      purple_debug_error("jingle", "Error parsing content\n");
215
0
      jabber_iq_send(jingle_session_terminate_packet(session,
216
0
        "unsupported-applications"));
217
0
    } else {
218
0
      jingle_content_handle_action(parsed_content, content,
219
0
          JINGLE_SESSION_ACCEPT);
220
0
    }
221
0
  }
222
0
}
223
224
static void
225
jingle_handle_session_info(JingleSession *session, xmlnode *jingle)
226
0
{
227
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
228
  /* XXX: call signal */
229
0
}
230
231
static void
232
jingle_handle_session_initiate(JingleSession *session, xmlnode *jingle)
233
0
{
234
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
235
236
0
  for (; content; content = xmlnode_get_next_twin(content)) {
237
0
    JingleContent *parsed_content = jingle_content_parse(content);
238
0
    if (parsed_content == NULL) {
239
0
      purple_debug_error("jingle", "Error parsing content\n");
240
0
      jabber_iq_send(jingle_session_terminate_packet(session,
241
0
        "unsupported-applications"));
242
0
    } else {
243
0
      jingle_session_add_content(session, parsed_content);
244
0
      jingle_content_handle_action(parsed_content, content,
245
0
          JINGLE_SESSION_INITIATE);
246
0
    }
247
0
  }
248
249
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
250
0
}
251
252
static void
253
jingle_handle_session_terminate(JingleSession *session, xmlnode *jingle)
254
0
{
255
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
256
257
0
  jingle_session_handle_action(session, jingle,
258
0
      JINGLE_SESSION_TERMINATE);
259
  /* display reason? */
260
0
  g_object_unref(session);
261
0
}
262
263
static void
264
jingle_handle_transport_accept(JingleSession *session, xmlnode *jingle)
265
0
{
266
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
267
268
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
269
270
0
  for (; content; content = xmlnode_get_next_twin(content)) {
271
0
    const gchar *name = xmlnode_get_attrib(content, "name");
272
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
273
0
    JingleContent *content = jingle_session_find_content(session, name, creator);
274
0
    jingle_content_accept_transport(content);
275
0
  }
276
0
}
277
278
static void
279
jingle_handle_transport_info(JingleSession *session, xmlnode *jingle)
280
0
{
281
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
282
283
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
284
285
0
  for (; content; content = xmlnode_get_next_twin(content)) {
286
0
    const gchar *name = xmlnode_get_attrib(content, "name");
287
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
288
0
    JingleContent *parsed_content =
289
0
        jingle_session_find_content(session, name, creator);
290
0
    if (parsed_content == NULL) {
291
0
      purple_debug_error("jingle", "Error parsing content\n");
292
0
      jabber_iq_send(jingle_session_terminate_packet(session,
293
0
        "unsupported-applications"));
294
0
    } else {
295
0
      jingle_content_handle_action(parsed_content, content,
296
0
          JINGLE_TRANSPORT_INFO);
297
0
    }
298
0
  }
299
0
}
300
301
static void
302
jingle_handle_transport_reject(JingleSession *session, xmlnode *jingle)
303
0
{
304
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
305
306
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
307
308
0
  for (; content; content = xmlnode_get_next_twin(content)) {
309
0
    const gchar *name = xmlnode_get_attrib(content, "name");
310
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
311
0
    JingleContent *content = jingle_session_find_content(session, name, creator);
312
0
    jingle_content_remove_pending_transport(content);
313
0
  }
314
0
}
315
316
static void
317
jingle_handle_transport_replace(JingleSession *session, xmlnode *jingle)
318
0
{
319
0
  xmlnode *content = xmlnode_get_child(jingle, "content");
320
321
0
  jabber_iq_send(jingle_session_create_ack(session, jingle));
322
323
0
  for (; content; content = xmlnode_get_next_twin(content)) {
324
0
    const gchar *name = xmlnode_get_attrib(content, "name");
325
0
    const gchar *creator = xmlnode_get_attrib(content, "creator");
326
0
    xmlnode *xmltransport = xmlnode_get_child(content, "transport");
327
0
    JingleTransport *transport = jingle_transport_parse(xmltransport);
328
0
    JingleContent *content = jingle_session_find_content(session, name, creator);
329
330
0
    jingle_content_set_pending_transport(content, transport);
331
0
  }
332
0
}
333
334
typedef struct {
335
  const char *name;
336
  void (*handler)(JingleSession*, xmlnode*);
337
} JingleAction;
338
339
static const JingleAction jingle_actions[] = {
340
  {"unknown-type",  jingle_handle_unknown_type},
341
  {"content-accept",  jingle_handle_content_accept},
342
  {"content-add",   jingle_handle_content_add},
343
  {"content-modify",  jingle_handle_content_modify},
344
  {"content-reject",  jingle_handle_content_reject},
345
  {"content-remove",  jingle_handle_content_remove},
346
  {"description-info",  jingle_handle_description_info},
347
  {"security-info", jingle_handle_security_info},
348
  {"session-accept",  jingle_handle_session_accept},
349
  {"session-info",  jingle_handle_session_info},
350
  {"session-initiate",  jingle_handle_session_initiate},
351
  {"session-terminate", jingle_handle_session_terminate},
352
  {"transport-accept",  jingle_handle_transport_accept},
353
  {"transport-info",  jingle_handle_transport_info},
354
  {"transport-reject",  jingle_handle_transport_reject},
355
  {"transport-replace", jingle_handle_transport_replace},
356
};
357
358
const gchar *
359
jingle_get_action_name(JingleActionType action)
360
0
{
361
0
  return jingle_actions[action].name;
362
0
}
363
364
JingleActionType
365
jingle_get_action_type(const gchar *action)
366
0
{
367
0
  static const int num_actions =
368
0
      sizeof(jingle_actions)/sizeof(JingleAction);
369
  /* Start at 1 to skip the unknown-action type */
370
0
  int i = 1;
371
0
  for (; i < num_actions; ++i) {
372
0
    if (purple_strequal(action, jingle_actions[i].name))
373
0
      return i;
374
0
  }
375
0
  return JINGLE_UNKNOWN_TYPE;
376
0
}
377
378
void
379
jingle_parse(JabberStream *js, const char *from, JabberIqType type,
380
             const char *id, xmlnode *jingle)
381
0
{
382
0
  const gchar *action;
383
0
  const gchar *sid;
384
0
  JingleActionType action_type;
385
0
  JingleSession *session;
386
387
0
  if (type != JABBER_IQ_SET) {
388
    /* TODO: send iq error here */
389
0
    return;
390
0
  }
391
392
0
  if (!(action = xmlnode_get_attrib(jingle, "action"))) {
393
    /* TODO: send iq error here */
394
0
    return;
395
0
  }
396
397
0
  action_type = jingle_get_action_type(action);
398
399
0
  purple_debug_info("jabber", "got Jingle package action = %s\n",
400
0
        action);
401
402
0
  if (!(sid = xmlnode_get_attrib(jingle, "sid"))) {
403
    /* send iq error here */
404
0
    return;
405
0
  }
406
407
0
  if (!(session = jingle_session_find_by_sid(js, sid))
408
0
      && !purple_strequal(action, "session-initiate")) {
409
0
    purple_debug_error("jingle", "jabber_jingle_session_parse couldn't find session\n");
410
    /* send iq error here */
411
0
    return;
412
0
  }
413
414
0
  if (action_type == JINGLE_SESSION_INITIATE) {
415
0
    if (session) {
416
      /* This should only happen if you start a session with yourself */
417
0
      purple_debug_error("jingle", "Jingle session with "
418
0
          "id={%s} already exists\n", sid);
419
      /* send iq error */
420
0
      return;
421
0
    } else {
422
0
      char *own_jid = g_strdup_printf("%s@%s/%s", js->user->node,
423
0
          js->user->domain, js->user->resource);
424
0
      session = jingle_session_create(js, sid, own_jid, from, FALSE);
425
0
      g_free(own_jid);
426
0
    }
427
0
  }
428
429
0
  jingle_actions[action_type].handler(session, jingle);
430
0
}
431
432
static void
433
jingle_terminate_sessions_gh(gpointer key, gpointer value, gpointer user_data)
434
0
{
435
0
  g_object_unref(value);
436
0
}
437
438
void
439
jingle_terminate_sessions(JabberStream *js)
440
0
{
441
0
  if (js->sessions)
442
0
    g_hash_table_foreach(js->sessions,
443
        jingle_terminate_sessions_gh, NULL);
444
0
}
445
446
#ifdef USE_VV
447
static GValueArray *
448
jingle_create_relay_info(const gchar *ip, guint port, const gchar *username,
449
  const gchar *password, const gchar *relay_type, GValueArray *relay_info)
450
{
451
  GValue value;
452
  GstStructure *turn_setup = gst_structure_new("relay-info",
453
    "ip", G_TYPE_STRING, ip,
454
    "port", G_TYPE_UINT, port,
455
    "username", G_TYPE_STRING, username,
456
    "password", G_TYPE_STRING, password,
457
    "relay-type", G_TYPE_STRING, relay_type,
458
    NULL);
459
  purple_debug_info("jabber", "created gst_structure %p\n",
460
    turn_setup);
461
  if (turn_setup) {
462
    memset(&value, 0, sizeof(GValue));
463
    g_value_init(&value, GST_TYPE_STRUCTURE);
464
    gst_value_set_structure(&value, turn_setup);
465
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
466
    relay_info = g_value_array_append(relay_info, &value);
467
G_GNUC_END_IGNORE_DEPRECATIONS
468
    gst_structure_free(turn_setup);
469
  }
470
  return relay_info;
471
}
472
473
GParameter *
474
jingle_get_params(JabberStream *js, const gchar *relay_ip, guint relay_udp,
475
  guint relay_tcp, guint relay_ssltcp, const gchar *relay_username,
476
    const gchar *relay_password, guint *num)
477
{
478
  /* don't set a STUN server if one is set globally in prefs, in that case
479
   this will be handled in media.c */
480
  gboolean has_account_stun = js->stun_ip && !purple_network_get_stun_ip();
481
  guint num_params = has_account_stun ?
482
    (relay_ip ? 3 : 2) : (relay_ip ? 1 : 0);
483
  GParameter *params = NULL;
484
  int next_index = 0;
485
486
  if (num_params > 0) {
487
    params = g_new0(GParameter, num_params);
488
489
    if (has_account_stun) {
490
      purple_debug_info("jabber",
491
        "setting param stun-ip for stream using Google auto-config: %s\n",
492
        js->stun_ip);
493
      params[next_index].name = "stun-ip";
494
      g_value_init(&params[next_index].value, G_TYPE_STRING);
495
      g_value_set_string(&params[next_index].value, js->stun_ip);
496
      purple_debug_info("jabber",
497
        "setting param stun-port for stream using Google auto-config: %d\n",
498
        js->stun_port);
499
      next_index++;
500
      params[next_index].name = "stun-port";
501
      g_value_init(&params[next_index].value, G_TYPE_UINT);
502
      g_value_set_uint(&params[next_index].value, js->stun_port);
503
      next_index++;
504
    }
505
506
    if (relay_ip) {
507
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
508
      GValueArray *relay_info = g_value_array_new(0);
509
G_GNUC_END_IGNORE_DEPRECATIONS
510
511
      if (relay_udp) {
512
        relay_info =
513
          jingle_create_relay_info(relay_ip, relay_udp, relay_username,
514
            relay_password, "udp", relay_info);
515
      }
516
      if (relay_tcp) {
517
        relay_info =
518
          jingle_create_relay_info(relay_ip, relay_tcp, relay_username,
519
            relay_password, "tcp", relay_info);
520
      }
521
      if (relay_ssltcp) {
522
        relay_info =
523
          jingle_create_relay_info(relay_ip, relay_ssltcp, relay_username,
524
            relay_password, "tls", relay_info);
525
      }
526
      params[next_index].name = "relay-info";
527
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
528
      g_value_init(&params[next_index].value, G_TYPE_VALUE_ARRAY);
529
      g_value_set_boxed(&params[next_index].value, relay_info);
530
      g_value_array_free(relay_info);
531
G_GNUC_END_IGNORE_DEPRECATIONS
532
    }
533
  }
534
535
  *num = num_params;
536
  return params;
537
}
538
#endif
539