Project

General

Profile

Feature #4461 » 0001-Add-HDHomeRun-server-support-for-LiveTV-only-4461.patch

Em Smith, 2020-06-14 00:50

View differences:

configure
28 28
  "satip_server:yes"
29 29
  "satip_client:yes"
30 30
  "hdhomerun_client:no"
31
  "hdhomerun_server:yes"
31 32
  "hdhomerun_static:yes"
32 33
  "iptv:yes"
33 34
  "tsfile:yes"
docs/property/config_hdhomerun_server_username.md
1
TVHeadend can pretend to be an HDHomeRun device.
2
This is only available if TVHeadend has been compiled
3
with the feature enabled.
4

  
5
This is currently an experimental feature.  It may
6
be withdrawn in the future.
7

  
8
This allows some media players to be able to access
9
Live TV channels.  Some other media players use a
10
different technique to access HDHomeRun so will not
11
be able to play Live TV channels.
12

  
13
On the media player, autodetect of devices will not work.
14
Instead, you need to enter the IP address and port
15
number of the TVHeadend web interface.  For example
16
```1.2.3.4:9981```.  If the media player does not
17
support manually entering details then it is not
18
supported.
19

  
20
Typically the media player will then ask for xmltv
21
details to populate the TV Guide.  This can normally
22
be retrieved from TVHeadend via the web interface.
23
For example ```http://1.2.3.4:9981/xmltv/channels```.
src/config.c
2085 2085
PROP_DOC(config_channelname_scheme)
2086 2086
PROP_DOC(config_picon_path)
2087 2087
PROP_DOC(config_picon_servicetype)
2088
PROP_DOC(config_hdhomerun_server_username)
2088 2089
PROP_DOC(viewlevel_config)
2089 2090
PROP_DOC(themes)
2090 2091

  
......
2507 2508
      .opts   = PO_HIDDEN | PO_EXPERT,
2508 2509
      .group  = 6
2509 2510
    },
2511
    {
2512
      .type   = PT_STR,
2513
      .id     = "hdhomerun_server_username",
2514
      .name   = N_("TVHeadend username for HDHomeRun Server Emulation"),
2515
      .desc   = N_("When TVheadend is acting as an HDHomeRun Server "
2516
                   "(emulating an HDHomeRun device for downstream "
2517
                   "media devices to stream Live TV) then "
2518
                   "we use this user for determining permissions.  "
2519
                   "This user must have basic streaming permissions.  "
2520
                   "This user must also have persistent "
2521
                   "authentication enabled in the user password settings.  "
2522
                   "It is strongly recommended that IP Blocking is used to "
2523
                   "prevent access from outside your network."
2524
                  ),
2525
      .doc    = prop_doc_config_hdhomerun_server_username,
2526
      .off    = offsetof(config_t, hdhomerun_server_username),
2527
      .opts   = PO_EXPERT
2528
#if !ENABLE_HDHOMERUN_SERVER
2529
      | PO_PHIDDEN
2530
#endif
2531
      ,
2532
      .group  = 6,
2533
    },
2534
    {
2535
      .type   = PT_BOOL,
2536
      .id     = "hdhomerun_server_enable",
2537
      .name   = N_("Enable HDHomeRun Server Emulation"),
2538
      .desc   = N_("Enable the TVHeadend server to emulate "
2539
                   "an HDHomeRun server.  This allows LiveTV "
2540
                   "to be used on some media servers."
2541
                  ),
2542
      .off    = offsetof(config_t, hdhomerun_server_enable),
2543
      .opts   = PO_EXPERT
2544
#if !ENABLE_HDHOMERUN_SERVER
2545
      | PO_PHIDDEN
2546
#endif
2547
,
2548
      .group  = 6
2549
    },
2510 2550
    {
2511 2551
      .type   = PT_STR,
2512 2552
      .id     = "http_user_agent",
src/config.h
74 74
  char *hdhomerun_ip;
75 75
  char *local_ip;
76 76
  int local_port;
77
  char *hdhomerun_server_username;
78
  int hdhomerun_server_enable;
77 79
} config_t;
78 80

  
79 81
extern const idclass_t config_class;
src/htsbuf.c
539 539

  
540 540

  
541 541

  
542
void htsbuf_append_and_escape_jsonstr_nv(htsbuf_queue_t *hq, const char *name, const char *value)
543
{
544
  htsbuf_append_and_escape_jsonstr(hq, name);
545
  htsbuf_append_str(hq, ": ");
546
  htsbuf_append_and_escape_jsonstr(hq, value);
547
}
548

  
549

  
550

  
542 551
/**
543 552
 *
544 553
 */
