gstrtspconnection.c 105 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) <2005-2009> Wim Taymans <wim.taymans@gmail.com>
3 4 5 6 7 8 9 10 11 12 13 14 15
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
16 17
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
 */
/*
 * Unless otherwise indicated, Source Code is licensed under MIT license.
 * See further explanation attached in License Statement (distributed in the file
 * LICENSE).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

43 44 45 46
/**
 * SECTION:gstrtspconnection
 * @short_description: manage RTSP connections
 * @see_also: gstrtspurl
47
 *
48 49 50 51
 * This object manages the RTSP connection to the server. It provides function
 * to receive and send bytes and messages.
 */

52 53 54 55 56 57 58 59 60 61 62 63
#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* we include this here to get the G_OS_* defines */
#include <glib.h>
64
#include <gst/gst.h>
65

66
/* necessary for IP_TOS define */
67
#if GLIB_CHECK_VERSION(2, 36, 0)
68
#include <gio/gnetworking.h>
69
#endif
70

71 72
#include "gstrtspconnection.h"

Sebastian Dröge's avatar
Sebastian Dröge committed
73
#ifdef IP_TOS
74 75 76 77 78 79 80
union gst_sockaddr
{
  struct sockaddr sa;
  struct sockaddr_in sa_in;
  struct sockaddr_in6 sa_in6;
  struct sockaddr_storage sa_stor;
};
Sebastian Dröge's avatar
Sebastian Dröge committed
81
#endif
82

83 84 85 86
typedef struct
{
  gint state;
  guint save;
87
  guchar out[3];                /* the size must be evenly divisible by 3 */
88
  guint cout;
89
  guint coutl;
90 91
} DecodeCtx;

92 93 94 95 96 97
#ifdef MSG_NOSIGNAL
#define SEND_FLAGS MSG_NOSIGNAL
#else
#define SEND_FLAGS 0
#endif

98 99 100 101 102 103 104 105 106 107
typedef enum
{
  TUNNEL_STATE_NONE,
  TUNNEL_STATE_GET,
  TUNNEL_STATE_POST,
  TUNNEL_STATE_COMPLETE
} GstRTSPTunnelState;

#define TUNNELID_LEN   24

108 109 110
struct _GstRTSPConnection
{
  /*< private > */
111
  /* URL for the remote connection */
112 113
  GstRTSPUrl *url;

114
  gboolean server;
115
  GSocketClient *client;
116 117
  GIOStream *stream0;
  GIOStream *stream1;
118

119 120
  GInputStream *input_stream;
  GOutputStream *output_stream;
121 122 123
  /* this is a read source we add on the write socket in tunneled mode to be
   * able to detect when client disconnects the GET channel */
  GInputStream *control_stream;
124

125
  /* connection state */
Sebastian Dröge's avatar
Sebastian Dröge committed
126 127 128
  GSocket *read_socket;
  GSocket *write_socket;
  GSocket *socket0, *socket1;
129
  gboolean manual_http;
130
  gboolean may_cancel;
Sebastian Dröge's avatar
Sebastian Dröge committed
131
  GCancellable *cancellable;
132

133
  gchar tunnelid[TUNNELID_LEN];
134
  gboolean tunneled;
135
  GstRTSPTunnelState tstate;
136

137 138 139
  /* the remote and local ip */
  gchar *remote_ip;
  gchar *local_ip;
140

141 142
  gint read_ahead;

143 144 145
  gchar *initial_buffer;
  gsize initial_buffer_offset;

146 147
  gboolean remember_session_id; /* remember the session id or not */

148 149 150 151 152 153 154 155 156 157 158
  /* Session state */
  gint cseq;                    /* sequence number */
  gchar session_id[512];        /* session id */
  gint timeout;                 /* session timeout in seconds */
  GTimer *timer;                /* timeout timer */

  /* Authentication */
  GstRTSPAuthMethod auth_method;
  gchar *username;
  gchar *passwd;
  GHashTable *auth_params;
159

160 161 162
  /* TLS */
  GTlsDatabase *tls_database;

163 164
  DecodeCtx ctx;
  DecodeCtx *ctxp;
Wim Taymans's avatar
Wim Taymans committed
165 166 167

  gchar *proxy_host;
  guint proxy_port;
168 169
};

170 171 172 173 174 175 176 177 178 179
enum
{
  STATE_START = 0,
  STATE_DATA_HEADER,
  STATE_DATA_BODY,
  STATE_READ_LINES,
  STATE_END,
  STATE_LAST
};

180 181 182 183 184 185 186
enum
{
  READ_AHEAD_EOH = -1,          /* end of headers */
  READ_AHEAD_CRLF = -2,
  READ_AHEAD_CRLFCR = -3
};

187 188 189 190
/* a structure for constructing RTSPMessages */
typedef struct
{
  gint state;
191
  GstRTSPResult status;
192 193 194 195 196 197 198 199 200 201 202 203
  guint8 buffer[4096];
  guint offset;

  guint line;
  guint8 *body_data;
  glong body_len;
} GstRTSPBuilder;

