Commit 8d49d1cd authored by Thomas Guillem's avatar Thomas Guillem

add vlc_credential API

parent 868b8453
......@@ -30,9 +30,10 @@
typedef struct vlc_keystore vlc_keystore;
typedef struct vlc_keystore_entry vlc_keystore_entry;
typedef struct vlc_credential vlc_credential;
/**
* @defgroup keystore Keystore API
* @defgroup keystore Keystore and credential API
* @{
* @defgroup keystore_public Keystore public API
* @{
......@@ -144,6 +145,106 @@ vlc_keystore_remove(vlc_keystore *p_keystore,
VLC_API void
vlc_keystore_release_entries(vlc_keystore_entry *p_entries, unsigned int i_count);
/**
* @}
* @defgroup credential Credential API
* @{
*/
/**
* @note init with vlc_credential_init()
*/
struct vlc_credential
{
/** url to store or to search */
const vlc_url_t *p_url;
/** http realm or smb domain */
const char *psz_realm;
/** http authtype */
const char *psz_authtype;
/** valid only if vlc_credential_get() returned true */
const char *psz_username;
/** valid only if vlc_credential_get() returned true */
const char *psz_password;
/* internal */
enum {
GET_FROM_URL,
GET_FROM_OPTION,
GET_FROM_KEYSTORE,
GET_FROM_DIALOG,
} i_get_order;
vlc_keystore *p_keystore;
vlc_keystore_entry *p_entries;
unsigned int i_entries_count;
char *psz_split_realm;
char *psz_var_username;
char *psz_var_password;
char *psz_dialog_username;
char *psz_dialog_password;
bool b_store;
};
/**
* Init a credential struct
*
* @note to be cleaned with vlc_credential_clean()
*
* @param psz_url url to store or to search
*/
VLC_API void
vlc_credential_init(vlc_credential *p_credential, const vlc_url_t *p_url);
/**
* Clean a credential struct
*/
VLC_API void
vlc_credential_clean(vlc_credential *p_credential);
/**
* Get a username/password couple
*
* This will search for a credential using url, VLC options, the vlc_keystore
* or by asking the user via dialog_Login(). This function can be called
* indefinitely, it will first return the user/password from the url (if any),
* then from VLC options (if any), then from the keystore (if any), and finally
* from the dialog (if any). This function will return true as long as the user
* fill the dialog texts and will return false when the user cancel it.
*
* @param p_parent the parent object (for var, keystore and dialog)
* @param psz_option_username VLC option name for the username
* @param psz_option_password VLC option name for the password
* @param psz_dialog_title dialog title, if NULL, this function won't use the
* keystore or the dialog
* @param psz_dialog_fmt dialog text using format
*
* @return true if vlc_credential.psz_username and vlc_credential.psz_password
* are valid, otherwise this function should not be called again.
*/
VLC_API bool
vlc_credential_get(vlc_credential *p_credential, vlc_object_t *p_parent,
const char *psz_option_username,
const char *psz_option_password,
const char *psz_dialog_title,
const char *psz_dialog_fmt, ...) VLC_FORMAT(6, 7);
#define vlc_credential_get(a, b, c, d, e, f, ...) \
vlc_credential_get(a, VLC_OBJECT(b), c, d, e, f, ##__VA_ARGS__)
/**
* Store the last dialog credential returned by vlc_credential_get()
*
* This function will store the credential only if it comes from the dialog and
* if the vlc_keystore object is valid.
*
* @return true if credential was stored, false otherwise
*/
VLC_API bool
vlc_credential_store(vlc_credential *p_credential);
/**
* @}
* @defgroup keystore_implementation Implemented by keystore modules
......
......@@ -519,6 +519,10 @@ vlc_cond_init_daytime
vlc_cond_signal
vlc_cond_timedwait
vlc_cond_wait
vlc_credential_init
vlc_credential_clean
vlc_credential_get
vlc_credential_store
vlc_sem_init
vlc_sem_destroy
vlc_sem_post
......
......@@ -23,8 +23,10 @@
#endif
#include <vlc_common.h>
#include <vlc_dialog.h>
#include <vlc_keystore.h>
#include <vlc_modules.h>
#include <vlc_url.h>
#include <libvlc.h>
#include <assert.h>
......@@ -120,5 +122,365 @@ vlc_keystore_release_entries(vlc_keystore_entry *p_entries, unsigned int i_count
free(p_entry->ppsz_values[j]);
free(p_entry->p_secret);
}
free (p_entries);
free(p_entries);
}
static vlc_keystore_entry *
find_closest_path(vlc_keystore_entry *p_entries, unsigned i_count,
const char *psz_path)
{
vlc_keystore_entry *p_match_entry = NULL;
size_t i_last_pathlen = 0;
/* Try to find the entry that has the closest path to psz_url */
for (unsigned int i = 0; i < i_count; ++i)
{
vlc_keystore_entry *p_entry = &p_entries[i];
const char *psz_entry_path = p_entry->ppsz_values[KEY_PATH];
size_t i_entry_pathlen = strlen(psz_entry_path);
if (strncasecmp(psz_path, psz_entry_path, i_entry_pathlen) == 0
&& i_entry_pathlen > i_last_pathlen)
{
i_last_pathlen = i_entry_pathlen;
p_match_entry = p_entry;
}
}
return p_match_entry;
}
static bool
is_credential_valid(vlc_credential *p_credential)
{
return p_credential->psz_username && *p_credential->psz_username != '\0'
&& p_credential->psz_password;
}
/* Default port for each protocol */
static struct
{
const char * psz_protocol;
unsigned int i_port;
} protocol_default_ports [] = {
{ "rtsp", 80 },
{ "http", 80 },
{ "https", 443 },
{ "ftp", 21 },
{ "sftp", 22 },
{ "smb", 445 },
};
/* Don't store a port if it's the default one */
static bool
protocol_is_default_port(const vlc_url_t *p_url)
{
for (unsigned int i = 0; i < sizeof(protocol_default_ports)
/ sizeof(*protocol_default_ports); ++i)
{
if (p_url->i_port == protocol_default_ports[i].i_port
&& strcasecmp(p_url->psz_protocol,
protocol_default_ports[i].psz_protocol) == 0)
return true;
}
return false;
}
static bool
protocol_is_smb(const vlc_url_t *p_url)
{
return strcasecmp(p_url->psz_protocol, "smb") == 0;
}
static bool
protocol_store_path(const vlc_url_t *p_url)
{
return p_url->psz_path
&& (strncasecmp(p_url->psz_protocol, "http", 4) == 0
|| strcasecmp(p_url->psz_protocol, "rtsp") == 0
|| protocol_is_smb(p_url));
}
/* Split domain;user in userinfo */
static void
smb_split_domain(vlc_credential *p_credential)
{
char *psz_delim = strchr(p_credential->psz_username, ';');
if (psz_delim)
{
size_t i_len = psz_delim - p_credential->psz_username;
if (i_len > 0)
{
p_credential->psz_split_realm = strndup(p_credential->psz_username,
i_len);
p_credential->psz_realm = p_credential->psz_split_realm;
}
p_credential->psz_username = psz_delim + 1;
}
}
static void
credential_find_keystore(vlc_credential *p_credential)
{
const vlc_url_t *p_url = p_credential->p_url;
const char *ppsz_values[KEY_MAX] = { 0 };
ppsz_values[KEY_PROTOCOL] = p_url->psz_protocol;
ppsz_values[KEY_USER] = p_credential->psz_username;
ppsz_values[KEY_SERVER] = p_url->psz_host;
/* don't try to match with the path */
ppsz_values[KEY_REALM] = p_credential->psz_realm;
ppsz_values[KEY_AUTHTYPE] = p_credential->psz_authtype;
char psz_port[21];
if (p_url->i_port > 0 && !protocol_is_default_port(p_url))
{
sprintf(psz_port, "%u", p_url->i_port);
ppsz_values[KEY_PORT] = psz_port;
}
if (p_credential->i_entries_count > 0)
{
vlc_keystore_release_entries(p_credential->p_entries,
p_credential->i_entries_count);
p_credential->i_entries_count = 0;
}
p_credential->i_entries_count = vlc_keystore_find(p_credential->p_keystore,
ppsz_values,
&p_credential->p_entries);
if (p_credential->i_entries_count > 0)
{
vlc_keystore_entry *p_entry;
if (protocol_store_path(p_url))
p_entry = find_closest_path(p_credential->p_entries,
p_credential->i_entries_count,
p_url->psz_path);
else
p_entry = &p_credential->p_entries[0];
if (!p_entry || p_entry->p_secret[p_entry->i_secret_len - 1] != '\0')
{
vlc_keystore_release_entries(p_credential->p_entries,
p_credential->i_entries_count);
p_credential->i_entries_count = 0;
}
else
{
p_credential->psz_password = (const char *)p_entry->p_secret;
p_credential->psz_username = p_entry->ppsz_values[KEY_USER];
}
}
}
void
vlc_credential_init(vlc_credential *p_credential, const vlc_url_t *p_url)
{
assert(p_credential);
memset(p_credential, 0, sizeof(*p_credential));
p_credential->i_get_order = GET_FROM_URL;
p_credential->p_url = p_url;
}
void
vlc_credential_clean(vlc_credential *p_credential)
{
if (p_credential->p_keystore)
{
if (p_credential->i_entries_count > 0)
vlc_keystore_release_entries(p_credential->p_entries,
p_credential->i_entries_count);
vlc_keystore_release(p_credential->p_keystore);
}
free(p_credential->psz_split_realm);
free(p_credential->psz_var_username);
free(p_credential->psz_var_password);
free(p_credential->psz_dialog_username);
free(p_credential->psz_dialog_password);
}
#undef vlc_credential_get
bool
vlc_credential_get(vlc_credential *p_credential, vlc_object_t *p_parent,
const char *psz_option_username,
const char *psz_option_password,
const char *psz_dialog_title,
const char *psz_dialog_fmt, ...)
{
assert(p_credential && p_parent);
const vlc_url_t *p_url = p_credential->p_url;
if (!p_url || !p_url->psz_protocol || !p_url->psz_host)
{
msg_Err(p_parent, "vlc_credential_get: invalid url");
return false;
}
/* Don't set username to NULL, we may want to use the last one set */
p_credential->psz_password = NULL;
while (!is_credential_valid(p_credential))
{
/* First, fetch credential from URL (if any).
* Secondly, fetch credential from VLC Options (if any).
* Thirdly, fetch credential from keystore (if any) using user and realm
* previously set by the caller, the URL or by VLC Options.
* Finally, fetch credential from the dialog (if any). This last will be
* repeated until user cancel the dialog. */
switch (p_credential->i_get_order)
{
case GET_FROM_URL:
p_credential->psz_username = p_url->psz_username;
p_credential->psz_password = p_url->psz_password;
if (p_credential->psz_password)
msg_Warn(p_parent, "Password in a URI is DEPRECATED");
if (p_url->psz_username && protocol_is_smb(p_url))
smb_split_domain(p_credential);
p_credential->i_get_order++;
break;
case GET_FROM_OPTION:
free(p_credential->psz_var_username);
free(p_credential->psz_var_password);
p_credential->psz_var_username =
p_credential->psz_var_password = NULL;
if (psz_option_username)
p_credential->psz_var_username =
var_InheritString(p_parent, psz_option_username);
if (psz_option_password)
p_credential->psz_var_password =
var_InheritString(p_parent, psz_option_password);
if (p_credential->psz_var_username)
p_credential->psz_username = p_credential->psz_var_username;
if (p_credential->psz_var_password)
p_credential->psz_password = p_credential->psz_var_password;
p_credential->i_get_order++;
break;
case GET_FROM_KEYSTORE:
if (!psz_dialog_title || !psz_dialog_fmt)
return false;
if (p_credential->p_keystore == NULL)
p_credential->p_keystore = vlc_keystore_create(p_parent);
if (p_credential->p_keystore != NULL)
credential_find_keystore(p_credential);
p_credential->i_get_order++;
break;
default:
case GET_FROM_DIALOG:
if (!psz_dialog_title || !psz_dialog_fmt)
return false;
free(p_credential->psz_dialog_username);
free(p_credential->psz_dialog_password);
p_credential->psz_dialog_username =
p_credential->psz_dialog_password = NULL;
/* TODO: save previously saved username and print it in dialog */
va_list ap;
char *psz_dialog_text;
va_start(ap, psz_dialog_fmt);
if (vasprintf(&psz_dialog_text, psz_dialog_fmt, ap) == -1)
{
va_end(ap);
return false;
}
va_end(ap);
dialog_Login(p_parent, &p_credential->psz_dialog_username,
&p_credential->psz_dialog_password,
psz_dialog_title, psz_dialog_text);
free(psz_dialog_text);
if (p_credential->psz_dialog_username
&& p_credential->psz_dialog_password)
{
/* TODO: add a dialog option to know if the user want to store
* the credential */
p_credential->b_store = true;
p_credential->psz_username = p_credential->psz_dialog_username;
p_credential->psz_password = p_credential->psz_dialog_password;
if (protocol_is_smb(p_url))
smb_split_domain(p_credential);
}
else
{
p_credential->psz_username = p_credential->psz_password = NULL;
return false;
}
break;
}
}
return is_credential_valid(p_credential);
}
bool
vlc_credential_store(vlc_credential *p_credential)
{
if (!p_credential->p_keystore || !p_credential->b_store)
return false;
const vlc_url_t *p_url = p_credential->p_url;
char *psz_path = NULL;
if (protocol_store_path(p_url) && (psz_path = strdup(p_url->psz_path)))
{
char *p_slash;
if (protocol_is_smb(p_url))
{
/* Remove all characters after the first slash (store the share but
* not the path) */
p_slash = strchr(psz_path + 1, '/');
}
else
{
/* Remove all characters after the last slash (store the path
* without the filename) */
p_slash = strrchr(psz_path + 1, '/');
}
if (p_slash && psz_path != p_slash)
*p_slash = '\0';
else
{
free(psz_path);
psz_path = NULL;
}
}
const char *ppsz_values[KEY_MAX] = { 0 };
ppsz_values[KEY_PROTOCOL] = p_url->psz_protocol;
ppsz_values[KEY_USER] = p_credential->psz_username;
ppsz_values[KEY_SERVER] = p_url->psz_host;
ppsz_values[KEY_PATH] = psz_path;
ppsz_values[KEY_REALM] = p_credential->psz_realm;
ppsz_values[KEY_AUTHTYPE] = p_credential->psz_authtype;
char psz_port[21];
if (p_url->i_port > 0 && !protocol_is_default_port(p_url))
{
sprintf(psz_port, "%u", p_url->i_port);
ppsz_values[KEY_PORT] = psz_port;
}
char *psz_label;
if (asprintf(&psz_label, "LibVLC password for %s://%s%s",
p_url->psz_protocol, p_url->psz_host,
psz_path ? psz_path : "") == -1)
return false;
bool b_ret = vlc_keystore_store(p_credential->p_keystore, ppsz_values,
(const uint8_t *)p_credential->psz_password,
-1, psz_label) == VLC_SUCCESS;
free(psz_label);
free(psz_path);
return b_ret;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment