From 66df66daa21fe69c698a4a46173a3ac011bd65a1 Mon Sep 17 00:00:00 2001 From: Jan Schmidt Date: Fri, 23 Feb 2007 18:12:27 +0000 Subject: [PATCH] gst/rtsp/: Implement simple Basic Authentication support so that urls like rtsp://user:pass@hostname/rtspstream work ... Original commit message from CVS: * gst/rtsp/gstrtspsrc.c: (gst_rtspsrc_finalize), (gst_rtspsrc_create_stream), (rtsp_auth_method_to_string), (gst_rtspsrc_parse_auth_hdr), (gst_rtspsrc_setup_auth), (gst_rtspsrc_send), (gst_rtspsrc_try_send), (gst_rtspsrc_open), (gst_rtspsrc_close), (gst_rtspsrc_play), (gst_rtspsrc_pause), (gst_rtspsrc_uri_set_uri): * gst/rtsp/gstrtspsrc.h: * gst/rtsp/rtspconnection.c: (rtsp_connection_create), (append_auth_header), (rtsp_connection_send), (rtsp_connection_free), (rtsp_connection_set_auth): * gst/rtsp/rtspconnection.h: * gst/rtsp/rtspdefs.h: * gst/rtsp/rtspurl.c: (rtsp_url_get_request_uri): * gst/rtsp/rtspurl.h: Implement simple Basic Authentication support so that urls like rtsp://user:pass@hostname/rtspstream work on hosts that require authentication. --- ChangeLog | 21 ++++ gst/rtsp/gstrtspsrc.c | 258 ++++++++++++++++++++++++++++++++------ gst/rtsp/gstrtspsrc.h | 2 + gst/rtsp/rtspconnection.c | 62 ++++++++- gst/rtsp/rtspconnection.h | 9 ++ gst/rtsp/rtspdefs.h | 10 ++ gst/rtsp/rtspurl.c | 17 +++ gst/rtsp/rtspurl.h | 1 + 8 files changed, 343 insertions(+), 37 deletions(-) diff --git a/ChangeLog b/ChangeLog index 75620919cf..88458e6242 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2007-02-23 Jan Schmidt + + * gst/rtsp/gstrtspsrc.c: (gst_rtspsrc_finalize), + (gst_rtspsrc_create_stream), (rtsp_auth_method_to_string), + (gst_rtspsrc_parse_auth_hdr), (gst_rtspsrc_setup_auth), + (gst_rtspsrc_send), (gst_rtspsrc_try_send), (gst_rtspsrc_open), + (gst_rtspsrc_close), (gst_rtspsrc_play), (gst_rtspsrc_pause), + (gst_rtspsrc_uri_set_uri): + * gst/rtsp/gstrtspsrc.h: + * gst/rtsp/rtspconnection.c: (rtsp_connection_create), + (append_auth_header), (rtsp_connection_send), + (rtsp_connection_free), (rtsp_connection_set_auth): + * gst/rtsp/rtspconnection.h: + * gst/rtsp/rtspdefs.h: + * gst/rtsp/rtspurl.c: (rtsp_url_get_request_uri): + * gst/rtsp/rtspurl.h: + + Implement simple Basic Authentication support so that urls like + rtsp://user:pass@hostname/rtspstream work on hosts that require + authentication. + 2007-02-22 Edgard Lima * sys/v4l2/gstv4l2object.c: diff --git a/gst/rtsp/gstrtspsrc.c b/gst/rtsp/gstrtspsrc.c index ffbf005e5a..eb85370de5 100644 --- a/gst/rtsp/gstrtspsrc.c +++ b/gst/rtsp/gstrtspsrc.c @@ -177,6 +177,12 @@ static GstStateChangeReturn gst_rtspsrc_change_state (GstElement * element, GstStateChange transition); static void gst_rtspsrc_handle_message (GstBin * bin, GstMessage * message); +static gboolean gst_rtspsrc_setup_auth (GstRTSPSrc * src, + RTSPMessage * response); +static gboolean gst_rtspsrc_try_send (GstRTSPSrc * src, RTSPMessage * request, + RTSPMessage * response, RTSPStatusCode * code); + + static gboolean gst_rtspsrc_open (GstRTSPSrc * src); static gboolean gst_rtspsrc_play (GstRTSPSrc * src); static gboolean gst_rtspsrc_pause (GstRTSPSrc * src); @@ -301,6 +307,7 @@ gst_rtspsrc_finalize (GObject * object) g_free (rtspsrc->stream_rec_lock); g_cond_free (rtspsrc->loop_cond); g_free (rtspsrc->location); + g_free (rtspsrc->req_location); g_free (rtspsrc->content_base); rtsp_url_free (rtspsrc->url); @@ -439,7 +446,8 @@ gst_rtspsrc_create_stream (GstRTSPSrc * src, SDPMessage * sdp, gint idx) stream->setup_url = g_strdup_printf ("%s%s", src->content_base, control_url); else - stream->setup_url = g_strdup_printf ("%s/%s", src->location, control_url); + stream->setup_url = + g_strdup_printf ("%s/%s", src->req_location, control_url); } GST_DEBUG_OBJECT (src, " setup: %s", GST_STR_NULL (stream->setup_url)); @@ -1533,6 +1541,145 @@ send_error: } } +#ifndef GST_DISABLE_GST_DEBUG +const gchar * +rtsp_auth_method_to_string (RTSPAuthMethod method) +{ + gint index = 0; + + while (method != 0) { + index++; + method >>= 1; + } + switch (index) { + case 0: + return "None"; + case 1: + return "Basic"; + case 2: + return "Digest"; + } + + return "Unknown"; +} +#endif + +/* Parse a WWW-Authenticate Response header and determine the + * available authentication methods + * FIXME: To implement digest or other auth types, we should extract + * the authentication tokens that the server provided for each method + * into an array of structures and give those to the connection object. + * + * This code should also cope with the fact that each WWW-Authenticate + * header can contain multiple challenge methods + tokens + * + * At the moment, we just do a minimal check for Basic auth and don't + * even parse out the realm */ +static void +gst_rtspsrc_parse_auth_hdr (gchar * hdr, RTSPAuthMethod * methods) +{ + gchar *start; + + g_return_if_fail (hdr != NULL); + g_return_if_fail (methods != NULL); + + /* Skip whitespace at the start of the string */ + for (start = hdr; start[0] != '\0' && g_ascii_isspace (start[0]); start++); + + if (g_ascii_strncasecmp (start, "basic", 5) == 0) + *methods |= RTSP_AUTH_BASIC; +} + +/** + * gst_rtspsrc_setup_auth: + * @src: the rtsp source + * + * Configure a username and password and auth method on the + * connection object based on a response we received from the + * peer. + * + * Currently, this requires that a username and password were supplied + * in the uri. In the future, they may be requested on demand by sending + * a message up the bus. + * + * Returns: TRUE if authentication information could be set up correctly. + */ +static gboolean +gst_rtspsrc_setup_auth (GstRTSPSrc * src, RTSPMessage * response) +{ + gchar *user = NULL; + gchar *pass = NULL; + RTSPAuthMethod avail_methods = RTSP_AUTH_NONE; + RTSPAuthMethod method; + RTSPResult auth_result; + gchar *hdr; + + /* Identify the available auth methods and see if any are supported */ + if (rtsp_message_get_header (response, RTSP_HDR_WWW_AUTHENTICATE, &hdr) == + RTSP_OK) { + gst_rtspsrc_parse_auth_hdr (hdr, &avail_methods); + } + + if (avail_methods == RTSP_AUTH_NONE) + goto no_auth_available; + + /* FIXME: For digest auth, if the response indicates that the session + * data are stale, we just update them in the connection object and + * return TRUE to retry the request */ + + /* Do we have username and password available? */ + if (src->url != NULL && !src->tried_url_auth) { + user = src->url->user; + pass = src->url->passwd; + src->tried_url_auth = TRUE; + GST_DEBUG_OBJECT (src, + "Attempting authentication using credentials from the URL"); + } + + /* FIXME: If the url didn't contain username and password or we tried them + * already, request a username and passwd from the application via some kind + * of credentials request message */ + + /* If we don't have a username and passwd at this point, bail out. */ + if (user == NULL || pass == NULL) + goto no_user_pass; + + /* Try to configure for each available authentication method, strongest to + * weakest */ + for (method = RTSP_AUTH_MAX; method != RTSP_AUTH_NONE; method >>= 1) { + /* Check if this method is available on the server */ + if ((method & avail_methods) == 0) + continue; + + /* Pass the credentials to the connection to try on the next request */ + auth_result = + rtsp_connection_set_auth (src->connection, method, user, pass); + /* INVAL indicates an invalid username/passwd were supplied, so we'll just + * ignore it and end up retrying later */ + if (auth_result == RTSP_OK || auth_result == RTSP_EINVAL) { + GST_DEBUG_OBJECT (src, "Attempting %s authentication", + rtsp_auth_method_to_string (method)); + break; + } + } + + if (method == RTSP_AUTH_NONE) + goto no_auth_available; + + return TRUE; + +no_auth_available: + /* Output an error indicating that we couldn't connect because there were + * no supported authentication protocols */ + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (NULL), + ("No supported authentication protocol was found")); + return FALSE; +no_user_pass: + /* We don't fire an error message, we just return FALSE and let the + * normal NOT_AUTHORIZED error be propagated */ + return FALSE; +} + /** * gst_rtspsrc_send: * @src: the rtsp source @@ -1548,11 +1695,64 @@ send_error: * If @code is NULL, this function will return FALSE (with an invalid @response * message) if the response code was not 200 (OK). * + * If the attempt results in an authentication failure, then this will attempt + * to retrieve authentication credentials via gst_rtspsrc_setup_auth and retry + * the request. + * * Returns: TRUE if the processing was successful. */ gboolean gst_rtspsrc_send (GstRTSPSrc * src, RTSPMessage * request, RTSPMessage * response, RTSPStatusCode * code) +{ + RTSPStatusCode int_code = RTSP_STS_OK; + gboolean res; + gboolean retry; + + do { + retry = FALSE; + res = gst_rtspsrc_try_send (src, request, response, &int_code); + + if (int_code == RTSP_STS_UNAUTHORIZED) { + if (gst_rtspsrc_setup_auth (src, response)) { + /* Try the request/response again after configuring the auth info + * and loop again */ + retry = TRUE; + } + } + } while (retry == TRUE); + + /* If the user requested the code, let them handle errors, otherwise + * post an error below */ + if (code != NULL) + *code = int_code; + else if (int_code != RTSP_STS_OK) + goto error_response; + + return res; + +error_response: + { + switch (response->type_data.response.code) { + case RTSP_STS_NOT_FOUND: + GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", + response->type_data.response.reason)); + break; + default: + GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), + ("Got error response: %d (%s).", response->type_data.response.code, + response->type_data.response.reason)); + break; + } + /* we return FALSE so we should unset the response ourselves */ + rtsp_message_unset (response); + return FALSE; + } +} + +static gboolean +gst_rtspsrc_try_send (GstRTSPSrc * src, RTSPMessage * request, + RTSPMessage * response, RTSPStatusCode * code) { RTSPResult res; RTSPStatusCode thecode; @@ -1590,12 +1790,14 @@ next: } thecode = response->type_data.response.code; - /* if the caller wanted the result code, we store it. Else we check if it's - * OK. */ + + /* if the caller wanted the result code, we store it. */ if (code) *code = thecode; - else if (thecode != RTSP_STS_OK) - goto error_response; + + /* If the request didn't succeed, bail out before doing any more */ + if (thecode != RTSP_STS_OK) + return FALSE; /* store new content base if any */ rtsp_message_get_header (response, RTSP_HDR_CONTENT_BASE, &content_base); @@ -1631,23 +1833,6 @@ handle_request_failed: /* ERROR was posted */ return FALSE; } -error_response: - { - switch (response->type_data.response.code) { - case RTSP_STS_NOT_FOUND: - GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), ("%s", - response->type_data.response.reason)); - break; - default: - GST_ELEMENT_ERROR (src, RESOURCE, READ, (NULL), - ("Got error response: %d (%s).", response->type_data.response.code, - response->type_data.response.reason)); - break; - } - /* we return FALSE so we should unset the response ourselves */ - rtsp_message_unset (response); - return FALSE; - } } /* parse the response and collect all the supported methods. We need this @@ -2058,20 +2243,21 @@ gst_rtspsrc_open (GstRTSPSrc * src) /* can't continue without a valid url */ if (G_UNLIKELY (src->url == NULL)) goto no_url; + src->tried_url_auth = FALSE; /* create connection */ - GST_DEBUG_OBJECT (src, "creating connection (%s)...", src->location); + GST_DEBUG_OBJECT (src, "creating connection (%s)...", src->req_location); if ((res = rtsp_connection_create (src->url, &src->connection)) < 0) goto could_not_create; /* connect */ - GST_DEBUG_OBJECT (src, "connecting (%s)...", src->location); + GST_DEBUG_OBJECT (src, "connecting (%s)...", src->req_location); if ((res = rtsp_connection_connect (src->connection)) < 0) goto could_not_connect; /* create OPTIONS */ GST_DEBUG_OBJECT (src, "create options..."); - res = rtsp_message_init_request (&request, RTSP_OPTIONS, src->location); + res = rtsp_message_init_request (&request, RTSP_OPTIONS, src->req_location); if (res < 0) goto create_request_failed; @@ -2086,7 +2272,7 @@ gst_rtspsrc_open (GstRTSPSrc * src) /* create DESCRIBE */ GST_DEBUG_OBJECT (src, "create describe..."); - res = rtsp_message_init_request (&request, RTSP_DESCRIBE, src->location); + res = rtsp_message_init_request (&request, RTSP_DESCRIBE, src->req_location); if (res < 0) goto create_request_failed; @@ -2177,11 +2363,8 @@ create_request_failed: } send_error: { - gchar *str = rtsp_strresult (res); - - GST_ELEMENT_ERROR (src, RESOURCE, WRITE, (NULL), - ("Could not send message. (%s)", str)); - g_free (str); + /* Don't post a message - the rtsp_send method will have + * taken care of it because we passed NULL for the response code */ goto cleanup_error; } methods_error: @@ -2232,7 +2415,8 @@ gst_rtspsrc_close (GstRTSPSrc * src) if (src->methods & RTSP_PLAY) { /* do TEARDOWN */ - res = rtsp_message_init_request (&request, RTSP_TEARDOWN, src->location); + res = + rtsp_message_init_request (&request, RTSP_TEARDOWN, src->req_location); if (res < 0) goto create_request_failed; @@ -2317,7 +2501,7 @@ gst_rtspsrc_play (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "PLAY..."); /* do play */ - res = rtsp_message_init_request (&request, RTSP_PLAY, src->location); + res = rtsp_message_init_request (&request, RTSP_PLAY, src->req_location); if (res < 0) goto create_request_failed; @@ -2378,7 +2562,7 @@ gst_rtspsrc_pause (GstRTSPSrc * src) GST_DEBUG_OBJECT (src, "PAUSE..."); /* do pause */ - res = rtsp_message_init_request (&request, RTSP_PAUSE, src->location); + res = rtsp_message_init_request (&request, RTSP_PAUSE, src->req_location); if (res < 0) goto create_request_failed; @@ -2541,11 +2725,13 @@ gst_rtspsrc_uri_set_uri (GstURIHandler * handler, const gchar * uri) rtsp_url_free (src->url); src->url = newurl; g_free (src->location); + g_free (src->req_location); src->location = g_strdup (uri); - if (!g_str_has_prefix (src->location, "rtsp://")) - memmove (src->location + 4, src->location + 5, strlen (src->location) - 4); + src->req_location = rtsp_url_get_request_uri (src->url); GST_DEBUG_OBJECT (src, "set uri: %s", GST_STR_NULL (uri)); + GST_DEBUG_OBJECT (src, "request uri is: %s", + GST_STR_NULL (src->req_location)); return TRUE; diff --git a/gst/rtsp/gstrtspsrc.h b/gst/rtsp/gstrtspsrc.h index 8f42895fa0..e491d50a3b 100644 --- a/gst/rtsp/gstrtspsrc.h +++ b/gst/rtsp/gstrtspsrc.h @@ -130,6 +130,7 @@ struct _GstRTSPSrc { /* properties */ gchar *location; + gchar *req_location; /* Sanitised URL to use in network requests */ RTSPUrl *url; RTSPLowerTrans protocols; gboolean debug; @@ -139,6 +140,7 @@ struct _GstRTSPSrc { /* state */ gchar *content_base; RTSPLowerTrans cur_protocols; + gboolean tried_url_auth; /* supported methods */ gint methods; diff --git a/gst/rtsp/rtspconnection.c b/gst/rtsp/rtspconnection.c index 1fb26bf545..19f5c6df5a 100644 --- a/gst/rtsp/rtspconnection.c +++ b/gst/rtsp/rtspconnection.c @@ -143,6 +143,10 @@ rtsp_connection_create (RTSPUrl * url, RTSPConnection ** conn) newconn->session_id[0] = 0; newconn->state = RTSP_STATE_INIT; + newconn->auth_method = RTSP_AUTH_NONE; + newconn->username = NULL; + newconn->passwd = NULL; + *conn = newconn; return RTSP_OK; @@ -231,6 +235,30 @@ append_header (gint key, gchar * value, GString * str) g_string_append_printf (str, "%s: %s\r\n", keystr, value); } +static void +append_auth_header (RTSPConnection * conn, RTSPMessage * message, GString * str) +{ + switch (conn->auth_method) { + case RTSP_AUTH_BASIC:{ + gchar *user_pass = + g_strdup_printf ("%s:%s", conn->username, conn->passwd); + gchar *user_pass64 = + g_base64_encode ((guchar *) user_pass, strlen (user_pass)); + gchar *auth_string = g_strdup_printf ("Basic %s", user_pass64); + + append_header (RTSP_HDR_AUTHORIZATION, auth_string, str); + + g_free (user_pass); + g_free (user_pass64); + g_free (auth_string); + break; + } + default: + /* Nothing to do */ + break; + } +} + RTSPResult rtsp_connection_send (RTSPConnection * conn, RTSPMessage * message) { @@ -278,12 +306,15 @@ rtsp_connection_send (RTSPConnection * conn, RTSPMessage * message) /* append session id if we have one */ if (conn->session_id[0] != '\0') { - rtsp_message_add_header (message, RTSP_HDR_SESSION, conn->session_id); + append_header (RTSP_HDR_SESSION, conn->session_id, str); } /* append headers */ g_hash_table_foreach (message->hdr_fields, (GHFunc) append_header, str); + /* Append any authentication headers */ + append_auth_header (conn, message, str); + /* append Content-Length and body if needed */ if (message->body != NULL && message->body_size > 0) { gchar *len; @@ -786,6 +817,9 @@ rtsp_connection_free (RTSPConnection * conn) WSACleanup (); #endif + g_free (conn->username); + g_free (conn->passwd); + g_free (conn); return RTSP_OK; @@ -812,3 +846,29 @@ rtsp_connection_flush (RTSPConnection * conn, gboolean flush) } return RTSP_OK; } + +RTSPResult +rtsp_connection_set_auth (RTSPConnection * conn, RTSPAuthMethod method, + gchar * user, gchar * pass) +{ + /* Digest isn't implemented yet */ + if (method == RTSP_AUTH_DIGEST) + return RTSP_ENOTIMPL; + + /* Make sure the username and passwd are being set for authentication */ + if (method == RTSP_AUTH_NONE && (user == NULL || pass == NULL)) + return RTSP_EINVAL; + + /* ":" chars are not allowed in usernames for basic auth */ + if (method == RTSP_AUTH_BASIC && g_strrstr (user, ":") != NULL) + return RTSP_EINVAL; + + g_free (conn->username); + g_free (conn->passwd); + + conn->auth_method = method; + conn->username = g_strdup (user); + conn->passwd = g_strdup (pass); + + return RTSP_OK; +} diff --git a/gst/rtsp/rtspconnection.h b/gst/rtsp/rtspconnection.h index 36532fd0f2..2076950b89 100644 --- a/gst/rtsp/rtspconnection.h +++ b/gst/rtsp/rtspconnection.h @@ -64,6 +64,11 @@ typedef struct _RTSPConnection RTSPState state; gint cseq; /* sequence number */ gchar session_id[512]; /* session id */ + + /* Authentication */ + RTSPAuthMethod auth_method; + gchar *username; + gchar *passwd; } RTSPConnection; /* opening/closing a connection */ @@ -78,6 +83,10 @@ RTSPResult rtsp_connection_receive (RTSPConnection *conn, RTSPMessage *mess RTSPResult rtsp_connection_flush (RTSPConnection *conn, gboolean flush); +/* Configure Authentication data */ +RTSPResult rtsp_connection_set_auth (RTSPConnection *conn, + RTSPAuthMethod method, gchar *user, gchar *pass); + G_END_DECLS #endif /* __RTSP_CONNECTION_H__ */ diff --git a/gst/rtsp/rtspdefs.h b/gst/rtsp/rtspdefs.h index 434361884d..f72a9a6546 100644 --- a/gst/rtsp/rtspdefs.h +++ b/gst/rtsp/rtspdefs.h @@ -99,6 +99,16 @@ typedef enum { RTSP_TEARDOWN = (1 << 10), } RTSPMethod; +/* Authentication methods, ordered by strength */ +typedef enum { + RTSP_AUTH_NONE = 0x00, + RTSP_AUTH_BASIC = 0x01, + RTSP_AUTH_DIGEST = 0x02 +} RTSPAuthMethod; + +/* Strongest available authentication method */ +#define RTSP_AUTH_MAX RTSP_AUTH_DIGEST + typedef enum { RTSP_HDR_INVALID, diff --git a/gst/rtsp/rtspurl.c b/gst/rtsp/rtspurl.c index 6ef0220e1e..cc2047c334 100644 --- a/gst/rtsp/rtspurl.c +++ b/gst/rtsp/rtspurl.c @@ -177,3 +177,20 @@ rtsp_url_get_port (RTSPUrl * url, guint16 * port) return RTSP_OK; } + +gchar * +rtsp_url_get_request_uri (RTSPUrl * url) +{ + gchar *uri; + + g_return_val_if_fail (url != NULL, NULL); + + if (url->port != 0) { + uri = g_strdup_printf ("rtsp://%s:%u/%s", url->host, url->port, + url->abspath); + } else { + uri = g_strdup_printf ("rtsp://%s/%s", url->host, url->abspath); + } + + return uri; +} diff --git a/gst/rtsp/rtspurl.h b/gst/rtsp/rtspurl.h index 3c39e92459..ce144f465d 100644 --- a/gst/rtsp/rtspurl.h +++ b/gst/rtsp/rtspurl.h @@ -64,6 +64,7 @@ typedef struct _RTSPUrl { RTSPResult rtsp_url_parse (const gchar *urlstr, RTSPUrl **url); void rtsp_url_free (RTSPUrl *url); +gchar *rtsp_url_get_request_uri (RTSPUrl *url); RTSPResult rtsp_url_set_port (RTSPUrl *url, guint16 port); RTSPResult rtsp_url_get_port (RTSPUrl *url, guint16 *port);