From 97629deedd219cdbf68042571edb918c2ba65867 Mon Sep 17 00:00:00 2001 From: "E.Smith" <31170571+azlm8t@users.noreply.github.com> Date: Wed, 10 Jun 2020 18:45:04 +0100 Subject: [PATCH] Add HDHomeRun server support for LiveTV only (#4461) We now offer the ability to pretend to be an HDHomeRun server to expose channels for LiveTV. DVR functionality is not exposed. To use: - Add a user and enable persistent authentication via Config->User->Passwords->Persistent Authentication Enable. - Use this user for the HDHomeRun server setup in Config->General->Base->HDHomeRun/Local Username. Then manually add the device to your client using the IP address and port of the TVHeadend server, such as 192.168.0.1:9981 Auto-discovery of the TVHeadend server does not work. The persistent authentication allows the client to stream LiveTV from the TVHeadend server using the credentials for the given user. If no user is specified then functionality is disabled (unless authentication is completely disabled). --- src/config.c | 12 +++++ src/config.h | 1 + src/webui/webui.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/src/config.c b/src/config.c index 2293d278b..86a3b7aea 100644 --- a/src/config.c +++ b/src/config.c @@ -2507,6 +2507,18 @@ const idclass_t config_class = { .opts = PO_HIDDEN | PO_EXPERT, .group = 6 }, + { + .type = PT_STR, + .id = "hdhomerun_server_username", + .name = N_("Local username for HDHomeRun Server"), + .desc = N_("When TVheadend is acting as an HDHomeRun Server, " + "we use this user for determining permissions such " + "as channels. This user must have persistent " + "authentication enabled in the user password settings."), + .off = offsetof(config_t, hdhomerun_server_username), + .opts = PO_EXPERT, + .group = 6, + }, { .type = PT_STR, .id = "http_user_agent", diff --git a/src/config.h b/src/config.h index c81c20f75..2a26343f6 100644 --- a/src/config.h +++ b/src/config.h @@ -74,6 +74,7 @@ typedef struct config { char *hdhomerun_ip; char *local_ip; int local_port; + char *hdhomerun_server_username; } config_t; extern const idclass_t config_class; diff --git a/src/webui/webui.c b/src/webui/webui.c index 3e63aeacc..d08ebd7c7 100644 --- a/src/webui/webui.c +++ b/src/webui/webui.c @@ -1597,6 +1597,121 @@ page_play_auth(http_connection_t *hc, const char *remain, void *opaque) return page_play_(hc, remain, opaque, URLAUTH_CODE); } + +/** + * Return the discovery information for HDHomeRun to give clients + * details of how to access the lineup. + * + * Our HDHomeRun server implementation uses a server configured + * username to determine access rather than passing it through the + * request. This is because HDHomeRun clients are usually configured + * with only an IP address and port number and do not offer any input + * for credentials. + */ +static int +hdhomerun_server_discover(http_connection_t *hc, const char *remain, void *opaque) +{ + const char *hdhr_user = config.hdhomerun_server_username ?: ""; + access_t *perm = access_get_by_username(hdhr_user); + char http_ip[128]; + htsbuf_queue_t *hq; + + if (access_verify2(perm, ACCESS_STREAMING)) + return http_noaccess_code(hc); + + hq = &hc->hc_reply; + tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip)); + + /* The contents below for the discovery message are based on tvhProxy */ + htsbuf_qprintf(hq, "{ " \ + "\"BaseURL\" : \"http://%s:%u\", " \ + "\"DeviceAuth\": \"none\", " \ + "\"DeviceID\": \"12345678\", " \ + "\"FirmwareName\": \"hdhomerun_atsc\", " \ + "\"FirmwareVersion\": \"20200101\", " \ + "\"FriendlyName\": \"tvheadend\", " \ + "\"LineupURL\": \"http://%s:%u/lineup.json\", " \ + "\"Manufacturer\": \"Silicondust\", " \ + "\"ModelNumber\": \"HDTC-2US\", " \ + "\"TunerCount\": 6 " \ + "}", + http_ip, tvheadend_webui_port, http_ip, tvheadend_webui_port); + http_output_content(hc, "application/json"); + return 0; +} + +/** + * Dummy password verify callback for HDHomeRun. We need this to + * ensure we can get the aa_auth information for the user in to the + * access_t since it is not populated by an access_get_by_username. + * Since the user is configured solely on the server, the "passwd" is + * always valid. + */ +static int +hdhomerun_server_verify_callback(void *aux, const char *passwd) +{ + return 1; +} + +/** + * Return the channel lineup for HDHomeRun + */ +static int +hdhomerun_server_lineup(http_connection_t *hc, const char *remain, void *opaque) +{ + const char *hdhr_user = config.hdhomerun_server_username ?: ""; + access_t *perm = access_get(hc->hc_peer, hdhr_user, hdhomerun_server_verify_callback, NULL); + htsbuf_queue_t *hq; + channel_t *ch; + const char *name; + const char *blank; + const char *chnum_str; + const int use_auth = perm && perm->aa_auth && !strempty(perm->aa_auth); + char buf1[128], chnum[32], ubuf[UUID_HEX_SIZE]; + char url[1024]; + char http_ip[128]; + const int flags = + (config.chname_num ? CHANNEL_ENAME_NUMBERS : 0) | + (config.chname_src ? CHANNEL_ENAME_SOURCES : 0); + int is_first = 1; + + if (access_verify2(perm, ACCESS_STREAMING)) + return http_noaccess_code(hc); + + tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip)); + blank = tvh_gettext_lang(perm->aa_lang_ui, channel_blank_name); + hq = &hc->hc_reply; + htsbuf_append_str(hq, "["); + tvh_mutex_lock(&global_lock); + CHANNEL_FOREACH(ch) { + if (!channel_access(ch, perm, 0) || !ch->ch_enabled) + continue; + if (!is_first) + htsbuf_append_str(hq, ", \n"); + name = channel_get_ename(ch, buf1, sizeof(buf1), blank, flags); + htsbuf_append_str(hq, "{ \"GuideName\" : "); + htsbuf_append_and_escape_jsonstr(hq, name); + htsbuf_append_str(hq, ", \"GuideNumber\" : "); + /* channel_get_number_as_str returns NULL if no channel number! */ + chnum_str = channel_get_number_as_str(ch, chnum, sizeof(chnum)); + htsbuf_append_and_escape_jsonstr(hq, chnum_str ? chnum_str : "0"); + htsbuf_append_str(hq, ", \"URL\" : "); + sprintf(url, "http://%s:%u/stream/channel/%s?profile=pass%s%s", + http_ip, + tvheadend_webui_port, + channel_get_uuid(ch, ubuf), + use_auth? "&auth=" : "", + use_auth ? perm->aa_auth : ""); + htsbuf_append_and_escape_jsonstr(hq, url); + htsbuf_append_str(hq, "}"); + is_first = 0; + } + tvh_mutex_unlock(&global_lock); + htsbuf_append_str(hq, "]"); + http_output_content(hc, "application/json"); + return 0; +} + /** * */ @@ -2133,6 +2248,9 @@ webui_init(int xspf) #if CONFIG_SATIP_SERVER http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS); #endif + /* These names are specified in https://info.hdhomerun.com/info/http_api */ + http_path_add("/discover.json", NULL, hdhomerun_server_discover, ACCESS_ANONYMOUS); + http_path_add("/lineup.json", NULL, hdhomerun_server_lineup, ACCESS_ANONYMOUS); http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify5); http_path_add_modify("/play/ticket", NULL, page_play_ticket, ACCESS_ANONYMOUS, page_play_path_modify12); -- 2.25.1