static void
build_reset (GstRTSPBuilder * builder)
{
  g_free (builder->body_data);
204
  memset (builder, 0, sizeof (GstRTSPBuilder));
205 206
}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
static gboolean
tls_accept_certificate (GTlsConnection * conn, GTlsCertificate * peer_cert,
    GTlsCertificateFlags errors, GstRTSPConnection * rtspconn)
{
  GError *error = NULL;
  gboolean accept = FALSE;

  if (rtspconn->tls_database) {
    GSocketConnectable *peer_identity;
    GTlsCertificateFlags validation_flags;

    GST_DEBUG ("TLS peer certificate not accepted, checking user database...");

    peer_identity =
        g_tls_client_connection_get_server_identity (G_TLS_CLIENT_CONNECTION
        (conn));

    errors =
        g_tls_database_verify_chain (rtspconn->tls_database, peer_cert,
        G_TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER, peer_identity,
        g_tls_connection_get_interaction (conn), G_TLS_DATABASE_VERIFY_NONE,
        NULL, &error);

    if (error)
      goto verify_error;

    validation_flags = gst_rtsp_connection_get_tls_validation_flags (rtspconn);

    accept = ((errors & validation_flags) == 0);
    if (accept)
      GST_DEBUG ("Peer certificate accepted");
    else
      GST_DEBUG ("Peer certificate not accepted (errors: 0x%08X)", errors);
  }

  return accept;

/* ERRORS */
verify_error:
  {
    GST_ERROR ("An error occurred while verifying the peer certificate: %s",
        error->message);
    g_clear_error (&error);
    return FALSE;
  }
}

static void
socket_client_event (GSocketClient * client, GSocketClientEvent event,
    GSocketConnectable * connectable, GIOStream * connection,
    GstRTSPConnection * rtspconn)
{
  if (event == G_SOCKET_CLIENT_TLS_HANDSHAKING) {
    GST_DEBUG ("TLS handshaking about to start...");

    g_signal_connect (connection, "accept-certificate",
        (GCallback) tls_accept_certificate, rtspconn);
  }
}

267 268
/**
 * gst_rtsp_connection_create:
269
 * @url: a #GstRTSPUrl
270
 * @conn: (out) (transfer full): storage for a #GstRTSPConnection
271 272 273 274 275
 *
 * Create a newly allocated #GstRTSPConnection from @url and store it in @conn.
 * The connection will not yet attempt to connect to @url, use
 * gst_rtsp_connection_connect().
 *
276 277
 * A copy of @url will be made.
 *
278 279
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 */
280
GstRTSPResult
281
gst_rtsp_connection_create (const GstRTSPUrl * url, GstRTSPConnection ** conn)
282 283 284 285
{
  GstRTSPConnection *newconn;

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
Wim Taymans's avatar
Wim Taymans committed
286
  g_return_val_if_fail (url != NULL, GST_RTSP_EINVAL);
287 288 289

  newconn = g_new0 (GstRTSPConnection, 1);

290
  newconn->may_cancel = TRUE;
Sebastian Dröge's avatar
Sebastian Dröge committed
291
  newconn->cancellable = g_cancellable_new ();
292
  newconn->client = g_socket_client_new ();
293

Wim Taymans's avatar
Wim Taymans committed
294 295 296
  if (url->transports & GST_RTSP_LOWER_TRANS_TLS)
    g_socket_client_set_tls (newconn->client, TRUE);

297 298 299
  g_signal_connect (newconn->client, "event", (GCallback) socket_client_event,
      newconn);

300
  newconn->url = gst_rtsp_url_copy (url);
301
  newconn->timer = g_timer_new ();
302
  newconn->timeout = 60;
303
  newconn->cseq = 1;
304

305 306
  newconn->remember_session_id = TRUE;

307 308 309
  newconn->auth_method = GST_RTSP_AUTH_NONE;
  newconn->username = NULL;
  newconn->passwd = NULL;
310
  newconn->auth_params = NULL;
311 312 313 314 315 316

  *conn = newconn;

  return GST_RTSP_OK;
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
static gboolean
collect_addresses (GSocket * socket, gchar ** ip, guint16 * port,
    gboolean remote, GError ** error)
{
  GSocketAddress *addr;

  if (remote)
    addr = g_socket_get_remote_address (socket, error);
  else
    addr = g_socket_get_local_address (socket, error);
  if (!addr)
    return FALSE;

  if (ip)
    *ip = g_inet_address_to_string (g_inet_socket_address_get_address
        (G_INET_SOCKET_ADDRESS (addr)));
  if (port)
    *port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (addr));

  g_object_unref (addr);

  return TRUE;
}


