gstreamer/gst/rtsp-server/rtsp-auth.c
Wim Taymans f18f2619e1 auth: add default authorizations
When no auth module is specified, use our table of defaults to look up the
default value of the check instead of always allowing everything. This was
we can disallow client settings by default.
2013-07-15 16:47:07 +02:00

550 lines
13 KiB
C

/* GStreamer
* Copyright (C) 2010 Wim Taymans <wim.taymans at gmail.com>
*
* 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
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:rtsp-auth
* @short_description: Authentication and authorization
* @see_also: #GstRTSPPermission, #GstRTSPtoken
*
* Last reviewed on 2013-07-11 (1.0.0)
*/
#include <string.h>
#include "rtsp-auth.h"
#define GST_RTSP_AUTH_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_RTSP_AUTH, GstRTSPAuthPrivate))
struct _GstRTSPAuthPrivate
{
GMutex lock;
/* the TLS certificate */
GTlsCertificate *certificate;
GHashTable *basic; /* protected by lock */
GstRTSPMethod methods;
};
enum
{
PROP_0,
PROP_LAST
};
GST_DEBUG_CATEGORY_STATIC (rtsp_auth_debug);
#define GST_CAT_DEFAULT rtsp_auth_debug
static void gst_rtsp_auth_get_property (GObject * object, guint propid,
GValue * value, GParamSpec * pspec);
static void gst_rtsp_auth_set_property (GObject * object, guint propid,
const GValue * value, GParamSpec * pspec);
static void gst_rtsp_auth_finalize (GObject * obj);
static gboolean default_authenticate (GstRTSPAuth * auth,
GstRTSPClientState * state);
static gboolean default_check (GstRTSPAuth * auth, GstRTSPClientState * state,
const gchar * check);
G_DEFINE_TYPE (GstRTSPAuth, gst_rtsp_auth, G_TYPE_OBJECT);
static void
gst_rtsp_auth_class_init (GstRTSPAuthClass * klass)
{
GObjectClass *gobject_class;
g_type_class_add_private (klass, sizeof (GstRTSPAuthPrivate));
gobject_class = G_OBJECT_CLASS (klass);
gobject_class->get_property = gst_rtsp_auth_get_property;
gobject_class->set_property = gst_rtsp_auth_set_property;
gobject_class->finalize = gst_rtsp_auth_finalize;
klass->authenticate = default_authenticate;
klass->check = default_check;
GST_DEBUG_CATEGORY_INIT (rtsp_auth_debug, "rtspauth", 0, "GstRTSPAuth");
}
static void
gst_rtsp_auth_init (GstRTSPAuth * auth)
{
GstRTSPAuthPrivate *priv;
auth->priv = priv = GST_RTSP_AUTH_GET_PRIVATE (auth);
g_mutex_init (&priv->lock);
priv->basic = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) gst_rtsp_token_unref);
/* bitwise or of all methods that need authentication */
priv->methods = 0;
}
static void
gst_rtsp_auth_finalize (GObject * obj)
{
GstRTSPAuth *auth = GST_RTSP_AUTH (obj);
GstRTSPAuthPrivate *priv = auth->priv;
GST_INFO ("finalize auth %p", auth);
if (priv->certificate)
g_object_unref (priv->certificate);
g_hash_table_unref (priv->basic);
g_mutex_clear (&priv->lock);
G_OBJECT_CLASS (gst_rtsp_auth_parent_class)->finalize (obj);
}
static void
gst_rtsp_auth_get_property (GObject * object, guint propid,
GValue * value, GParamSpec * pspec)
{
switch (propid) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
}
static void
gst_rtsp_auth_set_property (GObject * object, guint propid,
const GValue * value, GParamSpec * pspec)
{
switch (propid) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
}
}
/**
* gst_rtsp_auth_new:
*
* Create a new #GstRTSPAuth instance.
*
* Returns: a new #GstRTSPAuth
*/
GstRTSPAuth *
gst_rtsp_auth_new (void)
{
GstRTSPAuth *result;
result = g_object_new (GST_TYPE_RTSP_AUTH, NULL);
return result;
}
/**
* gst_rtsp_auth_set_tls_certificate:
* @auth: a #GstRTSPAuth
* @cert: (allow none): a #GTlsCertificate
*
* Set the TLS certificate for the auth. Client connections will only
* be accepted when TLS is negotiated.
*/
void
gst_rtsp_auth_set_tls_certificate (GstRTSPAuth * auth, GTlsCertificate * cert)
{
GstRTSPAuthPrivate *priv;
GTlsCertificate *old;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
priv = auth->priv;
if (cert)
g_object_ref (cert);
g_mutex_lock (&priv->lock);
old = priv->certificate;
priv->certificate = cert;
g_mutex_unlock (&priv->lock);
if (old)
g_object_unref (old);
}
/**
* gst_rtsp_auth_get_tls_certificate:
* @auth: a #GstRTSPAuth
*
* Get the #GTlsCertificate used for negotiating TLS @auth.
*
* Returns: (transfer full): the #GTlsCertificate of @auth. g_object_unref() after
* usage.
*/
GTlsCertificate *
gst_rtsp_auth_get_tls_certificate (GstRTSPAuth * auth)
{
GstRTSPAuthPrivate *priv;
GTlsCertificate *result;
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);
priv = auth->priv;
g_mutex_lock (&priv->lock);
if ((result = priv->certificate))
g_object_ref (result);
g_mutex_unlock (&priv->lock);
return result;
}
/**
* gst_rtsp_auth_add_basic:
* @auth: a #GstRTSPAuth
* @basic: the basic token
* @token: authorisation token
*
* Add a basic token for the default authentication algorithm that
* enables the client with privileges listed in @token.
*/
void
gst_rtsp_auth_add_basic (GstRTSPAuth * auth, const gchar * basic,
GstRTSPToken * token)
{
GstRTSPAuthPrivate *priv;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
g_return_if_fail (basic != NULL);
g_return_if_fail (GST_IS_RTSP_TOKEN (token));
priv = auth->priv;
g_mutex_lock (&priv->lock);
g_hash_table_replace (priv->basic, g_strdup (basic),
gst_rtsp_token_ref (token));
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_auth_remove_basic:
* @auth: a #GstRTSPAuth
* @basic: (transfer none): the basic token
*
* Add a basic token for the default authentication algorithm that
* enables the client qith privileges from @authgroup.
*/
void
gst_rtsp_auth_remove_basic (GstRTSPAuth * auth, const gchar * basic)
{
GstRTSPAuthPrivate *priv;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
g_return_if_fail (basic != NULL);
priv = auth->priv;
g_mutex_lock (&priv->lock);
g_hash_table_remove (priv->basic, basic);
g_mutex_unlock (&priv->lock);
}
static gboolean
default_authenticate (GstRTSPAuth * auth, GstRTSPClientState * state)
{
GstRTSPAuthPrivate *priv = auth->priv;
GstRTSPResult res;
gchar *authorization;
GST_DEBUG_OBJECT (auth, "authenticate");
res =
gst_rtsp_message_get_header (state->request, GST_RTSP_HDR_AUTHORIZATION,
&authorization, 0);
if (res < 0)
goto no_auth;
/* parse type */
if (g_ascii_strncasecmp (authorization, "basic ", 6) == 0) {
GstRTSPToken *token;
GST_DEBUG_OBJECT (auth, "check Basic auth");
g_mutex_lock (&priv->lock);
if ((token = g_hash_table_lookup (priv->basic, &authorization[6]))) {
GST_DEBUG_OBJECT (auth, "setting token %p", token);
state->token = token;
}
g_mutex_unlock (&priv->lock);
} else if (g_ascii_strncasecmp (authorization, "digest ", 7) == 0) {
GST_DEBUG_OBJECT (auth, "check Digest auth");
/* not implemented yet */
}
return TRUE;
no_auth:
{
GST_DEBUG_OBJECT (auth, "no authorization header found");
return TRUE;
}
}
static void
send_response (GstRTSPAuth * auth, GstRTSPStatusCode code,
GstRTSPClientState * state)
{
gst_rtsp_message_init_response (state->response, code,
gst_rtsp_status_as_text (code), state->request);
if (code == GST_RTSP_STS_UNAUTHORIZED) {
/* we only have Basic for now */
gst_rtsp_message_add_header (state->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
"Basic realm=\"GStreamer RTSP Server\"");
}
gst_rtsp_client_send_message (state->client, state->session, state->response);
}
static gboolean
ensure_authenticated (GstRTSPAuth * auth, GstRTSPClientState * state)
{
GstRTSPAuthClass *klass;
klass = GST_RTSP_AUTH_GET_CLASS (auth);
/* we need a token to check */
if (state->token == NULL) {
if (klass->authenticate) {
if (!klass->authenticate (auth, state))
goto authenticate_failed;
}
}
if (state->token == NULL)
goto no_auth;
return TRUE;
/* ERRORS */
authenticate_failed:
{
GST_DEBUG_OBJECT (auth, "authenticate failed");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, state);
return FALSE;
}
no_auth:
{
GST_DEBUG_OBJECT (auth, "no authorization token found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, state);
return FALSE;
}
}
/* new connection */
static gboolean
check_connect (GstRTSPAuth * auth, GstRTSPClientState * state,
const gchar * check)
{
GstRTSPAuthPrivate *priv = auth->priv;
if (priv->certificate) {
GTlsConnection *tls;
/* configure the connection */
tls = gst_rtsp_connection_get_tls (state->conn, NULL);
g_tls_connection_set_certificate (tls, priv->certificate);
}
return TRUE;
}
/* check url and methods */
static gboolean
check_url (GstRTSPAuth * auth, GstRTSPClientState * state, const gchar * check)
{
GstRTSPAuthPrivate *priv = auth->priv;
if ((state->method & priv->methods) != 0)
if (!ensure_authenticated (auth, state))
goto not_authenticated;
return TRUE;
/* ERRORS */
not_authenticated:
{
return FALSE;
}
}
/* check access to media factory */
static gboolean
check_factory (GstRTSPAuth * auth, GstRTSPClientState * state,
const gchar * check)
{
const gchar *role;
GstRTSPPermissions *perms;
if (!ensure_authenticated (auth, state))
return FALSE;
if (!(role = gst_rtsp_token_get_string (state->token,
GST_RTSP_MEDIA_FACTORY_ROLE)))
goto no_media_role;
if (!(perms = gst_rtsp_media_factory_get_permissions (state->factory)))
goto no_permissions;
if (g_str_equal (check, "auth.check.media.factory.access")) {
if (!gst_rtsp_permissions_is_allowed (perms, role,
GST_RTSP_MEDIA_FACTORY_PERM_ACCESS))
goto no_access;
} else if (g_str_equal (check, "auth.check.media.factory.construct")) {
if (!gst_rtsp_permissions_is_allowed (perms, role,
GST_RTSP_MEDIA_FACTORY_PERM_CONSTRUCT))
goto no_construct;
}
return TRUE;
/* ERRORS */
no_media_role:
{
GST_DEBUG_OBJECT (auth, "no media factory role found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, state);
return FALSE;
}
no_permissions:
{
GST_DEBUG_OBJECT (auth, "no permissions on media factory found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, state);
return FALSE;
}
no_access:
{
GST_DEBUG_OBJECT (auth, "no permissions to access media factory");
send_response (auth, GST_RTSP_STS_NOT_FOUND, state);
return FALSE;
}
no_construct:
{
GST_DEBUG_OBJECT (auth, "no permissions to construct media factory");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, state);
return FALSE;
}
}
static gboolean
check_client_settings (GstRTSPAuth * auth, GstRTSPClientState * state,
const gchar * check)
{
if (!ensure_authenticated (auth, state))
return FALSE;
return gst_rtsp_token_is_allowed (state->token,
GST_RTSP_TRANSPORT_PERM_CLIENT_SETTINGS);
}
static gboolean
default_check (GstRTSPAuth * auth, GstRTSPClientState * state,
const gchar * check)
{
gboolean res = FALSE;
/* FIXME, use hastable or so */
if (g_str_equal (check, GST_RTSP_AUTH_CHECK_CONNECT)) {
res = check_connect (auth, state, check);
} else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_URL)) {
res = check_url (auth, state, check);
} else if (g_str_has_prefix (check, "auth.check.media.factory.")) {
res = check_factory (auth, state, check);
} else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS)) {
res = check_client_settings (auth, state, check);
}
return res;
}
static gboolean
no_auth_check (const gchar * check)
{
gboolean res;
if (g_str_equal (check, GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS))
res = FALSE;
else
res = TRUE;
return res;
}
/**
* gst_rtsp_auth_check:
* @check: the item to check
*
* Check if @check is allowed in the current context.
*
* Returns: FALSE if check failed.
*/
gboolean
gst_rtsp_auth_check (const gchar * check)
{
gboolean result = FALSE;
GstRTSPAuthClass *klass;
GstRTSPClientState *state;
GstRTSPAuth *auth;
g_return_val_if_fail (check != NULL, FALSE);
if (!(state = gst_rtsp_client_state_get_current ()))
goto no_state;
/* no auth, we don't need to check */
if (!(auth = state->auth))
return no_auth_check (check);
klass = GST_RTSP_AUTH_GET_CLASS (auth);
GST_DEBUG_OBJECT (auth, "check authorization '%s'", check);
if (klass->check)
result = klass->check (auth, state, check);
return result;
/* ERRORS */
no_state:
{
GST_ERROR ("no clientstate found");
return FALSE;
}
}
/**
* gst_rtsp_auth_make_basic:
* @user: a userid
* @pass: a password
*
* Construct a Basic authorisation token from @user and @pass.
*
* Returns: the base64 encoding of the string @user:@pass. g_free()
* after usage.
*/
gchar *
gst_rtsp_auth_make_basic (const gchar * user, const gchar * pass)
{
gchar *user_pass;
gchar *result;
g_return_val_if_fail (user != NULL, NULL);
g_return_val_if_fail (pass != NULL, NULL);
user_pass = g_strjoin (":", user, pass, NULL);
result = g_base64_encode ((guchar *) user_pass, strlen (user_pass));
g_free (user_pass);
return result;
}