src/htsbuf.h
82 82
void htsbuf_append_and_escape_rfc8187(htsbuf_queue_t *hq, const char *s);
83 83

  
84 84
void htsbuf_append_and_escape_jsonstr(htsbuf_queue_t *hq, const char *s);
85
/** Append a name and value JSON string */
86
void htsbuf_append_and_escape_jsonstr_nv(htsbuf_queue_t *hq, const char *name, const char *value);
85 87

  
86 88
void htsbuf_dump_raw_stderr(htsbuf_queue_t *hq);
87 89

  
src/webui/webui.c
1597 1597
  return page_play_(hc, remain, opaque, URLAUTH_CODE);
1598 1598
}
1599 1599

  
1600

  
1601

  
1602
#if ENABLE_HDHOMERUN_SERVER
1603
/**
1604
 * Dummy password verify callback for HDHomeRun.  We need this to
1605
 * ensure we can get the aa_auth information for the user in to the
1606
 * access_t since it is not populated by an access_get_by_username.
1607
 * Since the user is configured solely on the server, the "passwd" is
1608
 * always valid.
1609
 *
1610
 * We have the username configured on the server (not the client)
1611
 * since HDHomeRun clients usually do not allow entry of credentials.
1612
 *
1613
 * This is called via the "access_get" call which does IP checking
1614
 * (configured via the Users/IP Blocking Records tab in expert
1615
 * setting).
1616
 */
1617
static int
1618
hdhomerun_server_verify_callback(void *aux, const char *passwd)
1619
{
1620
  return 1;
1621
}
1622

  
1623

  
1624
static const char *hdhomerun_get_server_name(void)
1625
{
1626
  return config.server_name ?: "TVHeadend";
1627
}
1628

  
1629
/**
1630
 * Our unique device id is calculated from our server's name
1631
 * in the general config tab.
1632
 */
1633
static uint32_t hdhomerun_get_deviceid(void)
1634
{
1635
  const char *server_name = hdhomerun_get_server_name();
1636
  const uint32_t deviceid = tvh_crc32((const uint8_t*)server_name, strlen(server_name), 0);
1637
  return deviceid;
1638
}
1639

  
1640

  
1641
/**
1642
 * @param fail_log_reason Log this reason if permissions fail.
1643
 * @return Permission for the verified user (caller owns the memory) or NULL.
1644
 */
1645
__attribute__((warn_unused_result))
1646
static access_t *hdhomerun_verify_user_permission(const http_connection_t *hc,
1647
                                                  const char *fail_log_reason)
1648
{
1649
  /* Not explicitly enabled?  Then all calls fail. */
1650
  if (!config.hdhomerun_server_enable) {
1651
    tvhwarn(LS_WEBUI, "hdhomerun server not enabled but received request [%s]",
1652
            fail_log_reason?:"");
1653
    return NULL;
1654
  }
1655

  
1656
  const char *hdhr_user = config.hdhomerun_server_username ?: "";
1657
  access_t *perm = access_get(hc->hc_peer, hdhr_user, hdhomerun_server_verify_callback, NULL);
1658

  
1659
  if (access_verify2(perm, ACCESS_STREAMING)) {
1660
    /* Failed */
1661
    tvhwarn(LS_WEBUI, "hdhomerun server received request but no streaming permission for user [%s] [%d] [%s]",
1662
            hdhr_user ?: "<none>",
1663
            perm? perm->aa_rights : 0,
1664
            fail_log_reason?:"");
1665
    access_destroy(perm);
1666
    return NULL;
1667
  } else {
1668
    return perm;
1669
  }
1670
}
1671

  
1672

  
1673
/**
1674
 * Return the discovery information for HDHomeRun to give clients
1675
 * details of how to access the lineup.
1676
 *
1677
 * Our HDHomeRun server implementation uses a server configured
1678
 * username to determine access rather than passing it through the
1679
 * request.  This is because HDHomeRun clients are usually configured
1680
 * with only an IP address and port number and do not offer any input
1681
 * for credentials.
1682
 */
