#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)
GstRTSPMediaFactory *factory;
GstRTSPAuth *auth;
GstRTSPToken *token;
-
- gst_init (&argc, &argv);
+ GOptionContext *optctx;
+ GError *error = NULL;
+
+ 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);
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,
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;
GstRTSPToken *default_token;
GstRTSPMethod methods;
GstRTSPAuthMethod auth_methods;
+ gchar *realm;
};
typedef struct
{
GstRTSPToken *token;
gchar *pass;
+ gchar *md5_pass;
} GstRTSPDigestEntry;
typedef struct
{
gst_rtsp_token_unref (entry->token);
g_free (entry->pass);
+ g_free (entry->md5_pass);
g_free (entry);
}
/* 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
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);
}
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
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;
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) {
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);
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);
+}