Wim Taymans's avatar
Wim Taymans committed
342
/**
Sebastian Dröge's avatar
Sebastian Dröge committed
343 344
 * gst_rtsp_connection_create_from_socket:
 * @socket: a #GSocket
345 346 347
 * @ip: the IP address of the other end
 * @port: the port used by the other end
 * @initial_buffer: data already read from @fd
348
 * @conn: (out) (transfer full): storage for a #GstRTSPConnection
Wim Taymans's avatar
Wim Taymans committed
349
 *
350
 * Create a new #GstRTSPConnection for handling communication on the existing
Wim Taymans's avatar
Wim Taymans committed
351 352
 * socket @socket. The @initial_buffer contains zero terminated data already
 * read from @socket which should be used before starting to read new data.
Wim Taymans's avatar
Wim Taymans committed
353 354 355 356
 *
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 */
GstRTSPResult
Sebastian Dröge's avatar
Sebastian Dröge committed
357 358
gst_rtsp_connection_create_from_socket (GSocket * socket, const gchar * ip,
    guint16 port, const gchar * initial_buffer, GstRTSPConnection ** conn)
Wim Taymans's avatar
Wim Taymans committed
359
{
360
  GstRTSPConnection *newconn = NULL;
Wim Taymans's avatar
Wim Taymans committed
361
  GstRTSPUrl *url;
362
  GstRTSPResult res;
363
  GError *err = NULL;
364
  gchar *local_ip;
365
  GIOStream *stream;
Wim Taymans's avatar
Wim Taymans committed
366

Sebastian Dröge's avatar
Sebastian Dröge committed
367
  g_return_val_if_fail (G_IS_SOCKET (socket), GST_RTSP_EINVAL);
368 369
  g_return_val_if_fail (ip != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
370

371
  if (!collect_addresses (socket, &local_ip, NULL, FALSE, &err))
372 373
    goto getnameinfo_failed;

Wim Taymans's avatar
Wim Taymans committed
374 375
  /* create a url for the client address */
  url = g_new0 (GstRTSPUrl, 1);
376
  url->host = g_strdup (ip);
377
  url->port = port;
Wim Taymans's avatar
Wim Taymans committed
378 379

  /* now create the connection object */
380
  GST_RTSP_CHECK (gst_rtsp_connection_create (url, &newconn), newconn_failed);
381 382
  gst_rtsp_url_free (url);

383
  stream = G_IO_STREAM (g_socket_connection_factory_create_connection (socket));
384

385
  /* both read and write initially */
386
  newconn->server = TRUE;
387 388
  newconn->socket0 = socket;
  newconn->stream0 = stream;
Sebastian Dröge's avatar
Sebastian Dröge committed
389
  newconn->write_socket = newconn->read_socket = newconn->socket0;
390 391
  newconn->input_stream = g_io_stream_get_input_stream (stream);
  newconn->output_stream = g_io_stream_get_output_stream (stream);
392
  newconn->control_stream = NULL;
393 394
  newconn->remote_ip = g_strdup (ip);
  newconn->local_ip = local_ip;
395 396
  newconn->initial_buffer = g_strdup (initial_buffer);

Wim Taymans's avatar
Wim Taymans committed
397 398 399 400
  *conn = newconn;

  return GST_RTSP_OK;

401
  /* ERRORS */
402 403 404 405 406 407
getnameinfo_failed:
  {
    GST_ERROR ("failed to get local address: %s", err->message);
    g_clear_error (&err);
    return GST_RTSP_ERROR;
  }
408 409
newconn_failed:
  {
410
    GST_ERROR ("failed to make connection");
411
    g_free (local_ip);
412 413 414 415 416 417 418
    gst_rtsp_url_free (url);
    return res;
  }
}

/**
 * gst_rtsp_connection_accept:
Sebastian Dröge's avatar
Sebastian Dröge committed
419
 * @socket: a socket
420
 * @conn: (out) (transfer full): storage for a #GstRTSPConnection
Sebastian Dröge's avatar
Sebastian Dröge committed
421
 * @cancellable: a #GCancellable to cancel the operation
422
 *
Sebastian Dröge's avatar
Sebastian Dröge committed
423
 * Accept a new connection on @socket and create a new #GstRTSPConnection for
424 425 426 427 428
 * handling communication on new socket.
 *
 * Returns: #GST_RTSP_OK when @conn contains a valid connection.
 */
GstRTSPResult
Sebastian Dröge's avatar
Sebastian Dröge committed
429 430
gst_rtsp_connection_accept (GSocket * socket, GstRTSPConnection ** conn,
    GCancellable * cancellable)
431
{
Sebastian Dröge's avatar
Sebastian Dröge committed
432 433
  GError *err = NULL;
  gchar *ip;
434
  guint16 port;
Sebastian Dröge's avatar
Sebastian Dröge committed
435 436
  GSocket *client_sock;
  GstRTSPResult ret;
437

Sebastian Dröge's avatar
Sebastian Dröge committed
438
  g_return_val_if_fail (G_IS_SOCKET (socket), GST_RTSP_EINVAL);
439 440
  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);

Sebastian Dröge's avatar
Sebastian Dröge committed
441 442
  client_sock = g_socket_accept (socket, cancellable, &err);
  if (!client_sock)
443 444
    goto accept_failed;

445
  /* get the remote ip address and port */
446 447
  if (!collect_addresses (client_sock, &ip, &port, TRUE, &err))
    goto getnameinfo_failed;
448

Sebastian Dröge's avatar
Sebastian Dröge committed
449 450 451
  ret =
      gst_rtsp_connection_create_from_socket (client_sock, ip, port, NULL,
      conn);
452
  g_object_unref (client_sock);
Sebastian Dröge's avatar
Sebastian Dröge committed
453 454 455
  g_free (ip);

  return ret;
456

Wim Taymans's avatar
Wim Taymans committed
457 458 459
  /* ERRORS */
accept_failed:
  {
Sebastian Dröge's avatar
Sebastian Dröge committed
460 461
    GST_DEBUG ("Accepting client failed: %s", err->message);
    g_clear_error (&err);
Wim Taymans's avatar
Wim Taymans committed
462 463
    return GST_RTSP_ESYS;
  }
464 465
getnameinfo_failed:
  {
466 467
    GST_DEBUG ("getnameinfo failed: %s", err->message);
    g_clear_error (&err);
Sebastian Dröge's avatar
Sebastian Dröge committed
468 469 470 471 472
    if (!g_socket_close (client_sock, &err)) {
      GST_DEBUG ("Closing socket failed: %s", err->message);
      g_clear_error (&err);
    }
    g_object_unref (client_sock);
473 474
    return GST_RTSP_ERROR;
  }
Wim Taymans's avatar
Wim Taymans committed
475 476
}

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
/**
 * gst_rtsp_connection_get_tls:
 * @conn: a #GstRTSPConnection
 * @error: #GError for error reporting, or NULL to ignore.
 *
 * Get the TLS connection of @conn.
 *
 * For client side this will return the #GTlsClientConnection when connected
 * over TLS.
 *
 * For server side connections, this function will create a GTlsServerConnection
 * when called the first time and will return that same connection on subsequent
 * calls. The server is then responsible for configuring the TLS connection.
 *
 * Returns: (transfer none): the TLS connection for @conn.
 *
 * Since: 1.2
 */
