gstreamer/gst/rtsp-server/rtsp-auth.c

807 lines
20 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: #GstRTSPPermissions, #GstRTSPToken
*
* The #GstRTSPAuth object is responsible for checking if the current user is
* allowed to perform requested actions. The default implementation has some
* reasonable checks but subclasses can implement custom security policies.
*
* A new auth object is made with gst_rtsp_auth_new(). It is usually configured
* on the #GstRTSPServer object.
*
* The RTSP server will call gst_rtsp_auth_check() with a string describing the
* check to perform. The possible checks are prefixed with
* GST_RTSP_AUTH_CHECK_*. Depending on the check, the default implementation
* will use the current #GstRTSPToken, #GstRTSPContext and
* #GstRTSPPermissions on the object to check if an operation is allowed.
*
* The default #GstRTSPAuth object has support for basic authentication. With
* gst_rtsp_auth_add_basic() you can add a basic authentication string together
* with the #GstRTSPToken that will become active when successfully
* authenticated.
*
* When a TLS certificate has been set with gst_rtsp_auth_set_tls_certificate(),
* the default auth object will require the client to connect with a TLS
* connection.
*
* Last reviewed on 2013-07-16 (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;
GTlsDatabase *database;
GTlsAuthenticationMode mode;
GHashTable *basic; /* protected by lock */
GstRTSPToken *default_token;
GstRTSPMethod methods;
};
enum
{
PROP_0,
PROP_LAST
};
enum
{
SIGNAL_ACCEPT_CERTIFICATE,
SIGNAL_LAST
};
static guint signals[SIGNAL_LAST] = { 0 };
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, GstRTSPContext * ctx);
static gboolean default_check (GstRTSPAuth * auth, GstRTSPContext * ctx,
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");
/**
* GstRTSPAuth::accept-certificate:
* @auth: a #GstRTSPAuth
* @connection: a #GTlsConnection
* @peer_cert: the peer's #GTlsCertificate
* @errors: the problems with @peer_cert.
*
* Emitted during the TLS handshake after the client certificate has
* been received. See also gst_rtsp_auth_set_tls_authentication_mode().
*
* Returns: %TRUE to accept @peer_cert (which will also
* immediately end the signal emission). %FALSE to allow the signal
* emission to continue, which will cause the handshake to fail if
* no one else overrides it.
*
* Since: 1.6
*/
signals[SIGNAL_ACCEPT_CERTIFICATE] = g_signal_new ("accept-certificate",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstRTSPAuthClass, accept_certificate),
g_signal_accumulator_true_handled, NULL, g_cclosure_marshal_generic,
G_TYPE_BOOLEAN, 3, G_TYPE_TLS_CONNECTION, G_TYPE_TLS_CERTIFICATE,
G_TYPE_TLS_CERTIFICATE_FLAGS);
}
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);
if (priv->database)
g_object_unref (priv->database);
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: (transfer full): 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: (transfer none) (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_set_tls_database:
* @auth: a #GstRTSPAuth
* @database: (transfer none) (allow-none): a #GTlsDatabase
*
* Sets the certificate database that is used to verify peer certificates.
* If set to %NULL (the default), then peer certificate validation will always
* set the %G_TLS_CERTIFICATE_UNKNOWN_CA error.
*
* Since 1.6
*/
void
gst_rtsp_auth_set_tls_database (GstRTSPAuth * auth, GTlsDatabase * database)
{
GstRTSPAuthPrivate *priv;
GTlsDatabase *old;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
priv = auth->priv;
if (database)
g_object_ref (database);
g_mutex_lock (&priv->lock);
old = priv->database;
priv->database = database;
g_mutex_unlock (&priv->lock);
if (old)
g_object_unref (old);
}
/**
* gst_rtsp_auth_get_tls_database:
* @auth: a #GstRTSPAuth
*
* Get the #GTlsDatabase used for verifying client certificate.
*
* Returns: (transfer full): the #GTlsDatabase of @auth. g_object_unref() after
* usage.
* Since: 1.6
*/
GTlsDatabase *
gst_rtsp_auth_get_tls_database (GstRTSPAuth * auth)
{
GstRTSPAuthPrivate *priv;
GTlsDatabase *result;
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);
priv = auth->priv;
g_mutex_lock (&priv->lock);
if ((result = priv->database))
g_object_ref (result);
g_mutex_unlock (&priv->lock);
return result;
}
/**
* gst_rtsp_auth_set_tls_authentication_mode:
* @auth: a #GstRTSPAuth
* @mode: (transfer none) (allow-none): a #GTlsAuthenticationMode
*
* The #GTlsAuthenticationMode to set on the underlying GTlsServerConnection.
* When set to another value than %G_TLS_AUTHENTICATION_NONE,
* #GstRTSPAuth::accept-certificate signal will be emitted and must be handled.
*
* Since: 1.6
*/
void
gst_rtsp_auth_set_tls_authentication_mode (GstRTSPAuth * auth,
GTlsAuthenticationMode mode)
{
GstRTSPAuthPrivate *priv;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
priv = auth->priv;
g_mutex_lock (&priv->lock);
priv->mode = mode;
g_mutex_unlock (&priv->lock);
}
/**
* gst_rtsp_auth_get_tls_authentication_mode:
* @auth: a #GstRTSPAuth
*
* Get the #GTlsAuthenticationMode.
*
* Returns: (transfer full): the #GTlsAuthenticationMode.
*/
GTlsAuthenticationMode
gst_rtsp_auth_get_tls_authentication_mode (GstRTSPAuth * auth)
{
GstRTSPAuthPrivate *priv;
GTlsAuthenticationMode result;
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), G_TLS_AUTHENTICATION_NONE);
priv = auth->priv;
g_mutex_lock (&priv->lock);
result = priv->mode;
g_mutex_unlock (&priv->lock);
return result;
}
/**
* gst_rtsp_auth_set_default_token:
* @auth: a #GstRTSPAuth
* @token: (transfer none) (allow-none): a #GstRTSPToken
*
* Set the default #GstRTSPToken to @token in @auth. The default token will
* be used for unauthenticated users.
*/
void
gst_rtsp_auth_set_default_token (GstRTSPAuth * auth, GstRTSPToken * token)
{
GstRTSPAuthPrivate *priv;
GstRTSPToken *old;
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
priv = auth->priv;
if (token)
gst_rtsp_token_ref (token);
g_mutex_lock (&priv->lock);
old = priv->default_token;
priv->default_token = token;
g_mutex_unlock (&priv->lock);
if (old)
gst_rtsp_token_unref (old);
}
/**
* gst_rtsp_auth_get_default_token:
* @auth: a #GstRTSPAuth
*
* Get the default token for @auth. This token will be used for unauthenticated
* users.
*
* Returns: (transfer full): the #GstRTSPToken of @auth. gst_rtsp_token_unref() after
* usage.
*/
GstRTSPToken *
gst_rtsp_auth_get_default_token (GstRTSPAuth * auth)
{
GstRTSPAuthPrivate *priv;
GstRTSPToken *result;
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);
priv = auth->priv;
g_mutex_lock (&priv->lock);
if ((result = priv->default_token))
gst_rtsp_token_ref (result);
g_mutex_unlock (&priv->lock);
return result;
}
/**
* gst_rtsp_auth_add_basic:
* @auth: a #GstRTSPAuth
* @basic: the basic token
* @token: (transfer none): 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 with 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, GstRTSPContext * ctx)
{
GstRTSPAuthPrivate *priv = auth->priv;
GstRTSPResult res;
gchar *authorization;
GST_DEBUG_OBJECT (auth, "authenticate");
g_mutex_lock (&priv->lock);
/* FIXME, need to ref but we have no way to unref when the ctx is
* popped */
ctx->token = priv->default_token;
g_mutex_unlock (&priv->lock);
res =
gst_rtsp_message_get_header (ctx->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);
ctx->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, GstRTSPContext * ctx)
{
gst_rtsp_message_init_response (ctx->response, code,
gst_rtsp_status_as_text (code), ctx->request);
if (code == GST_RTSP_STS_UNAUTHORIZED) {
/* we only have Basic for now */
gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
"Basic realm=\"GStreamer RTSP Server\"");
}
gst_rtsp_client_send_message (ctx->client, ctx->session, ctx->response);
}
static gboolean
ensure_authenticated (GstRTSPAuth * auth, GstRTSPContext * ctx)
{
GstRTSPAuthClass *klass;
klass = GST_RTSP_AUTH_GET_CLASS (auth);
/* we need a token to check */
if (ctx->token == NULL) {
if (klass->authenticate) {
if (!klass->authenticate (auth, ctx))
goto authenticate_failed;
}
}
if (ctx->token == NULL)
goto no_auth;
return TRUE;
/* ERRORS */
authenticate_failed:
{
GST_DEBUG_OBJECT (auth, "authenticate failed");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
return FALSE;
}
no_auth:
{
GST_DEBUG_OBJECT (auth, "no authorization token found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
return FALSE;
}
}
static gboolean
accept_certificate_cb (GTlsConnection * conn, GTlsCertificate * peer_cert,
GTlsCertificateFlags errors, GstRTSPAuth * auth)
{
gboolean ret = FALSE;
g_signal_emit (auth, signals[SIGNAL_ACCEPT_CERTIFICATE], 0,
conn, peer_cert, errors, &ret);
return ret;
}
/* new connection */
static gboolean
check_connect (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
GstRTSPAuthPrivate *priv = auth->priv;
GTlsConnection *tls;
/* configure the connection */
if (priv->certificate) {
tls = gst_rtsp_connection_get_tls (ctx->conn, NULL);
g_tls_connection_set_certificate (tls, priv->certificate);
}
if (priv->mode != G_TLS_AUTHENTICATION_NONE) {
tls = gst_rtsp_connection_get_tls (ctx->conn, NULL);
g_tls_connection_set_database (tls, priv->database);
g_object_set (tls, "authentication-mode", priv->mode, NULL);
g_signal_connect (tls, "accept-certificate",
G_CALLBACK (accept_certificate_cb), auth);
}
return TRUE;
}
/* check url and methods */
static gboolean
check_url (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
GstRTSPAuthPrivate *priv = auth->priv;
if ((ctx->method & priv->methods) != 0)
if (!ensure_authenticated (auth, ctx))
goto not_authenticated;
return TRUE;
/* ERRORS */
not_authenticated:
{
return FALSE;
}
}
/* check access to media factory */
static gboolean
check_factory (GstRTSPAuth * auth, GstRTSPContext * ctx, const gchar * check)
{
const gchar *role;
GstRTSPPermissions *perms;
if (!ensure_authenticated (auth, ctx))
return FALSE;
if (!(role = gst_rtsp_token_get_string (ctx->token,
GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE)))
goto no_media_role;
if (!(perms = gst_rtsp_media_factory_get_permissions (ctx->factory)))
goto no_permissions;
if (g_str_equal (check, GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_ACCESS)) {
if (!gst_rtsp_permissions_is_allowed (perms, role,
GST_RTSP_PERM_MEDIA_FACTORY_ACCESS))
goto no_access;
} else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_MEDIA_FACTORY_CONSTRUCT)) {
if (!gst_rtsp_permissions_is_allowed (perms, role,
GST_RTSP_PERM_MEDIA_FACTORY_CONSTRUCT))
goto no_construct;
}
gst_rtsp_permissions_unref (perms);
return TRUE;
/* ERRORS */
no_media_role:
{
GST_DEBUG_OBJECT (auth, "no media factory role found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
return FALSE;
}
no_permissions:
{
GST_DEBUG_OBJECT (auth, "no permissions on media factory found");
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
return FALSE;
}
no_access:
{
GST_DEBUG_OBJECT (auth, "no permissions to access media factory");
gst_rtsp_permissions_unref (perms);
send_response (auth, GST_RTSP_STS_NOT_FOUND, ctx);
return FALSE;
}
no_construct:
{
GST_DEBUG_OBJECT (auth, "no permissions to construct media factory");
gst_rtsp_permissions_unref (perms);
send_response (auth, GST_RTSP_STS_UNAUTHORIZED, ctx);
return FALSE;
}
}
static gboolean
check_client_settings (GstRTSPAuth * auth, GstRTSPContext * ctx,
const gchar * check)
{
if (!ensure_authenticated (auth, ctx))
return FALSE;
return gst_rtsp_token_is_allowed (ctx->token,
GST_RTSP_TOKEN_TRANSPORT_CLIENT_SETTINGS);
}
static gboolean
default_check (GstRTSPAuth * auth, GstRTSPContext * ctx, 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, ctx, check);
} else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_URL)) {
res = check_url (auth, ctx, check);
} else if (g_str_has_prefix (check, "auth.check.media.factory.")) {
res = check_factory (auth, ctx, check);
} else if (g_str_equal (check, GST_RTSP_AUTH_CHECK_TRANSPORT_CLIENT_SETTINGS)) {
res = check_client_settings (auth, ctx, 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;
GstRTSPContext *ctx;
GstRTSPAuth *auth;
g_return_val_if_fail (check != NULL, FALSE);
if (!(ctx = gst_rtsp_context_get_current ()))
goto no_context;
/* no auth, we don't need to check */
if (!(auth = ctx->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, ctx, check);
return result;
/* ERRORS */
no_context:
{
GST_ERROR ("no context found");
return FALSE;
}
}
/**
* gst_rtsp_auth_make_basic:
* @user: a userid
* @pass: a password
*
* Construct a Basic authorisation token from @user and @pass.
*
* Returns: (transfer full): 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;
}