1683
static int
1684
hdhomerun_server_discover(http_connection_t *hc, const char *remain, void *opaque)
1685
{
1686
  access_t *perm = hdhomerun_verify_user_permission(hc, "discover");
1687
  if (!perm)
1688
    return http_noaccess_code(hc);
1689

  
1690
  char http_ip[128];
1691
  htsbuf_queue_t *hq = &hc->hc_reply;
1692
  const char *server_name = hdhomerun_get_server_name();
1693
  const uint32_t deviceid = hdhomerun_get_deviceid();
1694

  
1695
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1696

  
1697
  /* The contents below for the discovery message are based on tvhProxy */
1698
  htsbuf_append_str(hq, "{ ");
1699
  htsbuf_append_and_escape_jsonstr_nv(hq, "FriendlyName", server_name);
1700
  htsbuf_qprintf(hq, ", "                                               \
1701
                 "\"BaseURL\" : \"http://%s:%u\", "                     \
1702
                 "\"DeviceAuth\": \"3xw5UaJXhVShHEBoy76FuYQi\", "       \
1703
                 "\"DeviceID\": \"%08X\", "                             \
1704
                 "\"FirmwareName\": \"hdhomerun_atsc\", "               \
1705
                 "\"FirmwareVersion\": \"20200101\", "                  \
1706
                 "\"LineupURL\": \"http://%s:%u/lineup.json\", "        \
1707
                 "\"Manufacturer\": \"Silicondust\", "                  \
1708
                 "\"ModelNumber\": \"HDTC-2US\", "                      \
1709
                 "\"TunerCount\": 6 "                                   \
1710
                 "}",
1711
                 http_ip, tvheadend_webui_port,
1712
                 deviceid,
1713
                 http_ip, tvheadend_webui_port);
1714
  http_output_content(hc, "application/json");
1715
  access_destroy(perm);
1716
  return 0;
1717
}
1718

  
1719

  
1720
/**
1721
 * Return the channel lineup for HDHomeRun
1722
 */
1723
static int
1724
hdhomerun_server_lineup(http_connection_t *hc, const char *remain, void *opaque)
1725
{
1726
  access_t *perm = hdhomerun_verify_user_permission(hc, "lineup");
1727
  if (!perm)
1728
    return http_noaccess_code(hc);
1729

  
1730
  htsbuf_queue_t *hq = &hc->hc_reply;
1731
  channel_t *ch;
1732
  const char *name;
1733
  const char *blank;
1734
  const char *chnum_str;
1735
  const int use_auth = perm && perm->aa_auth && !strempty(perm->aa_auth);
1736
  char buf1[128], chnum[32], ubuf[UUID_HEX_SIZE];
1737
  char url[1024];
1738
  char http_ip[128];
1739
  /* We use the UI flags to determine if we should include channel
1740
   * numbers/sources in the name.  This can help distinguish channels
1741
   * when you have multiple different sources of the same channel such
1742
   * as satellite and aerial.
1743
   */
1744
  const int flags =
1745
    (config.chname_num ? CHANNEL_ENAME_NUMBERS : 0) |
1746
    (config.chname_src ? CHANNEL_ENAME_SOURCES : 0);
1747
  int is_first = 1;
1748

  
1749
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1750
  blank = tvh_gettext_lang(perm->aa_lang_ui, channel_blank_name);
1751
  htsbuf_append_str(hq, "[");
1752
  tvh_mutex_lock(&global_lock);
1753
  CHANNEL_FOREACH(ch) {
1754
    if (!channel_access(ch, perm, 0) || !ch->ch_enabled)
1755
      continue;
1756
    if (!is_first)
1757
      htsbuf_append_str(hq, ", \n");
1758
    name = channel_get_ename(ch, buf1, sizeof(buf1), blank, flags);
1759
    htsbuf_append_str(hq, "{ \"GuideName\" : ");
1760
    htsbuf_append_and_escape_jsonstr(hq, name);
1761
    htsbuf_append_str(hq, ", \"GuideNumber\" : ");
1762
    /* channel_get_number_as_str returns NULL if no channel number! */
1763
    chnum_str = channel_get_number_as_str(ch, chnum, sizeof(chnum));
1764
    htsbuf_append_and_escape_jsonstr(hq, chnum_str ? chnum_str : "0");
1765
    htsbuf_append_str(hq, ", \"URL\" : ");
1766
    sprintf(url, "http://%s:%u/stream/channel/%s?profile=pass%s%s",
1767
            http_ip,
1768
            tvheadend_webui_port,
1769
            channel_get_uuid(ch, ubuf),
1770
            use_auth? "&auth=" : "",
1771
            use_auth ? perm->aa_auth : "");
1772
    htsbuf_append_and_escape_jsonstr(hq, url);
1773
    htsbuf_append_str(hq, "}");
1774
    is_first = 0;
1775
  }
1776
  tvh_mutex_unlock(&global_lock);
1777
  htsbuf_append_str(hq, "]");
1778
  http_output_content(hc, "application/json");
1779
  access_destroy(perm);
1780
  return 0;
1781
}
1782

  
1783

  
1784
static int
1785
hdhomerun_server_lineup_status(http_connection_t *hc, const char *remain, void *opaque)
1786
{
1787
  access_t *perm = hdhomerun_verify_user_permission(hc, "lineup_status");
1788
  if (!perm)
1789
    return http_noaccess_code(hc);
1790

  
1791
  htsbuf_queue_t *hq = &hc->hc_reply;
1792
  /* The contents below for the discovery message are based on the forum. */
1793
  htsbuf_append_str(hq, "{\"ScanInProgress\":0,\"ScanPossible\":0,\"Source\":\"Antenna\",\"SourceList\":[\"Antenna\"]}");
1794
  http_output_content(hc, "application/json");
1795
  access_destroy(perm);
1796
  return 0;
1797
}
1798

  
1799

  
1800

  
1801
/**
1802
 * Needed for some clients.  This contains much the same as discover,
1803
 * but in xml format.
1804
 */
