mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
rtsp-auth: Add support for parsing .htdigest files
Passwords are usually not stored in clear text, but instead stored already hashed in a .htdigest file. Add support for parsing such files, add API to allow setting a custom realm in RTSPAuth, and update the digest example. https://bugzilla.gnome.org/show_bug.cgi?id=796637
This commit is contained in:
parent
b5a61f488d
commit
5ede2a5c5c
4 changed files with 203 additions and 7 deletions
|
@ -48,11 +48,14 @@ gst_rtsp_auth_get_tls_database
|
|||
gst_rtsp_auth_set_tls_database
|
||||
gst_rtsp_auth_get_tls_authentication_mode
|
||||
gst_rtsp_auth_set_tls_authentication_mode
|
||||
gst_rtsp_auth_set_realm
|
||||
gst_rtsp_auth_get_realm
|
||||
gst_rtsp_auth_make_basic
|
||||
gst_rtsp_auth_add_basic
|
||||
gst_rtsp_auth_remove_basic
|
||||
gst_rtsp_auth_add_digest
|
||||
gst_rtsp_auth_remove_digest
|
||||
gst_rtsp_auth_parse_htdigest
|
||||
gst_rtsp_auth_check
|
||||
gst_rtsp_auth_get_default_token
|
||||
gst_rtsp_auth_set_default_token
|
||||
|
|
|
@ -21,6 +21,18 @@
|
|||
|
||||
#include <gst/rtsp-server/rtsp-server.h>
|
||||
|
||||
static gchar *htdigest_path = NULL;
|
||||
static gchar *realm = NULL;
|
||||
|
||||
static GOptionEntry entries[] = {
|
||||
{"htdigest-path", 'h', 0, G_OPTION_ARG_STRING, &htdigest_path,
|
||||
"Path to an htdigest file to parse (default: None)", "PATH"},
|
||||
{"realm", 'r', 0, G_OPTION_ARG_STRING, &realm,
|
||||
"Authentication realm (default: None)", "REALM"},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
static gboolean
|
||||
remove_func (GstRTSPSessionPool * pool, GstRTSPSession * session,
|
||||
GstRTSPServer * server)
|
||||
|
@ -63,8 +75,19 @@ main (int argc, char *argv[])
|
|||
GstRTSPMediaFactory *factory;
|
||||
GstRTSPAuth *auth;
|
||||
GstRTSPToken *token;
|
||||
GOptionContext *optctx;
|
||||
GError *error = NULL;
|
||||
|
||||
gst_init (&argc, &argv);
|
||||
optctx = g_option_context_new (NULL);
|
||||
g_option_context_add_main_entries (optctx, entries, NULL);
|
||||
g_option_context_add_group (optctx, gst_init_get_option_group ());
|
||||
if (!g_option_context_parse (optctx, &argc, &argv, &error)) {
|
||||
g_printerr ("Error parsing options: %s\n", error->message);
|
||||
g_option_context_free (optctx);
|
||||
g_clear_error (&error);
|
||||
return -1;
|
||||
}
|
||||
g_option_context_free (optctx);
|
||||
|
||||
loop = g_main_loop_new (NULL, FALSE);
|
||||
|
||||
|
@ -142,6 +165,23 @@ main (int argc, char *argv[])
|
|||
gst_rtsp_auth_add_digest (auth, "user", "password", token);
|
||||
gst_rtsp_token_unref (token);
|
||||
|
||||
if (htdigest_path) {
|
||||
token =
|
||||
gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
|
||||
"user", NULL);
|
||||
|
||||
if (!gst_rtsp_auth_parse_htdigest (auth, htdigest_path, token)) {
|
||||
g_printerr ("Could not parse htdigest at %s\n", htdigest_path);
|
||||
gst_rtsp_token_unref (token);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
gst_rtsp_token_unref (token);
|
||||
}
|
||||
|
||||
if (realm)
|
||||
gst_rtsp_auth_set_realm (auth, realm);
|
||||
|
||||
/* make admin token */
|
||||
token =
|
||||
gst_rtsp_token_new (GST_RTSP_TOKEN_MEDIA_FACTORY_ROLE, G_TYPE_STRING,
|
||||
|
@ -171,6 +211,10 @@ main (int argc, char *argv[])
|
|||
g_print ("stream with user:password ready at rtsp://127.0.0.1:8554/test\n");
|
||||
g_print ("stream with admin:power ready at rtsp://127.0.0.1:8554/test\n");
|
||||
g_print ("stream with admin2:power2 ready at rtsp://127.0.0.1:8554/test2\n");
|
||||
|
||||
if (htdigest_path)
|
||||
g_print ("stream with htdigest users ready at rtsp://127.0.0.1:8554/test\n");
|
||||
|
||||
g_main_loop_run (loop);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -67,12 +67,14 @@ struct _GstRTSPAuthPrivate
|
|||
GstRTSPToken *default_token;
|
||||
GstRTSPMethod methods;
|
||||
GstRTSPAuthMethod auth_methods;
|
||||
gchar *realm;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GstRTSPToken *token;
|
||||
gchar *pass;
|
||||
gchar *md5_pass;
|
||||
} GstRTSPDigestEntry;
|
||||
|
||||
typedef struct
|
||||
|
@ -88,6 +90,7 @@ gst_rtsp_digest_entry_free (GstRTSPDigestEntry * entry)
|
|||
{
|
||||
gst_rtsp_token_unref (entry->token);
|
||||
g_free (entry->pass);
|
||||
g_free (entry->md5_pass);
|
||||
g_free (entry);
|
||||
}
|
||||
|
||||
|
@ -195,6 +198,7 @@ gst_rtsp_auth_init (GstRTSPAuth * auth)
|
|||
/* bitwise or of all methods that need authentication */
|
||||
priv->methods = 0;
|
||||
priv->auth_methods = GST_RTSP_AUTH_BASIC;
|
||||
priv->realm = g_strdup ("GStreamer RTSP Server");
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -213,6 +217,7 @@ gst_rtsp_auth_finalize (GObject * obj)
|
|||
g_hash_table_unref (priv->digest);
|
||||
g_hash_table_unref (priv->nonces);
|
||||
g_mutex_clear (&priv->lock);
|
||||
g_free (priv->realm);
|
||||
|
||||
G_OBJECT_CLASS (gst_rtsp_auth_parent_class)->finalize (obj);
|
||||
}
|
||||
|
@ -565,6 +570,99 @@ gst_rtsp_auth_add_digest (GstRTSPAuth * auth, const gchar * user,
|
|||
g_mutex_unlock (&priv->lock);
|
||||
}
|
||||
|
||||
/* With auth lock taken */
|
||||
static gboolean
|
||||
update_digest_cb (gchar *key, GstRTSPDigestEntry *entry, GHashTable *digest)
|
||||
{
|
||||
g_hash_table_replace (digest, key, entry);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_rtsp_auth_parse_htdigest:
|
||||
* @path: (type filename): Path to the htdigest file
|
||||
* @token: (transfer none): authorisation token
|
||||
*
|
||||
* Parse the contents of the file at @path and enable the privileges
|
||||
* listed in @token for the users it describes.
|
||||
*
|
||||
* The format of the file is expected to match the format described by
|
||||
* <https://en.wikipedia.org/wiki/Digest_access_authentication#The_.htdigest_file>,
|
||||
* as output by the `htdigest` command.
|
||||
*
|
||||
* Returns: %TRUE if the file was successfully parsed, %FALSE otherwise.
|
||||
*
|
||||
* Since: 1.16
|
||||
*/
|
||||
gboolean
|
||||
gst_rtsp_auth_parse_htdigest (GstRTSPAuth *auth, const gchar *path,
|
||||
GstRTSPToken *token)
|
||||
{
|
||||
GstRTSPAuthPrivate *priv;
|
||||
gboolean ret = FALSE;
|
||||
gchar *line = NULL;
|
||||
gchar *eol = NULL;
|
||||
gchar *contents = NULL;
|
||||
GError *error = NULL;
|
||||
GHashTable *new_entries = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
|
||||
(GDestroyNotify) gst_rtsp_digest_entry_free);
|
||||
|
||||
|
||||
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), FALSE);
|
||||
g_return_val_if_fail (path != NULL, FALSE);
|
||||
g_return_val_if_fail (GST_IS_RTSP_TOKEN (token), FALSE);
|
||||
|
||||
priv = auth->priv;
|
||||
if (!g_file_get_contents (path, &contents, NULL, &error)) {
|
||||
GST_ERROR_OBJECT(auth, "Could not parse htdigest: %s", error->message);
|
||||
goto done;
|
||||
}
|
||||
|
||||
for (line = contents; line && *line; line = eol ? eol + 1 : NULL) {
|
||||
GstRTSPDigestEntry *entry;
|
||||
gchar **strv;
|
||||
eol = strchr (line, '\n');
|
||||
|
||||
if (eol)
|
||||
*eol = '\0';
|
||||
|
||||
strv = g_strsplit (line, ":", -1);
|
||||
|
||||
if (!(strv[0] && strv[1] && strv[2] && !strv[3])) {
|
||||
GST_ERROR_OBJECT (auth, "Invalid htdigest format");
|
||||
g_strfreev (strv);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (strlen (strv[2]) != 32) {
|
||||
GST_ERROR_OBJECT (auth, "Invalid htdigest format, hash is expected to be 32 characters long");
|
||||
g_strfreev (strv);
|
||||
goto done;
|
||||
}
|
||||
|
||||
entry = g_new0 (GstRTSPDigestEntry, 1);
|
||||
entry->token = gst_rtsp_token_ref (token);
|
||||
entry->md5_pass = g_strdup (strv[2]);
|
||||
g_hash_table_replace (new_entries, g_strdup (strv[0]), entry);
|
||||
g_strfreev (strv);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
/* We only update digest if the file was entirely valid */
|
||||
g_mutex_lock (&priv->lock);
|
||||
g_hash_table_foreach_steal (new_entries, (GHRFunc) update_digest_cb, priv->digest);
|
||||
g_mutex_unlock (&priv->lock);
|
||||
|
||||
done:
|
||||
if (error)
|
||||
g_clear_error (&error);
|
||||
g_free(contents);
|
||||
g_hash_table_unref (new_entries);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_rtsp_auth_remove_digest:
|
||||
* @auth: a #GstRTSPAuth
|
||||
|
@ -709,10 +807,17 @@ default_digest_auth (GstRTSPAuth * auth, GstRTSPContext * ctx,
|
|||
if (nonce_entry->client && nonce_entry->client != ctx->client)
|
||||
goto out;
|
||||
|
||||
expected_response =
|
||||
gst_rtsp_generate_digest_auth_response (NULL,
|
||||
gst_rtsp_method_as_text (ctx->method), "GStreamer RTSP Server", user,
|
||||
digest_entry->pass, uri, nonce);
|
||||
if (digest_entry->md5_pass) {
|
||||
expected_response = gst_rtsp_generate_digest_auth_response_from_md5 (NULL,
|
||||
gst_rtsp_method_as_text (ctx->method), digest_entry->md5_pass,
|
||||
uri, nonce);
|
||||
} else {
|
||||
expected_response =
|
||||
gst_rtsp_generate_digest_auth_response (NULL,
|
||||
gst_rtsp_method_as_text (ctx->method), realm, user,
|
||||
digest_entry->pass, uri, nonce);
|
||||
}
|
||||
|
||||
if (!expected_response || strcmp (response, expected_response) != 0)
|
||||
goto out;
|
||||
|
||||
|
@ -794,8 +899,10 @@ static void
|
|||
default_generate_authenticate_header (GstRTSPAuth * auth, GstRTSPContext * ctx)
|
||||
{
|
||||
if (auth->priv->auth_methods & GST_RTSP_AUTH_BASIC) {
|
||||
gchar *auth_header = g_strdup_printf("Basic realm=\"%s\"", auth->priv->realm);
|
||||
gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
|
||||
"Basic realm=\"GStreamer RTSP Server\"");
|
||||
auth_header);
|
||||
g_free (auth_header);
|
||||
}
|
||||
|
||||
if (auth->priv->auth_methods & GST_RTSP_AUTH_DIGEST) {
|
||||
|
@ -807,7 +914,7 @@ default_generate_authenticate_header (GstRTSPAuth * auth, GstRTSPContext * ctx)
|
|||
|
||||
auth_header =
|
||||
g_strdup_printf
|
||||
("Digest realm=\"GStreamer RTSP Server\", nonce=\"%s\"", nonce_value);
|
||||
("Digest realm=\"%s\", nonce=\"%s\"", auth->priv->realm, nonce_value);
|
||||
gst_rtsp_message_add_header (ctx->response, GST_RTSP_HDR_WWW_AUTHENTICATE,
|
||||
auth_header);
|
||||
g_free (auth_header);
|
||||
|
@ -1117,3 +1224,37 @@ gst_rtsp_auth_make_basic (const gchar * user, const gchar * pass)
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_rtsp_auth_set_realm:
|
||||
*
|
||||
* Set the @realm of @auth
|
||||
*
|
||||
* Since: 1.16
|
||||
*/
|
||||
void
|
||||
gst_rtsp_auth_set_realm (GstRTSPAuth *auth, const gchar *realm)
|
||||
{
|
||||
g_return_if_fail (GST_IS_RTSP_AUTH (auth));
|
||||
g_return_if_fail (realm != NULL);
|
||||
|
||||
if (auth->priv->realm)
|
||||
g_free (auth->priv->realm);
|
||||
|
||||
auth->priv->realm = g_strdup (realm);
|
||||
}
|
||||
|
||||
/**
|
||||
* gst_rtsp_auth_get_realm:
|
||||
*
|
||||
* Returns: (transfer full): the @realm of @auth
|
||||
*
|
||||
* Since: 1.16
|
||||
*/
|
||||
gchar *
|
||||
gst_rtsp_auth_get_realm (GstRTSPAuth *auth)
|
||||
{
|
||||
g_return_val_if_fail (GST_IS_RTSP_AUTH (auth), NULL);
|
||||
|
||||
return g_strdup(auth->priv->realm);
|
||||
}
|
||||
|
|
|
@ -135,6 +135,14 @@ GstRTSPAuthMethod gst_rtsp_auth_get_supported_methods (GstRTSPAuth *auth);
|
|||
GST_RTSP_SERVER_API
|
||||
gboolean gst_rtsp_auth_check (const gchar *check);
|
||||
|
||||
GST_RTSP_SERVER_API
|
||||
gboolean gst_rtsp_auth_parse_htdigest (GstRTSPAuth *auth, const gchar *path, GstRTSPToken *token);
|
||||
|
||||
GST_RTSP_SERVER_API
|
||||
void gst_rtsp_auth_set_realm (GstRTSPAuth *auth, const gchar *realm);
|
||||
|
||||
GST_RTSP_SERVER_API
|
||||
gchar * gst_rtsp_auth_get_realm (GstRTSPAuth *auth);
|
||||
|
||||
/* helpers */
|
||||
|
||||
|
|
Loading…
Reference in a new issue