int access_noacl;
-static int passwd_verify(const char *username, const char *passwd);
-static int passwd_verify2(const char *username, const char *passwd,
+static int passwd_verify(const char *username, verify_callback_t verify, void *aux);
+static int passwd_verify2(const char *username, verify_callback_t verify, void *aux,
const char *username2, const char *passwd2);
-static int passwd_verify_digest(const char *username, const uint8_t *digest,
- const uint8_t *challenge);
-static int passwd_verify_digest2(const char *username, const uint8_t *digest,
- const uint8_t *challenge,
- const char *username2, const char *passwd2);
/**
*
return 0;
}
-/**
- *
- */
-int
-access_verify(const char *username, const char *password,
- struct sockaddr *src, uint32_t mask)
-{
- uint32_t bits = 0;
- access_entry_t *ae;
- int match = 0, nouser = username == NULL || username[0] == '\0';
-
- if (access_noacl)
- return 0;
-
- if (access_ip_blocked(src))
- return -1;
-
- if (!passwd_verify2(username, password,
- superuser_username, superuser_password))
- return 0;
-
- if (passwd_verify(username, password))
- username = NULL;
-
- TAILQ_FOREACH(ae, &access_entries, ae_link) {
-
- if(!ae->ae_enabled)
- continue;
-
- if(ae->ae_username[0] != '*') {
- /* acl entry requires username to match */
- if(username == NULL || strcmp(username, ae->ae_username))
- continue; /* Didn't get one */
- }
-
- if(!netmask_verify(&ae->ae_ipmasks, src))
- continue; /* IP based access mismatches */
-
- if (ae->ae_username[0] != '*')
- match = 1;
-
- bits |= ae->ae_rights;
- }
-
- /* Username was not matched - no access */
- if (!match && !nouser)
- bits = 0;
-
- return (mask & ACCESS_OR) ?
- ((mask & bits) ? 0 : -1) :
- ((mask & bits) == mask ? 0 : -1);
-}
-
/*
*
*/
}
/**
- *
*/
static void
access_set_lang_ui(access_t *a)
*
*/
access_t *
-access_get(const char *username, const char *password, struct sockaddr *src)
+access_get(struct sockaddr *src, const char *username, verify_callback_t verify, void *aux)
{
access_t *a = access_alloc();
access_entry_t *ae;
if (!access_noacl && access_ip_blocked(src))
return a;
- if (!passwd_verify(username, password)) {
+ if (!passwd_verify(username, verify, aux)) {
a->aa_username = strdup(username);
a->aa_representative = strdup(username);
- if(!passwd_verify2(username, password,
+ if(!passwd_verify2(username, verify, aux,
superuser_username, superuser_password))
return access_full(a);
} else {
a->aa_representative = malloc(50);
tcp_get_str_from_ip((struct sockaddr*)src, a->aa_representative, 50);
- if(!passwd_verify2(username, password,
+ if(!passwd_verify2(username, verify, aux,
superuser_username, superuser_password))
return access_full(a);
username = NULL;
return a;
}
-/**
- *
- */
-access_t *
-access_get_hashed(const char *username, const uint8_t digest[20],
- const uint8_t *challenge, struct sockaddr *src)
-{
- access_t *a = access_alloc();
- access_entry_t *ae;
- int nouser = username == NULL || username[0] == '\0';
-
- if (!access_noacl && access_ip_blocked(src))
- return a;
-
- if (!passwd_verify_digest(username, digest, challenge)) {
- a->aa_username = strdup(username);
- a->aa_representative = strdup(username);
- if(!passwd_verify_digest2(username, digest, challenge,
- superuser_username, superuser_password))
- return access_full(a);
- } else {
- a->aa_representative = malloc(50);
- tcp_get_str_from_ip((struct sockaddr*)src, a->aa_representative, 50);
- if(!passwd_verify_digest2(username, digest, challenge,
- superuser_username, superuser_password))
- return access_full(a);
- username = NULL;
- }
-
- if(access_noacl)
- return access_full(a);
-
- TAILQ_FOREACH(ae, &access_entries, ae_link) {
-
- if(!ae->ae_enabled)
- continue;
-
- if(!netmask_verify(&ae->ae_ipmasks, src))
- continue; /* IP based access mismatches */
-
- if(ae->ae_username[0] != '*') {
-
- if (username == NULL || strcmp(username, ae->ae_username))
- continue;
-
- a->aa_match = 1;
- }
-
- access_update(a, ae);
- }
-
- /* Username was not matched - no access */
- if (!a->aa_match) {
- free(a->aa_username);
- a->aa_username = NULL;
- if (!nouser)
- a->aa_rights = 0;
- }
-
- access_set_lang_ui(a);
-
- if (tvhtrace_enabled())
- access_dump_a(a);
- return a;
-}
-
/**
*
*/
static int passwd_entry_class_password_set(void *o, const void *v);
static int
-passwd_verify_digest2(const char *username, const uint8_t *digest,
- const uint8_t *challenge,
- const char *username2, const char *passwd2)
-{
- uint8_t d[20];
-
- if (username == NULL || username[0] == '\0' ||
- username2 == NULL || username2[0] == '\0' ||
- passwd2 == NULL || passwd2[0] == '\0')
- return -1;
-
- if (strcmp(username, username2))
- return -1;
-
- sha1_calc(d, (uint8_t *)passwd2, strlen(passwd2), challenge, 32);
-
- return memcmp(d, digest, 20) ? -1 : 0;
-}
-
-static int
-passwd_verify_digest(const char *username, const uint8_t *digest,
- const uint8_t *challenge)
-{
- passwd_entry_t *pw;
-
- TAILQ_FOREACH(pw, &passwd_entries, pw_link)
- if (pw->pw_enabled &&
- !passwd_verify_digest2(username, digest, challenge,
- pw->pw_username, pw->pw_password))
- return 0;
- return -1;
-}
-
-static int
-passwd_verify2(const char *username, const char *passwd,
- const char *username2, const char *passwd2)
+passwd_verify2
+ (const char *username, verify_callback_t verify, void *aux,
+ const char *username2, const char *passwd2)
{
if (username == NULL || username[0] == '\0' ||
username2 == NULL || username2[0] == '\0' ||
- passwd == NULL || passwd2 == NULL)
+ passwd2 == NULL)
return -1;
if (strcmp(username, username2))
return -1;
- return strcmp(passwd, passwd2) ? -1 : 0;
+ return verify(aux, passwd2) ? 0 : -1;
}
static int
-passwd_verify(const char *username, const char *passwd)
+passwd_verify
+ (const char *username, verify_callback_t verify, void *aux)
{
passwd_entry_t *pw;
TAILQ_FOREACH(pw, &passwd_entries, pw_link)
if (pw->pw_enabled &&
- !passwd_verify2(username, passwd,
+ !passwd_verify2(username, verify, aux,
pw->pw_username, pw->pw_password))
return 0;
return -1;
*
* Return 0 if access is granted, -1 otherwise
*/
-int access_verify(const char *username, const char *password,
- struct sockaddr *src, uint32_t mask);
-
static inline int access_verify2(access_t *a, uint32_t mask)
{ return (mask & ACCESS_OR) ?
((a->aa_rights & mask) ? 0 : -1) :
/**
* Get the access structure
*/
-access_t *access_get(const char *username, const char *password,
- struct sockaddr *src);
+typedef int (*verify_callback_t)(void *aux, const char *passwd);
-/**
- *
- */
-access_t *
-access_get_hashed(const char *username, const uint8_t digest[20],
- const uint8_t *challenge, struct sockaddr *src);
+access_t *access_get(struct sockaddr *src, const char *username,
+ verify_callback_t verify, void *aux);
/**
*
memset(&config, 0, sizeof(config));
config.idnode.in_class = &config_class;
config.ui_quicktips = 1;
+ config.digest = 1;
+ config.realm = strdup("tvheadend");
config.info_area = strdup("login,storage,time");
config.cookie_expires = 7;
config.dscp = -1;
free(config.language);
free(config.language_ui);
free(config.theme_ui);
+ free(config.realm);
free(config.info_area);
free(config.muxconf_path);
free(config.chicon_path);
.opts = PO_ADVANCED,
.group = 1
},
+ {
+ .type = PT_BOOL,
+ .id = "digest",
+ .name = N_("Use HTTP digest authentication"),
+ .desc = N_("Digest access authentication is intended as a security trade-off. "
+ "It is intended to replace unencrypted HTTP basic access authentication. "
+ "This option should be enabled for the standard usage."),
+ .off = offsetof(config_t, digest),
+ .opts = PO_ADVANCED,
+ .group = 1
+ },
{
.type = PT_U32,
.intextra = INTEXTRA_RANGE(1, 0x7ff, 1),
int uilevel;
int uilevel_nochange;
int ui_quicktips;
+ int digest;
+ char *realm;
char *wizard;
char *full_version;
char *server_name;
* Message processing
* *************************************************************************/
+/**
+ *
+ */
+struct htsp_verify_struct {
+ const uint8_t *digest;
+ const uint8_t *challenge;
+};
+
+static int
+htsp_verify_callback(void *aux, const char *passwd)
+{
+ struct htsp_verify_struct *v = aux;
+ uint8_t d[20];
+
+ if (v->digest == NULL || v->challenge == NULL) return 0;
+ sha1_calc(d, (uint8_t *)passwd, strlen(passwd), v->challenge, 32);
+ return memcmp(d, v->digest, 20) == 0;
+}
+
/**
* Raise privs by field in message
*/
static int
htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
{
+ struct htsp_verify_struct vs;
const char *username;
const void *digest;
size_t digestlen;
if(!htsmsg_get_bin(m, "digest", &digest, &digestlen)) {
- rights = access_get_hashed(username, digest, htsp->htsp_challenge,
- (struct sockaddr *)htsp->htsp_peer);
+ vs.digest = digest;
+ vs.challenge = htsp->htsp_challenge;
+ rights = access_get((struct sockaddr *)htsp->htsp_peer, username,
+ htsp_verify_callback, &vs);
if (rights->aa_rights == 0) {
tvhlog(LOG_INFO, "htsp", "%s: Unauthorized access", htsp->htsp_logname);
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
+#include <openssl/md5.h>
#include "tvheadend.h"
#include "tcp.h"
"Dec"
};
+/**
+ * Digest authentication helpers
+ */
+typedef struct http_nonce {
+ RB_ENTRY(http_nonce) link;
+ mtimer_t expire;
+ char nonce[MD5_DIGEST_LENGTH*2+1];
+} http_nonce_t;
+
+static RB_HEAD(, http_nonce) http_nonces;
+
+static int
+http_nonce_cmp(const void *a, const void *b)
+{
+ return strcmp(((http_nonce_t *)a)->nonce, ((http_nonce_t *)b)->nonce);
+}
+
+static void
+http_nonce_timeout(void *aux)
+{
+ struct http_nonce *n = aux;
+ RB_REMOVE(&http_nonces, n, link);
+ free(n);
+}
+
+static char *
+http_get_nonce(void)
+{
+ struct http_nonce *n = calloc(1, sizeof(*n));
+ char stamp[16], *m;
+ int64_t mono = getmonoclock();
+ mono ^= 0xa1687211885fcd30;
+ snprintf(stamp, sizeof(stamp), "%"PRId64, mono);
+ m = md5sum(stamp, 1);
+ strcpy(n->nonce, m);
+ pthread_mutex_lock(&global_lock);
+ RB_INSERT_SORTED(&http_nonces, n, link, http_nonce_cmp);
+ mtimer_arm_rel(&n->expire, http_nonce_timeout, n, sec2mono(10));
+ pthread_mutex_unlock(&global_lock);
+ return m;
+}
+
+static char *
+http_find_nonce(const char *nonce)
+{
+ struct http_nonce *n, tmp;
+
+ if (nonce == NULL)
+ return NULL;
+ strcpy(tmp.nonce, nonce);
+ pthread_mutex_lock(&global_lock);
+ n = RB_FIND(&http_nonces, &tmp, link, http_nonce_cmp);
+ pthread_mutex_unlock(&global_lock);
+ if (n) {
+ pthread_mutex_lock(&global_lock);
+ mtimer_arm_rel(&n->expire, http_nonce_timeout, n, sec2mono(2*60));
+ pthread_mutex_unlock(&global_lock);
+ return n->nonce;
+ }
+ return NULL;
+}
+
+static char *
+http_get_opaque(const char *realm, const char *nonce)
+{
+ char *a = alloca(strlen(realm) + strlen(nonce) + 1);
+ strcpy(a, realm);
+ strcat(a, nonce);
+ return md5sum(a, 1);
+}
+
/**
* Transmit a HTTP reply
*/
htsbuf_qprintf(&hdrs, "Cache-Control: max-age=%d\r\n", maxage);
}
- if(rc == HTTP_STATUS_UNAUTHORIZED)
- htsbuf_append_str(&hdrs, "WWW-Authenticate: Basic realm=\"tvheadend\"\r\n");
+ if(rc == HTTP_STATUS_UNAUTHORIZED) {
+ const char *realm = config.realm;
+ if (realm == NULL || realm[0] == '\0')
+ realm = "tvheadend";
+ if (config.digest) {
+ if (hc->hc_nonce == NULL)
+ hc->hc_nonce = http_get_nonce();
+ char *opaque = http_get_opaque(realm, hc->hc_nonce);
+ htsbuf_append_str(&hdrs, "WWW-Authenticate: Digest realm=\"");
+ htsbuf_append_str(&hdrs, realm);
+ htsbuf_append_str(&hdrs, "\", qop=\"auth, auth-int\", nonce=\"");
+ htsbuf_append_str(&hdrs, hc->hc_nonce);
+ htsbuf_append_str(&hdrs, "\", opaque=\"");
+ htsbuf_append_str(&hdrs, opaque);
+ htsbuf_append_str(&hdrs, "\"\r\n");
+ free(opaque);
+ } else {
+ htsbuf_append_str(&hdrs, "WWW-Authenticate: Basic realm=\"");
+ htsbuf_append_str(&hdrs, realm);
+ htsbuf_append_str(&hdrs, "\"\r\n");
+ }
+ }
if (hc->hc_logout_cookie == 1) {
htsbuf_append_str(&hdrs, "Set-Cookie: logout=1; Path=\"/logout\"\r\n");
} else if (hc->hc_logout_cookie == 2) {
return 0;
}
+/**
+ *
+ */
+static char *
+http_get_header_value(const char *hdr, const char *name)
+{
+ char *tokbuf, *tok, *saveptr = NULL, *q, *s;
+
+ tokbuf = tvh_strdupa(hdr);
+ tok = strtok_r(tokbuf, ",", &saveptr);
+ while (tok) {
+ while (*tok == ' ')
+ tok++;
+ if ((q = strchr(tok, '=')) == NULL)
+ goto next;
+ *q = '\0';
+ if (strcasecmp(tok, name))
+ goto next;
+ s = q + 1;
+ if (*s == '"') {
+ q = strchr(++s, '"');
+ if (q)
+ *q = '\0';
+ } else {
+ q = strchr(s, ' ');
+ if (q)
+ *q = '\0';
+ }
+ return strdup(s);
+next:
+ tok = strtok_r(NULL, ",", &saveptr);
+ }
+ return NULL;
+}
+
/**
* Transmit a HTTP reply
*/
hc->hc_peer_ipstr, ticket_id, hc->hc_url);
}
+/**
+ *
+ */
+struct http_verify_structure {
+ char *password;
+ char *d_ha1;
+ char *d_all;
+ char *d_response;
+};
+
+static int
+http_verify_callback(void *aux, const char *passwd)
+{
+ struct http_verify_structure *v = aux;
+ char ha1[512];
+ char all[1024];
+ char *m;
+ int res;
+
+ if (v->d_ha1) {
+ snprintf(ha1, sizeof(ha1), "%s:%s", v->d_ha1, passwd);
+ m = md5sum(ha1, 1);
+ snprintf(all, sizeof(all), "%s:%s", m, v->d_all);
+ free(m);
+ m = md5sum(all, 1);
+ res = strcmp(m, v->d_response) == 0;
+ free(m);
+ return res;
+ }
+ if (v->password == NULL) return 0;
+ return strcmp(v->password, passwd) == 0;
+}
+
+/**
+ *
+ */
+static int
+http_verify_prepare(http_connection_t *hc, struct http_verify_structure *v)
+{
+ memset(v, 0, sizeof(*v));
+ if (hc->hc_authhdr) {
+
+ if (hc->hc_nonce == NULL)
+ return -1;
+
+ const char *method = http_cmd2str(hc->hc_cmd);
+ char *response = http_get_header_value(hc->hc_authhdr, "response");
+ char *qop = http_get_header_value(hc->hc_authhdr, "qop");
+ char *uri = http_get_header_value(hc->hc_authhdr, "uri");
+ char *realm = NULL, *nonce_count = NULL, *cnonce = NULL, *m = NULL;
+ char all[1024];
+ int res = -1;
+
+ if (qop == NULL || uri == NULL)
+ goto end;
+
+ if (strcasecmp(qop, "auth-int") == 0) {
+ m = md5sum(hc->hc_post_data ?: "", 1);
+ snprintf(all, sizeof(all), "%s:%s:%s", method, uri, m);
+ free(m);
+ m = NULL;
+ } else if (strcasecmp(qop, "auth") == 0) {
+ snprintf(all, sizeof(all), "%s:%s", method, uri);
+ } else {
+ goto end;
+ }
+
+ m = md5sum(all, 1);
+ if (qop == NULL || *qop == '\0') {
+ snprintf(all, sizeof(all), "%s:%s", hc->hc_nonce, m);
+ goto set;
+ } else {
+ realm = http_get_header_value(hc->hc_authhdr, "realm");
+ nonce_count = http_get_header_value(hc->hc_authhdr, "nc");
+ cnonce = http_get_header_value(hc->hc_authhdr, "cnonce");
+ if (realm == NULL || nonce_count == NULL || cnonce == NULL) {
+ goto end;
+ } else {
+ snprintf(all, sizeof(all), "%s:%s:%s:%s:%s",
+ hc->hc_nonce, nonce_count, cnonce, qop, m);
+set:
+ v->d_all = strdup(all);
+ snprintf(all, sizeof(all), "%s:%s", hc->hc_username, realm);
+ v->d_ha1 = strdup(all);
+ v->d_response = response;
+ response = NULL;
+ }
+ }
+ res = 0;
+end:
+ free(response);
+ free(qop);
+ free(uri);
+ free(realm);
+ free(nonce_count);
+ free(cnonce);
+ free(m);
+ return res;
+ } else {
+ v->password = hc->hc_password;
+ }
+ return 0;
+}
+
+/*
+ *
+ */
+static void
+http_verify_free(struct http_verify_structure *v)
+{
+ free(v->d_ha1);
+ free(v->d_all);
+ free(v->d_response);
+}
+
/**
* Return non-zero if no access
*/
int
http_access_verify(http_connection_t *hc, int mask)
{
+ struct http_verify_structure v;
int res = -1;
http_access_verify_ticket(hc);
if (res) {
access_destroy(hc->hc_access);
- hc->hc_access = access_get(hc->hc_username, hc->hc_password,
- (struct sockaddr *)hc->hc_peer);
+ if (http_verify_prepare(hc, &v))
+ return -1;
+ hc->hc_access = access_get((struct sockaddr *)hc->hc_peer, hc->hc_username,
+ http_verify_callback, &v);
+ http_verify_free(&v);
if (hc->hc_access)
res = access_verify2(hc->hc_access, mask);
}
http_access_verify_channel(http_connection_t *hc, int mask,
struct channel *ch)
{
+ struct http_verify_structure v;
int res = -1;
assert(ch);
if (res) {
access_destroy(hc->hc_access);
- hc->hc_access = access_get(hc->hc_username, hc->hc_password,
- (struct sockaddr *)hc->hc_peer);
+ if (http_verify_prepare(hc, &v))
+ return -1;
+ hc->hc_access = access_get((struct sockaddr *)hc->hc_peer, hc->hc_username,
+ http_verify_callback, &v);
+ http_verify_free(&v);
if (hc->hc_access) {
res = access_verify2(hc->hc_access, mask);
hc->hc_representative = hc->hc_peer_ipstr;
hc->hc_username = NULL;
hc->hc_password = NULL;
+ hc->hc_authhdr = NULL;
hc->hc_session = NULL;
/* Set keep-alive status */
/* Extract authorization */
if((v = http_arg_get(&hc->hc_args, "Authorization")) != NULL) {
if((n = http_tokenize(v, argv, 2, -1)) == 2) {
- n = base64_decode((uint8_t *)authbuf, argv[1], sizeof(authbuf) - 1);
- if (n < 0)
- n = 0;
- authbuf[n] = 0;
- if((n = http_tokenize(authbuf, argv, 2, ':')) == 2) {
- hc->hc_username = tvh_strdupa(argv[0]);
- hc->hc_password = tvh_strdupa(argv[1]);
- // No way to actually track this
+ if (strcasecmp(argv[0], "basic") == 0) {
+ n = base64_decode((uint8_t *)authbuf, argv[1], sizeof(authbuf) - 1);
+ if (n < 0)
+ n = 0;
+ authbuf[n] = 0;
+ if((n = http_tokenize(authbuf, argv, 2, ':')) == 2) {
+ hc->hc_username = tvh_strdupa(argv[0]);
+ hc->hc_password = tvh_strdupa(argv[1]);
+ // No way to actually track this
+ }
+ } else if (strcasecmp(argv[0], "digest") == 0) {
+ v = http_get_header_value(argv[1], "nonce");
+ if (v == NULL || http_find_nonce(v) == NULL) {
+ http_error(hc, HTTP_STATUS_UNAUTHORIZED);
+ free(v);
+ return -1;
+ }
+ if (hc->hc_nonce && strcmp(hc->hc_nonce, v) != 0) {
+ http_error(hc, HTTP_STATUS_UNAUTHORIZED);
+ free(v);
+ return -1;
+ }
+ free(hc->hc_nonce);
+ hc->hc_nonce = v;
+ v = http_get_header_value(argv[1], "username");
+ hc->hc_authhdr = tvh_strdupa(argv[1]);
+ hc->hc_username = tvh_strdupa(v);
+ free(v);
+ } else {
+ http_error(hc, HTTP_STATUS_BAD_REQUEST);
+ return -1;
}
}
}
free(hc->hc_post_data);
hc->hc_post_data = NULL;
-
+
http_arg_flush(&hc->hc_args);
http_arg_flush(&hc->hc_req_args);
error:
free(hdrline);
free(cmdline);
+
+ free(hc->hc_nonce);
+ hc->hc_nonce = NULL;
+
}
.stop = NULL,
.cancel = http_cancel
};
+ RB_INIT(&http_nonces);
http_server = tcp_server_create("http", "HTTP", bindaddr, tvheadend_webui_port, &ops, NULL);
atomic_set(&http_server_running, 1);
}
http_server_done(void)
{
http_path_t *hp;
+ http_nonce_t *n;
pthread_mutex_lock(&global_lock);
atomic_set(&http_server_running, 0);
free(hp);
}
pthread_mutex_unlock(&http_paths_mutex);
+ while ((n = RB_FIRST(&http_nonces)) != NULL) {
+ mtimer_disarm(&n->expire);
+ RB_REMOVE(&http_nonces, n, link);
+ free(n);
+ }
pthread_mutex_unlock(&global_lock);
}
char *hc_username;
char *hc_password;
+ char *hc_authhdr;
+ char *hc_nonce;
access_t *hc_access;
struct config_head *hc_user_config;
static inline int32_t sbuf_peek_s32be(sbuf_t *sb, int off) { return sbuf_peek_u32be(sb, off); }
static inline uint8_t *sbuf_peek(sbuf_t *sb, int off) { return sb->sb_data + off; }
-char *md5sum ( const char *str );
+char *md5sum ( const char *str, int lowercase );
int makedirs ( const char *subsys, const char *path, int mode, int mstrict, gid_t gid, uid_t uid );
}
char *
-md5sum ( const char *str )
+md5sum ( const char *str, int lowercase )
{
int i;
static unsigned char md5[MD5_DIGEST_LENGTH];
char *ret = malloc((MD5_DIGEST_LENGTH * 2) + 1);
MD5((const unsigned char*)str, strlen(str), md5);
- for ( i = 0; i < MD5_DIGEST_LENGTH; i++ ) {
- sprintf(&ret[i*2], "%02X", md5[i]);
- }
+ for (i = 0; i < MD5_DIGEST_LENGTH; i++)
+ sprintf(&ret[i*2], lowercase ? "%02x" : "%02X", md5[i]);
ret[MD5_DIGEST_LENGTH*2] = '\0';
return ret;
}