GTlsConnection *
gst_rtsp_connection_get_tls (GstRTSPConnection * conn, GError ** error)
{
  GTlsConnection *result;

  if (G_IS_TLS_CONNECTION (conn->stream0)) {
    /* we already had one, return it */
    result = G_TLS_CONNECTION (conn->stream0);
  } else if (conn->server) {
    /* no TLS connection but we are server, make one */
    result = (GTlsConnection *)
        g_tls_server_connection_new (conn->stream0, NULL, error);
    if (result) {
      g_object_unref (conn->stream0);
      conn->stream0 = G_IO_STREAM (result);
      conn->input_stream = g_io_stream_get_input_stream (conn->stream0);
      conn->output_stream = g_io_stream_get_output_stream (conn->stream0);
    }
  } else {
    /* client */
    result = NULL;
    g_set_error (error, GST_LIBRARY_ERROR, GST_LIBRARY_ERROR_FAILED,
        "client not connected with TLS");
  }
  return result;
}

522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
/**
 * gst_rtsp_connection_set_tls_validation_flags:
 * @conn: a #GstRTSPConnection
 * @flags: the validation flags.
 *
 * Sets the TLS validation flags to be used to verify the peer
 * certificate when a TLS connection is established.
 *
 * Returns: TRUE if the validation flags are set correctly, or FALSE if
 * @conn is NULL or is not a TLS connection.
 *
 * Since: 1.2.1
 */
gboolean
gst_rtsp_connection_set_tls_validation_flags (GstRTSPConnection * conn,
    GTlsCertificateFlags flags)
{
  gboolean res = FALSE;

  g_return_val_if_fail (conn != NULL, FALSE);

  res = g_socket_client_get_tls (conn->client);
  if (res)
    g_socket_client_set_tls_validation_flags (conn->client, flags);

  return res;
}

/**
 * gst_rtsp_connection_get_tls_validation_flags:
 * @conn: a #GstRTSPConnection
 *
 * Gets the TLS validation flags used to verify the peer certificate
 * when a TLS connection is established.
 *
 * Returns: the validationg flags.
 *
 * Since: 1.2.1
 */
GTlsCertificateFlags
gst_rtsp_connection_get_tls_validation_flags (GstRTSPConnection * conn)
{
  g_return_val_if_fail (conn != NULL, 0);

  return g_socket_client_get_tls_validation_flags (conn->client);
}

569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
/**
 * gst_rtsp_connection_set_tls_database:
 * @conn: a #GstRTSPConnection
 * @database: a #GTlsDatabase
 *
 * Sets the anchor certificate authorities database. This certificate
 * database will be used to verify the server's certificate in case it
 * can't be verified with the default certificate database first.
 *
 * Since: 1.4
 */
void
gst_rtsp_connection_set_tls_database (GstRTSPConnection * conn,
    GTlsDatabase * database)
{
  GTlsDatabase *old_db;

  g_return_if_fail (conn != NULL);

  if (database)
    g_object_ref (database);

  old_db = conn->tls_database;
  conn->tls_database = database;

  if (old_db)
    g_object_unref (old_db);
}

/**
 * gst_rtsp_connection_get_tls_database:
 * @conn: a #GstRTSPConnection
 *
 * Gets the anchor certificate authorities database that will be used
 * after a server certificate can't be verified with the default
 * certificate database.
 *
 * Returns: (transfer full): the anchor certificate authorities database, or NULL if no
 * database has been previously set. Use g_object_unref() to release the
 * certificate database.
 *
 * Since: 1.4
 */
GTlsDatabase *
gst_rtsp_connection_get_tls_database (GstRTSPConnection * conn)
{
  GTlsDatabase *result;

  g_return_val_if_fail (conn != NULL, NULL);

  if ((result = conn->tls_database))
    g_object_ref (result);

  return result;
}

625
static GstRTSPResult
626
setup_tunneling (GstRTSPConnection * conn, GTimeVal * timeout, gchar * uri)
627 628 629
{
  gint i;
  GstRTSPResult res;
630
  gchar *value;
631
  guint16 url_port;
632 633
  GstRTSPMessage *msg;
  GstRTSPMessage response;
Wim Taymans's avatar
Wim Taymans committed
634
  gboolean old_http;
635 636 637 638
  GstRTSPUrl *url;
  GError *error = NULL;
  GSocketConnection *connection;
  GSocket *socket;
639
  gchar *luri = NULL;
640 641 642

  memset (&response, 0, sizeof (response));
  gst_rtsp_message_init (&response);
643

644 645
  url = conn->url;

646
  /* create a random sessionid */
647 648 649 650
  for (i = 0; i < TUNNELID_LEN; i++)
    conn->tunnelid[i] = g_random_int_range ('a', 'z');
  conn->tunnelid[TUNNELID_LEN - 1] = '\0';

651 652 653 654 655 656 657 658 659 660 661
  /* create the GET request for the read connection */
  GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_GET, uri),
      no_message);
  msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;

  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
      conn->tunnelid);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
      "application/x-rtsp-tunnelled");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
662

663 664 665 666 667 668 669 670 671 672 673
  /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
   * request from being base64 encoded */
  conn->tunneled = FALSE;
  GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
  gst_rtsp_message_free (msg);
  conn->tunneled = TRUE;

  /* receive the response to the GET request */
  /* we need to temporarily set manual_http to TRUE since
   * gst_rtsp_connection_receive() will treat the HTTP response as a parsing
   * failure otherwise */
Wim Taymans's avatar
Wim Taymans committed
674
  old_http = conn->manual_http;
675 676 677
  conn->manual_http = TRUE;
  GST_RTSP_CHECK (gst_rtsp_connection_receive (conn, &response, timeout),
      read_failed);
Wim Taymans's avatar
Wim Taymans committed
678
  conn->manual_http = old_http;
679 680 681 682 683 684

  if (response.type != GST_RTSP_MESSAGE_HTTP_RESPONSE ||
      response.type_data.response.code != GST_RTSP_STS_OK)
    goto wrong_result;

  if (gst_rtsp_message_get_header (&response, GST_RTSP_HDR_X_SERVER_IP_ADDRESS,
685
          &value, 0) == GST_RTSP_OK) {
686 687 688 689
    g_free (url->host);
    url->host = g_strdup (value);
    g_free (conn->remote_ip);
    conn->remote_ip = g_strdup (value);
690 691
  }

692
  gst_rtsp_url_get_port (url, &url_port);
693
  luri = g_strdup_printf ("http://%s:%d%s%s%s", url->host, url_port,
694 695
      url->abspath, url->query ? "?" : "", url->query ? url->query : "");

696
  /* connect to the host/port */
697 698 699 700 701
  if (conn->proxy_host) {
    connection = g_socket_client_connect_to_host (conn->client,
        conn->proxy_host, conn->proxy_port, conn->cancellable, &error);
  } else {
    connection = g_socket_client_connect_to_uri (conn->client,
702
        luri, 0, conn->cancellable, &error);
703
  }
704
  if (connection == NULL)
705 706
    goto connect_failed;

707 708 709 710 711 712 713 714 715
  socket = g_socket_connection_get_socket (connection);

  /* get remote address */
  g_free (conn->remote_ip);
  conn->remote_ip = NULL;

  if (!collect_addresses (socket, &conn->remote_ip, NULL, TRUE, &error))
    goto remote_address_failed;

716
  /* this is now our writing socket */
717 718
  conn->stream1 = G_IO_STREAM (connection);
  conn->socket1 = socket;
Sebastian Dröge's avatar
Sebastian Dröge committed
719
  conn->write_socket = conn->socket1;
720
  conn->output_stream = g_io_stream_get_output_stream (conn->stream1);
721
  conn->control_stream = NULL;
722

723
  /* create the POST request for the write connection */
