* IPTV - HTTP/HTTPS handler
*
* Copyright (C) 2013 Adam Sutton
+ * Copyright (C) 2014,2015 Jaroslav Kysela
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#include "iptv_private.h"
#include "http.h"
#include "misc/m3u.h"
+#include "openssl/aes.h"
+
+#if AES_BLOCK_SIZE != 16
+#error "Wrong openssl!"
+#endif
typedef struct http_priv {
iptv_mux_t *im;
http_client_t *hc;
uint8_t started;
sbuf_t m3u_sbuf;
+ sbuf_t key_sbuf;
int m3u_header;
uint64_t off;
char *host_url;
int64_t hls_seq;
char *hls_url;
uint8_t hls_url2;
+ uint8_t hls_encrypted;
+ char *hls_url_after_key;
+ char *hls_key_url;
htsmsg_t *hls_m3u;
+ htsmsg_t *hls_key;
uint8_t *hls_si;
time_t hls_last_si;
+ struct {
+ char tmp[AES_BLOCK_SIZE];
+ int tmp_len;
+ AES_KEY key;
+ unsigned char iv[AES_BLOCK_SIZE];
+ } hls_aes128;
} http_priv_t;
+/***/
+
+static int iptv_http_complete_key ( http_client_t *hc );
+
/*
*
*/
static char *
iptv_http_get_url( http_priv_t *hp, htsmsg_t *m )
{
- htsmsg_t *items, *item, *inf, *sel = NULL;
+ htsmsg_t *items, *item, *inf, *sel = NULL, *key;
htsmsg_field_t *f;
int64_t seq, bandwidth, sel_bandwidth = 0;
int width, height, sel_width = 0, sel_height = 0;
} else {
s = htsmsg_get_str(item, "m3u-url");
if (s && s[0]) {
+ key = htsmsg_get_map(item, "x-key");
+ if (key && strcmp(htsmsg_get_str(key, "METHOD") ?: "NONE", "NONE") != 0)
+ key = htsmsg_copy(key);
+ else
+ key = NULL;
s = strdup(s);
htsmsg_field_destroy(items, f);
if (hp->hls_url) {
hp->hls_url2 = 1;
hp->hls_m3u = m;
+ htsmsg_destroy(hp->hls_key);
+ hp->hls_key = key;
hp->hls_seq++;
}
return (char *)s;
return NULL;
}
+/*
+ *
+ */
+static void
+iptv_http_data_aes128 ( http_priv_t *hp, sbuf_t *sb, int off )
+{
+ unsigned char *in = sb->sb_data + off;
+ unsigned char *out = in;
+ size_t size = sb->sb_ptr - off;
+
+ AES_cbc_encrypt(in, out, size, &hp->hls_aes128.key, hp->hls_aes128.iv, AES_DECRYPT);
+}
/*
* Connected
}
hp->m3u_header++;
sbuf_reset(&hp->m3u_sbuf, 8192);
+ sbuf_reset(&hp->key_sbuf, 32);
+ hp->hls_encrypted = 0;
return 0;
}
}
{
http_priv_t *hp = hc->hc_aux;
iptv_mux_t *im;
- int pause = 0, rem;
+ int pause = 0, off, rem;
+ uint8_t tsbuf[188];
if (hp == NULL || hp->im == NULL || hc->hc_code != HTTP_STATUS_OK)
return 0;
sbuf_append(&hp->m3u_sbuf, buf, len);
return 0;
}
+ if (hc->hc_data_complete == iptv_http_complete_key) {
+ sbuf_append(&hp->key_sbuf, buf, len);
+ return 0;
+ }
+
+ pthread_mutex_lock(&iptv_lock);
- if (hp->off == 0) {
- tvhtrace("iptv", "hexdump");
- tvhlog_hexdump("iptv", buf, MAX(len, 4096));
- tvhtrace("iptv", "hexdump end");
+ if (hp->hls_encrypted) {
+ off = im->mm_iptv_buffer.sb_ptr;
+ if (hp->hls_aes128.tmp_len + len >= AES_BLOCK_SIZE) {
+ if (hp->hls_aes128.tmp_len) {
+ sbuf_append(&im->mm_iptv_buffer, hp->hls_aes128.tmp, hp->hls_aes128.tmp_len);
+ rem = AES_BLOCK_SIZE - hp->hls_aes128.tmp_len;
+ hp->hls_aes128.tmp_len = 0;
+ sbuf_append(&im->mm_iptv_buffer, buf, rem);
+ len -= rem;
+ buf += rem;
+ }
+ rem = len % AES_BLOCK_SIZE;
+ sbuf_append(&im->mm_iptv_buffer, buf, len - rem);
+ buf += len - rem;
+ len = rem;
+ }
+ memcpy(hp->hls_aes128.tmp + hp->hls_aes128.tmp_len, buf, len);
+ hp->hls_aes128.tmp_len += len;
+ if (off == im->mm_iptv_buffer.sb_ptr)
+ return 0;
+ buf = im->mm_iptv_buffer.sb_data + im->mm_iptv_buffer.sb_ptr;
+ len = im->mm_iptv_buffer.sb_ptr - off;
+ assert((len % 16) == 0);
+ iptv_http_data_aes128(hp, &im->mm_iptv_buffer, off);
+ } else {
+ sbuf_append(&im->mm_iptv_buffer, buf, len);
}
+ tsdebug_write((mpegts_mux_t *)im, buf, len);
+ hp->off += len;
- if (hp->hls_url && hp->off < 2*188 && len >= 2*188) {
+ if (hp->hls_url && hp->off == 0 && len >= 2*188) {
free(hp->hls_si);
hp->hls_si = malloc(2*188);
memcpy(hp->hls_si, buf, 2*188);
}
- pthread_mutex_lock(&iptv_lock);
-
if (dispatch_clock != hp->hls_last_si && hp->hls_si) {
- /* do rounding to next MPEG-TS packet */
+ /* do rounding to start of the last MPEG-TS packet */
rem = 188 - (hp->off % 188);
- if (rem < 188) {
- if (len < rem)
- goto next;
- sbuf_append(&im->mm_iptv_buffer, buf, rem);
- buf += rem;
- len -= rem;
+ if (im->mm_iptv_buffer.sb_ptr >= rem) {
+ im->mm_iptv_buffer.sb_ptr -= rem;
+ memcpy(tsbuf, im->mm_iptv_buffer.sb_data + im->mm_iptv_buffer.sb_ptr, rem);
+ sbuf_append(&im->mm_iptv_buffer, hp->hls_si, 2*188);
+ hp->hls_last_si = dispatch_clock;
+ sbuf_append(&im->mm_iptv_buffer, tsbuf, rem);
hp->off += rem;
}
- sbuf_append(&im->mm_iptv_buffer, hp->hls_si, 2*188);
- hp->hls_last_si = dispatch_clock;
}
-next:
- hp->off += len;
- sbuf_append(&im->mm_iptv_buffer, buf, len);
- tsdebug_write((mpegts_mux_t *)im, buf, len);
-
if (len > 0)
if (iptv_input_recv_packets(im, len) == 1)
pause = hc->hc_pause = 1;
/*
* Complete data
*/
+static void
+iptv_http_reconnect ( http_client_t *hc, const char *url )
+{
+ url_t u;
+ int r;
+
+ urlinit(&u);
+ if (!urlparse(url, &u)) {
+ hc->hc_keepalive = 0;
+ r = http_client_simple_reconnect(hc, &u, HTTP_VERSION_1_1);
+ if (r < 0)
+ tvherror("iptv", "cannot reopen http client: %d'", r);
+ } else {
+ tvherror("iptv", "m3u url invalid '%s'", url);
+ }
+ urlreset(&u);
+}
+
static int
iptv_http_complete
( http_client_t *hc )
{
http_priv_t *hp = hc->hc_aux;
+ const char *s;
char *url;
htsmsg_t *m, *m2;
- url_t u;
int r;
if (hp == NULL || hp->im == NULL)
url = iptv_http_get_url(hp, m);
if (hp->hls_m3u == m)
m = NULL;
+ if (hp->hls_key) {
+ s = htsmsg_get_str(hp->hls_key, "METHOD");
+ if (s == NULL || strcmp(s, "AES-128")) {
+ tvherror("iptv", "unknown crypto method '%s'", s);
+ goto end;
+ }
+ memset(&hp->hls_aes128.iv, 0, sizeof(hp->hls_aes128.iv));
+ s = htsmsg_get_str(hp->hls_key, "IV");
+ if (s != NULL) {
+ if (s[0] != '0' || s[1] != 'x' || strlen(s) != (AES_BLOCK_SIZE * 2) + 2) {
+ tvherror("iptv", "unknown IV type or length (%s)", s);
+ goto end;
+ }
+ hex2bin(hp->hls_aes128.iv, sizeof(hp->hls_aes128.iv), s + 2);
+ }
+ s = htsmsg_get_str(hp->hls_key, "URI");
+ if (s == NULL) {
+ tvherror("iptv", "no URI in KEY attribute");
+ goto end;
+ }
+ hp->hls_encrypted = 1;
+ if (strcmp(hp->hls_key_url ?: "", s)) {
+ free(hp->hls_key_url);
+ hp->hls_key_url = strdup(s);
+ free(hp->hls_url_after_key);
+ hp->hls_url_after_key = url;
+ url = strdup(s);
+ hc->hc_data_complete = iptv_http_complete_key;
+ sbuf_reset(&hp->key_sbuf, 32);
+ }
+ }
tvhtrace("iptv", "m3u url: '%s'", url);
if (url == NULL) {
tvherror("iptv", "m3u contents parsing failed");
goto fin;
}
new_m3u:
- urlinit(&u);
- if (!urlparse(url, &u)) {
- hc->hc_keepalive = 0;
- r = http_client_simple_reconnect(hc, &u, HTTP_VERSION_1_1);
- if (r < 0)
- tvherror("iptv", "cannot reopen http client: %d'", r);
- } else {
- tvherror("iptv", "m3u url invalid '%s'", url);
- }
+ iptv_http_reconnect(hc, url);
+end:
free(url);
- urlreset(&u);
fin:
htsmsg_destroy(m);
} else {
return 0;
}
+static int
+iptv_http_complete_key
+ ( http_client_t *hc )
+{
+ http_priv_t *hp = hc->hc_aux;
+
+ if (hp == NULL)
+ return 0;
+ tvhtrace("iptv", "received key len %d", hp->key_sbuf.sb_ptr);
+ if (hp->key_sbuf.sb_ptr != AES_BLOCK_SIZE) {
+ tvherror("iptv", "AES-128 key wrong length (%d)\n", hp->key_sbuf.sb_ptr);
+ return 0;
+ }
+ AES_set_decrypt_key(hp->key_sbuf.sb_data, 128, &hp->hls_aes128.key);
+ hc->hc_data_complete = iptv_http_complete;
+ iptv_http_reconnect(hc, hp->hls_url_after_key);
+ free(hp->hls_url_after_key);
+ hp->hls_url_after_key = NULL;
+ htsmsg_destroy(hp->hls_key);
+ hp->hls_key = NULL;
+ return 0;
+}
+
/*
* Custom headers
*/
hp->hc = hc;
im->im_data = hp;
sbuf_init(&hp->m3u_sbuf);
+ sbuf_init(&hp->key_sbuf);
sbuf_init_fixed(&im->mm_iptv_buffer, IPTV_BUF_SIZE);
http_client_register(hc); /* register to the HTTP thread */
r = http_client_simple(hc, u);
pthread_mutex_lock(&iptv_lock);
im->im_data = NULL;
sbuf_free(&hp->m3u_sbuf);
+ sbuf_free(&hp->key_sbuf);
htsmsg_destroy(hp->hls_m3u);
+ htsmsg_destroy(hp->hls_key);
free(hp->hls_url);
+ free(hp->hls_url_after_key);
+ free(hp->hls_key_url);
free(hp->hls_si);
free(hp->host_url);
free(hp);