]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
http: implement digest authorisation per RFC2617
authorJaroslav Kysela <perex@perex.cz>
Fri, 8 Apr 2016 15:22:41 +0000 (17:22 +0200)
committerJaroslav Kysela <perex@perex.cz>
Fri, 8 Apr 2016 15:22:41 +0000 (17:22 +0200)
src/access.c
src/access.h
src/config.c
src/config.h
src/htsp_server.c
src/http.c
src/http.h
src/tvheadend.h
src/utils.c

index 005de7b11402870b9dfa09fa7c59fc3a36305edd..2f4d380dafb8daf3a99ce8cc2efc373027116ebd 100644 (file)
@@ -49,14 +49,9 @@ const char *superuser_password;
 
 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);
 
 /**
  *
@@ -358,59 +353,6 @@ access_ip_blocked(struct sockaddr *src)
   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);
-}
-
 /*
  *
  */
@@ -624,7 +566,6 @@ access_update(access_t *a, access_entry_t *ae)
 }
 
 /**
- *
  */
 static void
 access_set_lang_ui(access_t *a)
@@ -646,7 +587,7 @@ 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;
@@ -655,16 +596,16 @@ access_get(const char *username, const char *password, struct sockaddr *src)
   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;
@@ -708,72 +649,6 @@ access_get(const char *username, const char *password, struct sockaddr *src)
   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;
-}
-
 /**
  *
  */
@@ -1716,62 +1591,30 @@ const idclass_t access_entry_class = {
 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;
index a742232fc4494e307a86810f0f61de4f452a1c9b..4aabc2a4d3195a50a800b22f369c9e9e36d32d63 100644 (file)
@@ -239,9 +239,6 @@ access_get_theme(access_t *a);
  *
  * 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) :
@@ -252,15 +249,10 @@ int access_verify_list(htsmsg_t *list, const char *item);
 /**
  * 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);
 
 /**
  *
index 2daab011566536bbdafa0cecb1e7f5efdc575f30..6a5b5f7d43f7e13fb157e2b37b539458d1227bf3 100644 (file)
@@ -1631,6 +1631,8 @@ config_boot ( const char *path, gid_t gid, uid_t uid )
   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;
@@ -1751,6 +1753,7 @@ void config_done ( void )
   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);
@@ -2059,6 +2062,17 @@ const idclass_t config_class = {
       .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),
index e841e9867dfa538c651e6181beafda3c75a16cf4..c1c5afe210b550600e2c54363dc382b596cb2be5 100644 (file)
@@ -33,6 +33,8 @@ typedef struct config {
   int uilevel;
   int uilevel_nochange;
   int ui_quicktips;
+  int digest;
+  char *realm;
   char *wizard;
   char *full_version;
   char *server_name;
index a0e031368b42e9c66203c0098e8ab5e0650d090d..ae74a8deb35b632743a6f98557058b8aae26a3c7 100644 (file)
@@ -2906,12 +2906,32 @@ struct {
  * 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;
@@ -2923,8 +2943,10 @@ htsp_authenticate(htsp_connection_t *htsp, htsmsg_t *m)
 
   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);
index 2dcc944377c0811b9f650b9418e7360dac0ba12e..295c5ea32f545da3f89d5de5566a5a60d8513466 100644 (file)
@@ -30,6 +30,7 @@
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
+#include <openssl/md5.h>
 
 #include "tvheadend.h"
 #include "tcp.h"
@@ -218,6 +219,77 @@ static const char *cachemonths[12] = {
   "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
  */
@@ -276,8 +348,28 @@ http_send_header(http_connection_t *hc, int rc, const char *content,
     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) {
@@ -366,6 +458,41 @@ http_encoding_valid(http_connection_t *hc, const char *encoding)
   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
  */
@@ -548,12 +675,128 @@ http_access_verify_ticket(http_connection_t *hc)
         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);
@@ -562,8 +805,11 @@ http_access_verify(http_connection_t *hc, int mask)
 
   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);
   }
@@ -578,6 +824,7 @@ int
 http_access_verify_channel(http_connection_t *hc, int mask,
                            struct channel *ch)
 {
+  struct http_verify_structure v;
   int res = -1;
 
   assert(ch);
@@ -592,8 +839,11 @@ http_access_verify_channel(http_connection_t *hc, int mask,
 
   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);
 
@@ -799,6 +1049,7 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
   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 */
@@ -837,14 +1088,37 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
   /* 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;
       }
     }
   }
@@ -1159,7 +1433,7 @@ http_serve_requests(http_connection_t *hc)
 
     free(hc->hc_post_data);
     hc->hc_post_data = NULL;
-
+    
     http_arg_flush(&hc->hc_args);
     http_arg_flush(&hc->hc_req_args);
 
@@ -1175,6 +1449,10 @@ http_serve_requests(http_connection_t *hc)
 error:
   free(hdrline);
   free(cmdline);
+
+  free(hc->hc_nonce);
+  hc->hc_nonce = NULL;
+
 }
 
 
@@ -1230,6 +1508,7 @@ http_server_init(const char *bindaddr)
     .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);
 }
@@ -1244,6 +1523,7 @@ void
 http_server_done(void)
 {
   http_path_t *hp;
+  http_nonce_t *n;
 
   pthread_mutex_lock(&global_lock);
   atomic_set(&http_server_running, 0);
@@ -1257,5 +1537,10 @@ http_server_done(void)
     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);
 }
index aba1343cdeafc637afe3801cda894565c3944de9..2a8d9b78ce3448e524dda6970733fccab7c4066b 100644 (file)
@@ -149,6 +149,8 @@ typedef struct http_connection {
 
   char *hc_username;
   char *hc_password;
+  char *hc_authhdr;
+  char *hc_nonce;
   access_t *hc_access;
 
   struct config_head *hc_user_config;
index 1f3747e9832de8c769125e1cd54862ba32661188..62cd39862a266394d57dad5d1e96ce18f47c86bb 100644 (file)
@@ -803,7 +803,7 @@ uint32_t sbuf_peek_u32be(sbuf_t *sb, int off);
 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 );
 
index 3e558bdf207f7395571d5b598e5f773749e21995..c4d8c24adbf99b3cba53a402c66651194883885f 100644 (file)
@@ -468,15 +468,14 @@ sbuf_read(sbuf_t *sb, int fd)
 }
 
 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;
 }