724
  GST_RTSP_CHECK (gst_rtsp_message_new_request (&msg, GST_RTSP_POST, luri),
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
      no_message);
  msg->type = GST_RTSP_MESSAGE_HTTP_REQUEST;

  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_X_SESSIONCOOKIE,
      conn->tunnelid);
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_ACCEPT,
      "application/x-rtsp-tunnelled");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CACHE_CONTROL, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_PRAGMA, "no-cache");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_EXPIRES,
      "Sun, 9 Jan 1972 00:00:00 GMT");
  gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CONTENT_LENGTH, "32767");

  /* we need to temporarily set conn->tunneled to FALSE to prevent the HTTP
   * request from being base64 encoded */
  conn->tunneled = FALSE;
  GST_RTSP_CHECK (gst_rtsp_connection_send (conn, msg, timeout), write_failed);
  gst_rtsp_message_free (msg);
  conn->tunneled = TRUE;
744

745
exit:
746
  gst_rtsp_message_unset (&response);
747
  g_free (luri);
748

749 750 751
  return res;

  /* ERRORS */
752
no_message:
753
  {
754
    GST_ERROR ("failed to create request (%d)", res);
755
    goto exit;
756
  }
757
write_failed:
758
  {
759 760 761
    GST_ERROR ("write failed (%d)", res);
    gst_rtsp_message_free (msg);
    conn->tunneled = TRUE;
762
    goto exit;
763
  }
764
read_failed:
765
  {
766 767
    GST_ERROR ("read failed (%d)", res);
    conn->manual_http = FALSE;
768
    goto exit;
769 770 771
  }
wrong_result:
  {
772 773
    GST_ERROR ("got failure response %d %s", response.type_data.response.code,
        response.type_data.response.reason);
774 775
    res = GST_RTSP_ERROR;
    goto exit;
776
  }
777
connect_failed:
778
  {
779 780 781
    GST_ERROR ("failed to connect: %s", error->message);
    res = GST_RTSP_ERROR;
    g_clear_error (&error);
782
    goto exit;
783
  }
784
remote_address_failed:
785
  {
786 787 788 789
    GST_ERROR ("failed to resolve address: %s", error->message);
    g_object_unref (connection);
    g_clear_error (&error);
    return GST_RTSP_ERROR;
790 791 792 793 794
  }
}

/**
 * gst_rtsp_connection_connect:
795
 * @conn: a #GstRTSPConnection
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
 * @timeout: a #GTimeVal timeout
 *
 * Attempt to connect to the url of @conn made with
 * gst_rtsp_connection_create(). If @timeout is #NULL this function can block
 * forever. If @timeout contains a valid timeout, this function will return
 * #GST_RTSP_ETIMEOUT after the timeout expired.
 *
 * This function can be cancelled with gst_rtsp_connection_flush().
 *
 * Returns: #GST_RTSP_OK when a connection could be made.
 */
GstRTSPResult
gst_rtsp_connection_connect (GstRTSPConnection * conn, GTimeVal * timeout)
{
  GstRTSPResult res;
811 812 813 814 815 816
  GSocketConnection *connection;
  GSocket *socket;
  GError *error = NULL;
  gchar *uri, *remote_ip;
  GstClockTime to;
  guint16 url_port;
817 818 819 820
  GstRTSPUrl *url;

  g_return_val_if_fail (conn != NULL, GST_RTSP_EINVAL);
  g_return_val_if_fail (conn->url != NULL, GST_RTSP_EINVAL);
821
  g_return_val_if_fail (conn->stream0 == NULL, GST_RTSP_EINVAL);
822

823 824 825 826
  to = timeout ? GST_TIMEVAL_TO_TIME (*timeout) : 0;
  g_socket_client_set_timeout (conn->client,
      (to + GST_SECOND - 1) / GST_SECOND);

827 828
  url = conn->url;

829
  gst_rtsp_url_get_port (url, &url_port);
830

831 832 833 834
  if (conn->tunneled) {
    uri = g_strdup_printf ("http://%s:%d%s%s%s", url->host, url_port,
        url->abspath, url->query ? "?" : "", url->query ? url->query : "");
  } else {
835
    uri = gst_rtsp_url_get_request_uri (url);
836
  }
837

838 839 840 841 842 843 844
  if (conn->proxy_host) {
    connection = g_socket_client_connect_to_host (conn->client,
        conn->proxy_host, conn->proxy_port, conn->cancellable, &error);
  } else {
    connection = g_socket_client_connect_to_uri (conn->client,
        uri, url_port, conn->cancellable, &error);
  }
845
  if (connection == NULL)
846
    goto connect_failed;
847

848 849
  /* get remote address */
  socket = g_socket_connection_get_socket (connection);
Wim Taymans's avatar
Wim Taymans committed
850

851 852 853 854 855
  if (!collect_addresses (socket, &remote_ip, NULL, TRUE, &error))
    goto remote_address_failed;

  g_free (conn->remote_ip);
  conn->remote_ip = remote_ip;
856 857
  conn->stream0 = G_IO_STREAM (connection);
  conn->socket0 = socket;
858
  /* this is our read socket */
Sebastian Dröge's avatar
Sebastian Dröge committed
859
  conn->read_socket = conn->socket0;
860
  conn->write_socket = conn->socket0;
861 862
  conn->input_stream = g_io_stream_get_input_stream (conn->stream0);
  conn->output_stream = g_io_stream_get_output_stream (conn->stream0);
863
  conn->control_stream = NULL;
864 865

  if (conn->tunneled) {
866
    res = setup_tunneling (conn, timeout, uri);
867 868 869
    if (res != GST_RTSP_OK)
      goto tunneling_failed;
  }
870
  g_free (uri);
871 872 873

  return GST_RTSP_OK;

874 875
  /* ERRORS */
connect_failed:
876
  {
877 878
    GST_ERROR ("failed to connect: %s", error->message);
    g_clear_error (&error);
879
    g_free (uri);
880
    return GST_RTSP_ERROR;
881
  }
882
remote_address_failed:
883
  {
884 885 886
    GST_ERROR ("failed to connect: %s", error->message);
    g_object_unref (connection);
    g_clear_error (&error);
887
    g_free (uri);
888
    return GST_RTSP_ERROR;
889
  }
890
tunneling_failed:
891
  {
892
    GST_ERROR ("failed to setup tunneling");
893
    g_free (uri);
894
    return res;
895 896 897
  }
}

898 899 900 901
static void
auth_digest_compute_hex_urp (const gchar * username,
    const gchar * realm, const gchar * password, gchar hex_urp[33])
{
902 903 904 905 906 907 908 909 910 911 912 913 914 915
  GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
  const gchar *digest_string;

  g_checksum_update (md5_context, (const guchar *) username, strlen (username));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) realm, strlen (realm));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) password, strlen (password));
  digest_string = g_checksum_get_string (md5_context);

  memset (hex_urp, 0, 33);
  memcpy (hex_urp, digest_string, strlen (digest_string));

  g_checksum_free (md5_context);
916 917 918 919 920 921 922
}

static void
auth_digest_compute_response (const gchar * method,
    const gchar * uri, const gchar * hex_a1, const gchar * nonce,
    gchar response[33])
{
923 924 925
  char hex_a2[33] = { 0, };
  GChecksum *md5_context = g_checksum_new (G_CHECKSUM_MD5);
  const gchar *digest_string;
926 927

  /* compute A2 */
928 929 930 931 932
  g_checksum_update (md5_context, (const guchar *) method, strlen (method));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) uri, strlen (uri));
  digest_string = g_checksum_get_string (md5_context);
  memcpy (hex_a2, digest_string, strlen (digest_string));
933 934

  /* compute KD */
935 936 937 938 939 940 941 942
  g_checksum_reset (md5_context);
  g_checksum_update (md5_context, (const guchar *) hex_a1, strlen (hex_a1));
  g_checksum_update (md5_context, (const guchar *) ":", 1);
  g_checksum_update (md5_context, (const guchar *) nonce, strlen (nonce));
  g_checksum_update (md5_context, (const guchar *) ":", 1);

  g_checksum_update (md5_context, (const guchar *) hex_a2, 32);
  digest_string = g_checksum_get_string (md5_context);
943
  memset (response, 0, 33);
944 945 946
  memcpy (response, digest_string, strlen (digest_string));

  g_checksum_free (md5_context);
947 948
}

949 950 951 952 953
static void
add_auth_header (GstRTSPConnection * conn, GstRTSPMessage * message)
{
  switch (conn->auth_method) {
    case GST_RTSP_AUTH_BASIC:{
Wim Taymans's avatar
Wim Taymans committed
954 955 956 957
      gchar *user_pass;
      gchar *user_pass64;
      gchar *auth_string;

958 959 960
      if (conn->username == NULL || conn->passwd == NULL)
        break;

Wim Taymans's avatar
Wim Taymans committed
961 962 963
      user_pass = g_strdup_printf ("%s:%s", conn->username, conn->passwd);
      user_pass64 = g_base64_encode ((guchar *) user_pass, strlen (user_pass));
      auth_string = g_strdup_printf ("Basic %s", user_pass64);
964

965
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
966 967 968 969 970 971
          auth_string);

      g_free (user_pass);
      g_free (user_pass64);
      break;
    }
972 973 974 975 976 977 978 979 980 981
    case GST_RTSP_AUTH_DIGEST:{
      gchar response[33], hex_urp[33];
      gchar *auth_string, *auth_string2;
      gchar *realm;
      gchar *nonce;
      gchar *opaque;
      const gchar *uri;
      const gchar *method;

      /* we need to have some params set */
982 983
      if (conn->auth_params == NULL || conn->username == NULL ||
          conn->passwd == NULL)
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
        break;

      /* we need the realm and nonce */
      realm = (gchar *) g_hash_table_lookup (conn->auth_params, "realm");
      nonce = (gchar *) g_hash_table_lookup (conn->auth_params, "nonce");
      if (realm == NULL || nonce == NULL)
        break;

      auth_digest_compute_hex_urp (conn->username, realm, conn->passwd,
          hex_urp);

      method = gst_rtsp_method_as_text (message->type_data.request.method);
      uri = message->type_data.request.uri;

      /* Assume no qop, algorithm=md5, stale=false */
      /* For algorithm MD5, a1 = urp. */
      auth_digest_compute_response (method, uri, hex_urp, nonce, response);
      auth_string = g_strdup_printf ("Digest username=\"%s\", "
          "realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
          conn->username, realm, nonce, uri, response);

      opaque = (gchar *) g_hash_table_lookup (conn->auth_params, "opaque");
      if (opaque) {
        auth_string2 = g_strdup_printf ("%s, opaque=\"%s\"", auth_string,
            opaque);
        g_free (auth_string);
        auth_string = auth_string2;
      }
1012
      gst_rtsp_message_take_header (message, GST_RTSP_HDR_AUTHORIZATION,
1013 1014 1015
          auth_string);
      break;
    }
1016 1017 1018 1019 1020 1021 1022
    default:
      /* Nothing to do */
      break;
  }
}

static void
1023
gen_date_string (gchar * date_string, guint len)
1024
{
1025 1026 1027 1028 1029 1030 1031
  static const char wkdays[7][4] =
      { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  static const char months[12][4] =
      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
    "Nov", "Dec"
  };
  struct tm tm;
1032
  time_t t;
1033

1034
  time (&t);
1035 1036

#ifdef HAVE_GMTIME_R
1037
  gmtime_r (&t, &tm);
1038
#else
1039
  tm = *gmtime (&t);
1040
#endif
1041 1042 1043 1044

  g_snprintf (date_string, len, "%s, %02d %s %04d %02d:%02d:%02d GMT",
      wkdays[tm.tm_wday], tm.tm_mday, months[tm.tm_mon], tm.tm_year + 1900,
      tm.tm_hour, tm.tm_min, tm.tm_sec);
1045 1046
}

1047
static GstRTSPResult
1048
write_bytes (GOutputStream * stream, const guint8 * buffer, guint * idx,
1049
    guint size, gboolean block, GCancellable * cancellable)
1050 1051
{
  guint left;
1052 1053
  gssize r;
  GError *err = NULL;
1054

1055
  if (G_UNLIKELY (*idx > size))
1056 1057 1058 1059 1060
    return GST_RTSP_ERROR;

  left = size - *idx;

  while (left) {
1061 1062 1063 1064 1065 1066
    if (block)
      r = g_output_stream_write (stream, (gchar *) & buffer[*idx], left,
          cancellable, &err);
    else
      r = g_pollable_output_stream_write_nonblocking (G_POLLABLE_OUTPUT_STREAM
          (stream), (gchar *) & buffer[*idx], left, cancellable, &err);
1067 1068
    if (G_UNLIKELY (r < 0))
      goto error;
1069

1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
    left -= r;
    *idx += r;
  }
  return GST_RTSP_OK;

  /* ERRORS */
error:
  {
    if (G_UNLIKELY (r == 0))
      return GST_RTSP_EEOF;

    GST_DEBUG ("%s", err->message);
    if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
      g_clear_error (&err);
1084
      return GST_RTSP_EINTR;
1085
    } else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
Sebastian Dröge's avatar
Sebastian Dröge committed
1086
      g_clear_error (&err);
1087 1088 1089 1090
      return GST_RTSP_EINTR;
    } else if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) {
      g_clear_error (&err);
      return GST_RTSP_ETIMEOUT;
1091
    }
1092 1093
    g_clear_error (&err);
    return GST_RTSP_ESYS;
1094 1095 1096
  }
}

1097
static gint
Sebastian Dröge's avatar
Sebastian Dröge committed
1098
fill_raw_bytes (GstRTSPConnection * conn, guint8 * buffer, guint size,
1099
    gboolean block, GError ** err)
1100 1101 1102
{
  gint out = 0;

1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
  if (G_UNLIKELY (conn->initial_buffer != NULL)) {
    gsize left = strlen (&conn->initial_buffer[conn->initial_buffer_offset]);

    out = MIN (left, size);
    memcpy (buffer, &conn->initial_buffer[conn->initial_buffer_offset], out);

    if (left == (gsize) out) {
      g_free (conn->initial_buffer);
      conn->initial_buffer = NULL;
      conn->initial_buffer_offset = 0;
    } else
      conn->initial_buffer_offset += out;
  }

  if (G_LIKELY (size > (guint) out)) {
Sebastian Dröge's avatar
Sebastian Dröge committed
1118
    gssize r;
1119 1120 1121
    gsize count = size - out;
    if (block)
      r = g_input_stream_read (conn->input_stream, (gchar *) & buffer[out],
1122
          count, conn->may_cancel ? conn->cancellable : NULL, err);
1123 1124 1125
    else
      r = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM
          (conn->input_stream), (gchar *) & buffer[out], count,