1805
static int
1806
hdhomerun_server_device_xml(http_connection_t *hc, const char *remain, void *opaque)
1807
{
1808
  access_t *perm = hdhomerun_verify_user_permission(hc, "device.xml");
1809
  if (!perm)
1810
    return http_noaccess_code(hc);
1811

  
1812
  const char *server_name = hdhomerun_get_server_name();
1813
  char http_ip[128];
1814
  htsbuf_queue_t *hq = &hc->hc_reply;
1815
  const uint32_t deviceid = hdhomerun_get_deviceid();
1816

  
1817
  tcp_get_str_from_ip(hc->hc_self, http_ip, sizeof(http_ip));
1818
  htsbuf_qprintf(hq, "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
1819
                 "<specVersion>"
1820
                 "<major>1</major>"
1821
                 "<minor>0</minor>"
1822
                 "</specVersion>"
1823
                 "<URLBase>http://%s:%u</URLBase>"
1824
                 "<device>"
1825
                 "<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>"
1826
                 "<friendlyName>%s</friendlyName>"
1827
                 "<manufacturer>Silicondust</manufacturer>"
1828
                 "<modelName>HDTC-2US</modelName>"
1829
                 "<modelNumber>HDTC-2US</modelNumber>"
1830
                 "<serialNumber></serialNumber>"
1831
                 /* Version 5 UUID (random) with top part as server id*/
1832
                 "<UDN>uuid:%8.8x-745e-5d9a-8903-4a02327a7e09</UDN>"
1833
                 "</device>"
1834
                 "</root>",
1835
                 http_ip, tvheadend_webui_port,
1836
                 server_name,
1837
                 deviceid);
1838

  
1839
  http_output_content(hc, "application/xml");
1840
  access_destroy(perm);
1841
  return 0;
1842
}
1843
#endif  /* ENABLE_HDHOMERUN_SERVER */
1844

  
1600 1845
/**
1601 1846
 *
1602 1847
 */
......
2134 2379
  http_path_add("/satip_server", NULL, satip_server_http_page, ACCESS_ANONYMOUS);
2135 2380
#endif
2136 2381

  
2382
#if ENABLE_HDHOMERUN_SERVER
2383
  /* These names are specified in https://info.hdhomerun.com/info/http_api */
2384
  http_path_add("/discover.json", NULL, hdhomerun_server_discover, ACCESS_ANONYMOUS);
2385
  http_path_add("/lineup.json", NULL, hdhomerun_server_lineup, ACCESS_ANONYMOUS);
2386
  /* These names are not specified in the documents but are required to make Plex work. */
2387
  http_path_add("/lineup_status.json", NULL, hdhomerun_server_lineup_status, ACCESS_ANONYMOUS);
2388
  http_path_add("/device.xml", NULL, hdhomerun_server_device_xml, ACCESS_ANONYMOUS);
2389
#endif
2390

  
2137 2391
  http_path_add_modify("/play", NULL, page_play, ACCESS_ANONYMOUS, page_play_path_modify5);
2138 2392
  http_path_add_modify("/play/ticket", NULL, page_play_ticket, ACCESS_ANONYMOUS, page_play_path_modify12);
2139 2393
  http_path_add_modify("/play/auth", NULL, page_play_auth, ACCESS_ANONYMOUS, page_play_path_modify10);
2140
- 
(14-14/19)