add suppport for user access control by -pw password (HTML digest)

This commit is contained in:
F. Duncanh
2025-04-22 17:13:13 -04:00
parent 2f809eeadd
commit be4fb423c4
17 changed files with 566 additions and 81 deletions

View File

@@ -1,24 +1,26 @@
<h1 <h1
id="uxplay-1.71-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay id="uxplay-1.72-beta-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix 1.72 (beta): AirPlay-Mirror and AirPlay-Audio server for Linux, macOS,
(now also runs on Windows).</h1> and Unix (now also runs on Windows).</h1>
<h3 <h3
id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found."><strong>Now id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-issues-should-be-posted-and-latest-versions-can-be-found."><strong>Now
developed at the GitHub site <a href="https://github.com/FDH2/UxPlay" developed at the GitHub site <a href="https://github.com/FDH2/UxPlay"
class="uri">https://github.com/FDH2/UxPlay</a> (where ALL user issues class="uri">https://github.com/FDH2/UxPlay</a> (where ALL user issues
should be posted, and latest versions can be found).</strong></h3> should be posted, and latest versions can be found).</strong></h3>
<ul> <ul>
<li><em><strong>NEW in v1.71</strong>: Support for (YouTube) HLS (HTTP <li><p><em><strong>NEW in v1.72</strong>: Improved Support for (YouTube)
Live Streaming) video with the new “-hls” option.</em> <strong>Only HLS (HTTP Live Streaming) video with the new “-hls” option.</em>
streaming from the YouTube iOS app (in "m3u8" protocol) is currently <strong>Only streaming from the YouTube iOS app (in "m3u8" protocol) is
supported</strong>: (streaming using the AirPlay icon in a browser currently supported</strong>: (streaming using the AirPlay icon in a
window is <strong>not</strong> yet supported).Click on the airplay icon browser window is <strong>not</strong> yet supported).Click on the
in the YouTube app to stream video. (You may need to wait until airplay icon in the YouTube app to stream video. <strong>Please report
advertisements have finished or been skipped before clicking the YouTube any issues with this new feature of UxPlay</strong>.</p>
airplay icon.) <strong>Please report any issues with this new feature of <p><em>The default video player for HLS is GStreamer playbin v3: use
UxPlay</strong>. <em>The default video player for HLS is GStreamer “-hls 2” to revert to playbin v2 if some videos fail to play</em>.</p>
playbin v3: use “-hls 2” to revert to playbin v2 if some videos fail to <ul>
play</em>.</li> <li>added support for setting a password (as an alternative to on-screen
pin codes) to control client access (-pw option)</li>
</ul></li>
</ul> </ul>
<h2 id="highlights">Highlights:</h2> <h2 id="highlights">Highlights:</h2>
<ul> <ul>
@@ -507,8 +509,12 @@ option: see “<code>-pin</code>” and “<code>-reg</code>” in <a
href="#usage">Usage</a> for details, if you wish to use it. <em>Some href="#usage">Usage</a> for details, if you wish to use it. <em>Some
clients with MDM (Mobile Device Management, often present on clients with MDM (Mobile Device Management, often present on
employer-owned devices) are required to use pin-authentication: UxPlay employer-owned devices) are required to use pin-authentication: UxPlay
will provide this even when running without the pin will provide this even when running without the pin option.</em>
option.</em></p></li> Password authentication (-pw <em>pwd</em>)is also offered as an
alternative solution to pin codes: users need to know the password
<em>pwd</em> and enter it on their iOS/macOS device to access UxPlay,
when prompted (if <em>pwd</em> is not set, a displayed random pin code
must be entered at <strong>each</strong> new connection.)</p></li>
<li><p>By default, UxPlay is locked to its current client until that <li><p>By default, UxPlay is locked to its current client until that
client drops the connection; since UxPlay-1.58, the option client drops the connection; since UxPlay-1.58, the option
<code>-nohold</code> modifies this behavior so that when a new client <code>-nohold</code> modifies this behavior so that when a new client
@@ -999,6 +1005,17 @@ key (base-64 format), Device ID, and Device name; commenting out (with
options -restrict, -block, -allow for more ways to control client options -restrict, -block, -allow for more ways to control client
access). <em>(Add a line “reg” in the startup file if you wish to use access). <em>(Add a line “reg” in the startup file if you wish to use
this feature.)</em></p> this feature.)</em></p>
<p><strong>-pw</strong> [<em>pwd</em>]. (since 1.72). As an alternative
to -pin, client access can be controlled with a password set when uxplay
starts (set it in the .uxplay startup file, where it is stored as
cleartext.) All users must then know this password. This uses HTTP md5
Digest authentication, which is now regarded as providing weak security,
but it is only used to validate the uxplay password, and no user
credentials are exposed. _Note: -pin and -pw are alternatives: if both
are specified at startup, the earlier of these two options is discarded.
If <em>pwd</em> is not specified, a random 4-digit pin code is
displayed, and must be entered on the client at <strong>each</strong>
new conenction.</p>
<p><strong>-vsync [x]</strong> (In Mirror mode:) this option <p><strong>-vsync [x]</strong> (In Mirror mode:) this option
(<strong>now the default</strong>) uses timestamps to synchronize audio (<strong>now the default</strong>) uses timestamps to synchronize audio
with video on the server, with an optional audio delay in (decimal) with video on the server, with an optional audio delay in (decimal)
@@ -1627,9 +1644,15 @@ an AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
introduced 2017, running tvOS 12.2.1), so it does not seem to matter introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.</p> what version UxPlay claims to be.</p>
<h1 id="changelog">Changelog</h1> <h1 id="changelog">Changelog</h1>
<p>1.72 2025-05-15. Improved HLS Live Streaming (YouTube) support. Add
support for password (HTTP Digest Authentication, -pw option) as
alternative to on-screen pin codes.</p>
<p>1.72 2024-04-22 Add requested options -md &lt;filename&gt; to output <p>1.72 2024-04-22 Add requested options -md &lt;filename&gt; to output
audio metadata text to a file for possible display (complements -ca audio metadata text to a file for possible display (complements -ca
option), and -vol <v> option to set initial audio-streaming volume.</p> option), and -vol <v> option to set initial audio-streaming volume. Add
support password user access control with HTTP digest Authentication
(-pw [pwd]). If no pwd is set, a random pin is displayed for entry at
each new connection.</p>
<p>1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially <p>1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
only for YouTube movies. Fix issue with NTP timeout on Windows.</p> only for YouTube movies. Fix issue with NTP timeout on Windows.</p>
<p>1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x <p>1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x

View File

@@ -1,18 +1,23 @@
# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows). # UxPlay 1.72 (beta): AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).** ### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).**
- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming) - ***NEW in v1.72**: Improved Support for (YouTube) HLS (HTTP Live Streaming)
video with the new "-hls" option.* **Only streaming from the YouTube iOS app video with the new "-hls" option.* **Only streaming from the YouTube iOS app
(in \"m3u8\" protocol) is currently supported**: (streaming using the AirPlay icon in a browser window (in \"m3u8\" protocol) is currently supported**: (streaming using the AirPlay icon in a browser window
is **not** yet supported).Click on the airplay icon in the is **not** yet supported).Click on the airplay icon in the
YouTube app to stream video. (You may need to wait until YouTube app to stream video.
advertisements have finished or been skipped before clicking the **Please report any issues with this new feature of UxPlay**.
YouTube airplay icon.) **Please report any issues with this new
feature of UxPlay**. _The default video player for HLS is _The default video player for HLS is
GStreamer playbin v3: use "-hls 2" to revert to playbin v2 if GStreamer playbin v3: use "-hls 2" to revert to playbin v2 if
some videos fail to play_. some videos fail to play_.
* added support for setting a password (as an alternative to on-screen
pin codes) to control client access (-pw option); added support for
setting initial audion volume (--vol option), and output of audio-mode
metatdate to file (for display, -md option).
## Highlights: ## Highlights:
- GPLv3, open source. - GPLv3, open source.
@@ -499,7 +504,11 @@ below for help with this or other problems.
[Usage](#usage) for details, if you wish to use it. *Some clients [Usage](#usage) for details, if you wish to use it. *Some clients
with MDM (Mobile Device Management, often present on employer-owned with MDM (Mobile Device Management, often present on employer-owned
devices) are required to use pin-authentication: UxPlay will provide devices) are required to use pin-authentication: UxPlay will provide
this even when running without the pin option.* this even when running without the pin option.* Password authentication
(-pw _pwd_)is also offered as an alternative solution to pin codes:
users need to know the password _pwd_ and enter it on their iOS/macOS device
to access UxPlay, when prompted (if _pwd_ is not set, a displayed random
pin code must be entered at **each** new connection.)
- By default, UxPlay is locked to its current client until that client - By default, UxPlay is locked to its current client until that client
drops the connection; since UxPlay-1.58, the option `-nohold` drops the connection; since UxPlay-1.58, the option `-nohold`
@@ -1006,6 +1015,17 @@ deregisters the corresponding client (see options -restrict, -block,
-allow for more ways to control client access). *(Add a line "reg" in -allow for more ways to control client access). *(Add a line "reg" in
the startup file if you wish to use this feature.)* the startup file if you wish to use this feature.)*
**-pw** [*pwd*]. (since 1.72). As an alternative to -pin, client access
can be controlled with a password set when uxplay starts (set it in
the .uxplay startup file, where it is stored as cleartext.) All users must
then know this password. This uses HTTP md5 Digest authentication,
which is now regarded as providing weak security, but it is only used to
validate the uxplay password, and no user credentials are exposed. _Note:
-pin and -pw are alternatives: if both are specified at startup, the
earlier of these two options is discarded. If *pwd* is not specified,
a random 4-digit pin code is displayed, and must be entered on the client
at **each** new conenction.
**-vsync \[x\]** (In Mirror mode:) this option (**now the default**) **-vsync \[x\]** (In Mirror mode:) this option (**now the default**)
uses timestamps to synchronize audio with video on the server, with an uses timestamps to synchronize audio with video on the server, with an
optional audio delay in (decimal) milliseconds (*x* = "20.5" means optional audio delay in (decimal) milliseconds (*x* = "20.5" means
@@ -1671,10 +1691,15 @@ introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be. what version UxPlay claims to be.
# Changelog # Changelog
1.72 2025-05-15. Improved HLS Live Streaming (YouTube) support.
Add support for password (HTTP Digest Authentication, -pw option) as
alternative to on-screen pin codes.
1.72 2024-04-22 Add requested options -md \<filename\> to output audio 1.72 2024-04-22 Add requested options -md \<filename\> to output audio
metadata text to a file for possible display (complements -ca option), metadata text to a file for possible display (complements -ca option),
and -vol <v> option to set initial audio-streaming volume. and -vol <v> option to set initial audio-streaming volume. Add support
password user access control with HTTP digest Authentication (-pw [pwd]).
If no pwd is set, a random pin is displayed for entry at each new connection.
1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially 1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
only for YouTube movies. Fix issue with NTP timeout on Windows. only for YouTube movies. Fix issue with NTP timeout on Windows.

View File

@@ -1,17 +1,19 @@
# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows). # UxPlay 1.72 (beta): AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).** ### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).**
- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming) - ***NEW in v1.72**: Improved Support for (YouTube) HLS (HTTP Live
video with the new "-hls" option.* **Only streaming from the YouTube Streaming) video with the new "-hls" option.* **Only streaming from
iOS app (in \"m3u8\" protocol) is currently supported**: (streaming the YouTube iOS app (in \"m3u8\" protocol) is currently supported**:
using the AirPlay icon in a browser window is **not** yet (streaming using the AirPlay icon in a browser window is **not** yet
supported).Click on the airplay icon in the YouTube app to stream supported).Click on the airplay icon in the YouTube app to stream
video. (You may need to wait until advertisements have finished or video. **Please report any issues with this new feature of UxPlay**.
been skipped before clicking the YouTube airplay icon.) **Please
report any issues with this new feature of UxPlay**. *The default *The default video player for HLS is GStreamer playbin v3: use "-hls
video player for HLS is GStreamer playbin v3: use "-hls 2" to revert 2" to revert to playbin v2 if some videos fail to play*.
to playbin v2 if some videos fail to play*.
- added support for setting a password (as an alternative to
on-screen pin codes) to control client access (-pw option)
## Highlights: ## Highlights:
@@ -499,7 +501,12 @@ below for help with this or other problems.
[Usage](#usage) for details, if you wish to use it. *Some clients [Usage](#usage) for details, if you wish to use it. *Some clients
with MDM (Mobile Device Management, often present on employer-owned with MDM (Mobile Device Management, often present on employer-owned
devices) are required to use pin-authentication: UxPlay will provide devices) are required to use pin-authentication: UxPlay will provide
this even when running without the pin option.* this even when running without the pin option.* Password
authentication (-pw *pwd*)is also offered as an alternative solution
to pin codes: users need to know the password *pwd* and enter it on
their iOS/macOS device to access UxPlay, when prompted (if *pwd* is
not set, a displayed random pin code must be entered at **each** new
connection.)
- By default, UxPlay is locked to its current client until that client - By default, UxPlay is locked to its current client until that client
drops the connection; since UxPlay-1.58, the option `-nohold` drops the connection; since UxPlay-1.58, the option `-nohold`
@@ -1007,6 +1014,17 @@ deregisters the corresponding client (see options -restrict, -block,
-allow for more ways to control client access). *(Add a line "reg" in -allow for more ways to control client access). *(Add a line "reg" in
the startup file if you wish to use this feature.)* the startup file if you wish to use this feature.)*
**-pw** \[*pwd*\]. (since 1.72). As an alternative to -pin, client
access can be controlled with a password set when uxplay starts (set it
in the .uxplay startup file, where it is stored as cleartext.) All users
must then know this password. This uses HTTP md5 Digest authentication,
which is now regarded as providing weak security, but it is only used to
validate the uxplay password, and no user credentials are exposed.
\_Note: -pin and -pw are alternatives: if both are specified at startup,
the earlier of these two options is discarded. If *pwd* is not
specified, a random 4-digit pin code is displayed, and must be entered
on the client at **each** new conenction.
**-vsync \[x\]** (In Mirror mode:) this option (**now the default**) **-vsync \[x\]** (In Mirror mode:) this option (**now the default**)
uses timestamps to synchronize audio with video on the server, with an uses timestamps to synchronize audio with video on the server, with an
optional audio delay in (decimal) milliseconds (*x* = "20.5" means optional audio delay in (decimal) milliseconds (*x* = "20.5" means
@@ -1672,9 +1690,16 @@ what version UxPlay claims to be.
# Changelog # Changelog
1.72 2025-05-15. Improved HLS Live Streaming (YouTube) support. Add
support for password (HTTP Digest Authentication, -pw option) as
alternative to on-screen pin codes.
1.72 2024-04-22 Add requested options -md \<filename\> to output audio 1.72 2024-04-22 Add requested options -md \<filename\> to output audio
metadata text to a file for possible display (complements -ca option), metadata text to a file for possible display (complements -ca option),
and -vol `<v>`{=html} option to set initial audio-streaming volume. and -vol `<v>`{=html} option to set initial audio-streaming volume. Add
support password user access control with HTTP digest Authentication
(-pw \[pwd\]). If no pwd is set, a random pin is displayed for entry at
each new connection.
1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially 1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
only for YouTube movies. Fix issue with NTP timeout on Windows. only for YouTube movies. Fix issue with NTP timeout on Windows.

View File

@@ -507,6 +507,8 @@ void ed25519_key_destroy(ed25519_key_t *key) {
} }
} }
// SHA 512 // SHA 512
struct sha_ctx_s { struct sha_ctx_s {
@@ -540,7 +542,6 @@ void sha_final(sha_ctx_t *ctx, uint8_t *out, unsigned int *len) {
void sha_reset(sha_ctx_t *ctx) { void sha_reset(sha_ctx_t *ctx) {
if (!EVP_MD_CTX_reset(ctx->digest_ctx) || if (!EVP_MD_CTX_reset(ctx->digest_ctx) ||
!EVP_DigestInit_ex(ctx->digest_ctx, EVP_sha512(), NULL)) { !EVP_DigestInit_ex(ctx->digest_ctx, EVP_sha512(), NULL)) {
handle_error(__func__); handle_error(__func__);
} }
} }
@@ -552,6 +553,63 @@ void sha_destroy(sha_ctx_t *ctx) {
} }
} }
//MD5
struct md5_ctx_s {
EVP_MD_CTX *digest_ctx;
};
md5_ctx_t *md5_init() {
md5_ctx_t *ctx = malloc(sizeof(md5_ctx_t));
assert(ctx != NULL);
ctx->digest_ctx = EVP_MD_CTX_new();
assert(ctx->digest_ctx != NULL);
if (!EVP_DigestInit_ex(ctx->digest_ctx, EVP_md5(), NULL)) {
handle_error(__func__);
}
return ctx;
}
void md5_update(md5_ctx_t *ctx, const uint8_t *in, int len) {
if (!EVP_DigestUpdate(ctx->digest_ctx, in, len)) {
handle_error(__func__);
}
}
void md5_final(md5_ctx_t *ctx, uint8_t *out, unsigned int *len) {
if (!EVP_DigestFinal_ex(ctx->digest_ctx, out, len)) {
handle_error(__func__);
}
}
void md5_reset(md5_ctx_t *ctx) {
if (!EVP_MD_CTX_reset(ctx->digest_ctx) ||
!EVP_DigestInit_ex(ctx->digest_ctx, EVP_md5(), NULL)) {
handle_error(__func__);
}
}
void md5_destroy(md5_ctx_t *ctx) {
if (ctx) {
EVP_MD_CTX_free(ctx->digest_ctx);
free(ctx);
}
}
#define MD5_DIGEST_LENGTH 16
char *get_md5(char *string) {
unsigned char hash[MD5_DIGEST_LENGTH];
md5_ctx_t *ctx = NULL;
ctx = md5_init();
md5_update(ctx, (const unsigned char *) string, strlen(string));
md5_final(ctx, hash, NULL);
md5_destroy(ctx);
ctx = NULL;
char *result_str = utils_hex_to_string(hash, MD5_DIGEST_LENGTH);
return result_str; //must free result_str after use
}
int get_random_bytes(unsigned char *buf, int num) { int get_random_bytes(unsigned char *buf, int num) {
return RAND_bytes(buf, num); return RAND_bytes(buf, num);
} }

View File

@@ -109,6 +109,15 @@ void sha_final(sha_ctx_t *ctx, uint8_t *out, unsigned int *len);
void sha_reset(sha_ctx_t *ctx); void sha_reset(sha_ctx_t *ctx);
void sha_destroy(sha_ctx_t *ctx); void sha_destroy(sha_ctx_t *ctx);
//MD5
typedef struct md5_ctx_s md5_ctx_t;
md5_ctx_t *md5_init();
void md5_update(md5_ctx_t *ctx, const uint8_t *in, int len);
void md5_final(md5_ctx_t *ctx, uint8_t *out, unsigned int *len);
void md5_reset(md5_ctx_t *ctx);
void md5_destroy(md5_ctx_t *ctx);
char *get_md5(char *string);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -151,17 +151,21 @@ struct dnssd_s {
uint32_t features1; uint32_t features1;
uint32_t features2; uint32_t features2;
unsigned char require_pw; unsigned char pin_pw;
}; };
dnssd_t * dnssd_t *
dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error, int require_pw) dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len, int *error, unsigned char pin_pw)
{ {
dnssd_t *dnssd; dnssd_t *dnssd;
char *end; char *end;
unsigned long features; unsigned long features;
/* pin_pw = 0: no pin or password
1: use onscreen pin for client access control
2: require password for client accress control.
*/
if (error) *error = DNSSD_ERROR_NOERROR; if (error) *error = DNSSD_ERROR_NOERROR;
@@ -171,7 +175,7 @@ dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len,
return NULL; return NULL;
} }
dnssd->require_pw = (unsigned char) require_pw; dnssd->pin_pw = pin_pw;
features = strtoul(FEATURES_1, &end, 16); features = strtoul(FEATURES_1, &end, 16);
if (!end || (features & 0xFFFFFFFF) != features) { if (!end || (features & 0xFFFFFFFF) != features) {
@@ -302,10 +306,19 @@ dnssd_register_raop(dnssd_t *dnssd, unsigned short port)
dnssd->TXTRecordSetValue(&dnssd->raop_record, "am", strlen(GLOBAL_MODEL), GLOBAL_MODEL); dnssd->TXTRecordSetValue(&dnssd->raop_record, "am", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "md", strlen(RAOP_MD), RAOP_MD); dnssd->TXTRecordSetValue(&dnssd->raop_record, "md", strlen(RAOP_MD), RAOP_MD);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "rhd", strlen(RAOP_RHD), RAOP_RHD); dnssd->TXTRecordSetValue(&dnssd->raop_record, "rhd", strlen(RAOP_RHD), RAOP_RHD);
if (dnssd->require_pw) { switch (dnssd->pin_pw) {
case 2:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true"); dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true");
} else { dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", 4, "0x84");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("false"), "false"); break;
case 1:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", 3, "0x8c");
break;
default:
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("true"), "false");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", strlen(RAOP_SF), RAOP_SF);
break;
} }
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sr", strlen(RAOP_SR), RAOP_SR); dnssd->TXTRecordSetValue(&dnssd->raop_record, "sr", strlen(RAOP_SR), RAOP_SR);
dnssd->TXTRecordSetValue(&dnssd->raop_record, "ss", strlen(RAOP_SS), RAOP_SS); dnssd->TXTRecordSetValue(&dnssd->raop_record, "ss", strlen(RAOP_SS), RAOP_SS);
@@ -361,18 +374,26 @@ dnssd_register_airplay(dnssd_t *dnssd, unsigned short port)
return -1; return -1;
} }
// flags is a string representing a 20-bit flag (up to 3 hex digits)
dnssd->TXTRecordCreate(&dnssd->airplay_record, 0, NULL); dnssd->TXTRecordCreate(&dnssd->airplay_record, 0, NULL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "deviceid", strlen(device_id), device_id); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "deviceid", strlen(device_id), device_id);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "features", strlen(features), features); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "features", strlen(features), features);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", strlen(AIRPLAY_FLAGS), AIRPLAY_FLAGS); switch (dnssd->pin_pw) {
case 1: // display onscreen pin
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
case 2: // require password
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
default:
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("false"), "false");
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "flags", 3, "0x4");
break;
}
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "model", strlen(GLOBAL_MODEL), GLOBAL_MODEL); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "model", strlen(GLOBAL_MODEL), GLOBAL_MODEL);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pk", strlen(dnssd->pk), dnssd->pk); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pk", strlen(dnssd->pk), dnssd->pk);
if (dnssd->require_pw) {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("true"), "true");
} else {
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pw", strlen("false"), "false");
}
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pi", strlen(AIRPLAY_PI), AIRPLAY_PI); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "pi", strlen(AIRPLAY_PI), AIRPLAY_PI);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "srcvers", strlen(AIRPLAY_SRCVERS), AIRPLAY_SRCVERS); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "srcvers", strlen(AIRPLAY_SRCVERS), AIRPLAY_SRCVERS);
dnssd->TXTRecordSetValue(&dnssd->airplay_record, "vv", strlen(AIRPLAY_VV), AIRPLAY_VV); dnssd->TXTRecordSetValue(&dnssd->airplay_record, "vv", strlen(AIRPLAY_VV), AIRPLAY_VV);

View File

@@ -35,7 +35,7 @@ extern "C" {
typedef struct dnssd_s dnssd_t; typedef struct dnssd_s dnssd_t;
DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, int require_pw); DNSSD_API dnssd_t *dnssd_init(const char *name, int name_len, const char *hw_addr, int hw_addr_len, int *error, unsigned char pin_pw);
DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, unsigned short port); DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, unsigned short port);
DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, unsigned short port); DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, unsigned short port);

View File

@@ -44,7 +44,7 @@
#define RAOP_VN "65537" #define RAOP_VN "65537"
#define AIRPLAY_SRCVERS GLOBAL_VERSION /*defined in global.h */ #define AIRPLAY_SRCVERS GLOBAL_VERSION /*defined in global.h */
#define AIRPLAY_FLAGS "0x4" #define AIRPLAY_FLAGS "0x84"
#define AIRPLAY_VV "2" #define AIRPLAY_VV "2"
#define AIRPLAY_PI "2e388006-13ba-4041-9a67-25dd4a43d536" #define AIRPLAY_PI "2e388006-13ba-4041-9a67-25dd4a43d536"

View File

@@ -16,6 +16,7 @@
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <stdbool.h> #include <stdbool.h>
@@ -195,6 +196,168 @@ pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_ke
return 0; return 0;
} }
int
pairing_session_make_nonce(pairing_session_t *session, uint64_t *local_time, const char *client_data, unsigned char *nonce, int len) {
unsigned char hash[SHA512_DIGEST_LENGTH];
if (len > sizeof(hash)) {
return -1;
}
if (!client_data || !local_time || !session || !nonce || len <= 0) {
return -2;
}
sha_ctx_t *ctx = sha_init();
sha_update(ctx, (const unsigned char *) local_time, sizeof(uint64_t));
sha_update(ctx, (const unsigned char *) client_data, strlen(client_data));
sha_update(ctx, (const unsigned char *) session->ed_ours, ED25519_KEY_SIZE);
sha_final(ctx, hash, NULL);
sha_destroy(ctx);
memcpy(nonce, hash, len);
return 0;
}
static
char *get_token(char **cursor, char *token_name, char start_char, char end_char) {
char *ptr = *cursor;
ptr = strstr(ptr, token_name);
if (!ptr) {
return NULL;
}
ptr += strlen(token_name);
ptr = strchr(ptr, start_char);
if (!ptr) {
return NULL;
}
char *token = ++ptr;
ptr = strchr(ptr, end_char);
if (!ptr) {
return NULL;
}
*(ptr++) = '\0';
*cursor = ptr;
return token;
}
//#define test_digest
bool
pairing_digest_verify(const char *method, const char * authorization, const char *password) {
/* RFC 2617 HTTP md5 Digest password authentication */
char *sentence = (char *) calloc(strlen(authorization) + 1, sizeof(char));
strncpy(sentence, authorization, strlen(authorization));
char *username = NULL;
char *realm = NULL;
char *nonce = NULL;
char *uri = NULL;
char *qop = NULL;
char *nc = NULL;
char *cnonce = NULL;
char *response = NULL;
char *cursor = sentence;
const char *pwd = password;
const char *mthd = method;
char *raw;
int len;
bool authenticated;
#ifdef test_digest
char testauth[] = "Digest username=\"Mufasa\","
"realm=\"testrealm@host.com\","
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
"uri=\"/dir/index.html\","
"qop=auth,"
"nc=00000001,"
"cnonce=\"0a4f113b\","
"response=\"6629fae49393a05397450978507c4ef1\","
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\""
;
pwd = "Circle Of Life";
mthd = "GET";
cursor = testauth;
char HA1[] = "939e7578ed9e3c518a452acee763bce9";
char HA2[] = "39aff3a2bab6126f332b942af96d3366";
#endif
username = get_token(&cursor, "username", '\"', '\"');
realm = get_token(&cursor, "realm", '\"', '\"');
nonce = get_token(&cursor,"nonce", '\"', '\"');
uri = get_token(&cursor,"uri", '\"', '\"');
qop = get_token(&cursor, "qop", '=', ',');
if (qop) {
nc = get_token(&cursor, "nc", '=', ',');
cnonce = get_token(&cursor, "cnonce", '\"', '\"');
}
response = get_token(&cursor, "response", '\"', '\"');
#ifdef test_digest
printf("username: [%s] realm: [%s]\n", username, realm);
printf("nonce: [%s]\n", nonce);
printf("method: [%s]\n", mthd);
printf("uri: [%s]\n", uri);
if (qop) {
printf("qop: [%s], nc=[%s], cnonce: [%s]\n", qop, nc, cnonce);
}
printf("response: [%s]\n", response);
#endif
/* H1 = H(username : realm : password ) */
len = strlen(username) + strlen(realm) + strlen(pwd) + 3;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s", username, realm, pwd);
char *hash1 = get_md5(raw);
free (raw);
#ifdef test_digest
printf("hash1: should be %s, was: %s\n ", HA1, hash1_str);
#endif
/* H2 = H(method : uri) */
len = strlen(mthd) + strlen(uri) + 2;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s", mthd, uri);
char *hash2 = get_md5(raw);
free (raw);
#ifdef test_digest
printf("hash2: should be %s, was: %s\n", HA2, hash2_str);
#endif
/* result = H(H1 : nonce (or nonce:nc:cnonce:qop) : H2) */
len = strlen(hash1) + strlen(nonce) + strlen(hash2) + 3;
if (qop) {
len += strlen(nc) + strlen(cnonce) + strlen(qop) + 3;
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s:%s:%s:%s", hash1, nonce, nc, cnonce, qop, hash2);
} else {
raw = (char *) calloc(len, sizeof(char));
snprintf(raw, len, "%s:%s:%s", hash1, nonce, hash2);
}
free (hash1);
free (hash2);
char *result = get_md5(raw);
free (raw);
authenticated = (strcmp(result,response) ? false : true);
#ifdef test_digest
printf("result: should be %s, was: %s, authenticated is %s\n", response, result, (authenticated ? "true" : "false"));
#endif
free (result);
free(sentence);
#ifdef test_digest
exit(0);
#endif
return authenticated;
}
int int
pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE]) pairing_session_get_signature(pairing_session_t *session, unsigned char signature[PAIRING_SIG_SIZE])
{ {

View File

@@ -62,4 +62,7 @@ int srp_confirm_pair_setup(pairing_session_t *session, pairing_t *pairing, unsig
unsigned char *auth_tag); unsigned char *auth_tag);
void access_client_session_data(pairing_session_t *session, char **username, char **client_pk, bool *setup); void access_client_session_data(pairing_session_t *session, char **username, char **client_pk, bool *setup);
void ed25519_pk_to_base64(const unsigned char *pk, char **pk64); void ed25519_pk_to_base64(const unsigned char *pk, char **pk64);
int pairing_session_make_nonce(pairing_session_t *session, uint64_t *local_time, const char *client_data, unsigned char *nonce, int len);
bool pairing_digest_verify(const char *method, const char * authorization, const char *password);
#endif #endif

View File

@@ -77,6 +77,10 @@ struct raop_s {
/* activate support for HLS live streaming */ /* activate support for HLS live streaming */
bool hls_support; bool hls_support;
/* used in digest authentication */
char *nonce;
char * random_pw;
}; };
struct raop_conn_s { struct raop_conn_s {
@@ -99,7 +103,7 @@ struct raop_conn_s {
connection_type_t connection_type; connection_type_t connection_type;
char *client_session_id; char *client_session_id;
bool authenticated;
bool have_active_remote; bool have_active_remote;
}; };
typedef struct raop_conn_s raop_conn_t; typedef struct raop_conn_s raop_conn_t;
@@ -159,6 +163,7 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
conn->client_session_id = NULL; conn->client_session_id = NULL;
conn->airplay_video = NULL; conn->airplay_video = NULL;
conn->authenticated = false;
conn->have_active_remote = false; conn->have_active_remote = false;
@@ -579,6 +584,7 @@ raop_init(raop_callbacks_t *callbacks) {
raop->hls_support = false; raop->hls_support = false;
raop->nonce = NULL;
return raop; return raop;
} }
@@ -602,7 +608,7 @@ raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile)
#else #else
unsigned char public_key[ED25519_KEY_SIZE]; unsigned char public_key[ED25519_KEY_SIZE];
pairing_get_public_key(pairing, public_key); pairing_get_public_key(pairing, public_key);
char *pk_str = utils_pk_to_string(public_key, ED25519_KEY_SIZE); char *pk_str = utils_hex_to_string(public_key, ED25519_KEY_SIZE);
strncpy(raop->pk_str, (const char *) pk_str, 2*ED25519_KEY_SIZE); strncpy(raop->pk_str, (const char *) pk_str, 2*ED25519_KEY_SIZE);
free(pk_str); free(pk_str);
#endif #endif
@@ -638,6 +644,13 @@ raop_destroy(raop_t *raop) {
pairing_destroy(raop->pairing); pairing_destroy(raop->pairing);
httpd_destroy(raop->httpd); httpd_destroy(raop->httpd);
logger_destroy(raop->logger); logger_destroy(raop->logger);
if (raop->nonce) {
free(raop->nonce);
}
if (raop->random_pw) {
free(raop->random_pw);
}
free(raop); free(raop);
/* Cleanup the network */ /* Cleanup the network */

View File

@@ -90,6 +90,7 @@ struct raop_callbacks_s {
void (*display_pin) (void *cls, char * pin); void (*display_pin) (void *cls, char * pin);
void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name); void (*register_client) (void *cls, const char *device_id, const char *pk_str, const char *name);
bool (*check_register) (void *cls, const char *pk_str); bool (*check_register) (void *cls, const char *pk_str);
const char* (*passwd) (void *cls, int *len);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id); void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
int (*video_set_codec)(void *cls, video_codec_t codec); int (*video_set_codec)(void *cls, video_codec_t codec);
/* for HLS video player controls */ /* for HLS video player controls */

View File

@@ -25,6 +25,7 @@
#include <plist/plist.h> #include <plist/plist.h>
#define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */ #define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */
#define SECOND_IN_USECS 1000000 #define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000
typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *, typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *,
http_response_t *, char **, int *); http_response_t *, char **, int *);
@@ -36,6 +37,33 @@ raop_handler_info(raop_conn_t *conn,
{ {
assert(conn->raop->dnssd); assert(conn->raop->dnssd);
#if 0
/* initial GET/info request sends plist with string "txtAirPlay" */
bool txtAirPlay = false;
const char* content_type = NULL;
content_type = http_request_get_header(request, "Content-Type");
if (content_type && strstr(content_type, "application/x-apple-binary-plist")) {
char *qualifier_string = NULL;
const char *data = NULL;
int data_len = 0;
data = http_request_get_data(request, &data_len);
//parsing bplist
plist_t req_root_node = NULL;
plist_from_bin(data, data_len, &req_root_node);
plist_t req_qualifier_node = plist_dict_get_item(req_root_node, "qualifier");
if (PLIST_IS_ARRAY(req_qualifier_node)) {
plist_t req_string_node = plist_array_get_item(req_qualifier_node, 0);
plist_get_string_val(req_string_node, &qualifier_string);
}
if (qualifier_string && !strcmp(qualifier_string, "txtAirPlay")) {
printf("qualifier: %s\n", qualifier_string);
txtAirPlay = true;
}
if (qualifier_string) {
free(qualifier_string);
}
}
#endif
plist_t res_node = plist_new_dict(); plist_t res_node = plist_new_dict();
/* deviceID is the physical hardware address, and will not change */ /* deviceID is the physical hardware address, and will not change */
@@ -555,6 +583,81 @@ raop_handler_setup(raop_conn_t *conn,
logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1"); logger_log(conn->raop->logger, LOGGER_DEBUG, "SETUP 1");
// First setup // First setup
/* RFC2617 Digest authentication (md5 hash) of uxplay client-access password, if set */
if (!conn->authenticated && conn->raop->callbacks.passwd) {
int len;
const char *password = conn->raop->callbacks.passwd(conn->raop->callbacks.cls, &len);
// len = -1 means use a random password for this connection
if (len == -1 && !conn->raop->random_pw) {
// get and store 4 random digits
int pin_4 = random_pin();
if (pin_4 < 0) {
logger_log(conn->raop->logger, LOGGER_ERR, "Failed to generate random pin");
}
char pin[6] = {'\0'};
snprintf(pin, 5, "%04u", pin_4 % 10000);
printf("*** set new pin = [%s]\n", pin);
conn->raop->random_pw = strndup((const char *) pin, 6);
printf("*** stored new pin = [%s]\n", conn->raop->random_pw);
}
if (len == -1 && conn->raop->callbacks.display_pin) {
char *pin = conn->raop->random_pw;
assert(pin);
conn->raop->callbacks.display_pin(conn->raop->callbacks.cls, pin);
logger_log(conn->raop->logger, LOGGER_INFO, "*** CLIENT MUST NOW ENTER PIN = \"%s\" AS AIRPLAY PASSWORD", pin);
password = (const char *) pin;
}
if (len && !conn->authenticated) {
char nonce_string[33] = { '\0' };
//bool stale = false; //not implemented
const char *authorization = NULL;
authorization = http_request_get_header(request, "Authorization");
if (authorization) {
char *ptr = strstr(authorization, "nonce=\"") + strlen("nonce=\"");
strncpy(nonce_string, ptr, 32);
const char *method = http_request_get_method(request);
conn->authenticated = pairing_digest_verify(method, authorization, password);
if (conn->authenticated) {
printf("initial authenticatication OK\n");
conn->authenticated = conn->authenticated && !strcmp(nonce_string, conn->raop->nonce);
if (!conn->authenticated) {
logger_log(conn->raop->logger, LOGGER_INFO, "authentication rejected (nonce mismatch) %s %s",
nonce_string, conn->raop->nonce);
}
}
if (conn->authenticated && conn->raop->random_pw) {
printf("*********free random_pw\n");
free (conn->raop->random_pw);
conn->raop->random_pw = NULL;
}
if (conn->raop->nonce) {
free(conn->raop->nonce);
conn->raop->nonce = NULL;
}
logger_log(conn->raop->logger, LOGGER_INFO, "Client authentication %s", (conn->authenticated ? "success" : "failure"));
}
if (!conn->authenticated) {
/* create a nonce */
const char *url = http_request_get_url(request);
unsigned char nonce[16] = { '\0' };
int len = 16;
uint64_t now = raop_ntp_get_local_time();
assert (!pairing_session_make_nonce(conn->session, &now, url, nonce, len));
if (conn->raop->nonce) {
free(conn->raop->nonce);
}
conn->raop->nonce = utils_hex_to_string(nonce, len);
char response_text[80] = "Digest realm=\"raop\", nonce=\"";
strncat(response_text, conn->raop->nonce, strlen(conn->raop->nonce));
strncat(response_text, "\"", 1);
http_response_init(response, "RTSP/1.0", 401, "Unauthorized");
http_response_add_header(response, "WWW-Authenticate", response_text);
return;
}
}
}
char* eiv = NULL; char* eiv = NULL;
uint64_t eiv_len = 0; uint64_t eiv_len = 0;
@@ -1047,7 +1150,7 @@ raop_handler_teardown(raop_conn_t *conn,
uint64_t val; uint64_t val;
int count = plist_array_get_size(req_streams_node); int count = plist_array_get_size(req_streams_node);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
plist_t req_stream_node = plist_array_get_item(req_streams_node,0); plist_t req_stream_node = plist_array_get_item(req_streams_node,i);
plist_t req_stream_type_node = plist_dict_get_item(req_stream_node, "type"); plist_t req_stream_type_node = plist_dict_get_item(req_stream_node, "type");
plist_get_uint_val(req_stream_type_node, &val); plist_get_uint_val(req_stream_type_node, &val);
if (val == 96) { if (val == 96) {

View File

@@ -186,14 +186,14 @@ char *utils_parse_hex(const char *str, int str_len, int *data_len) {
return data; return data;
} }
char *utils_pk_to_string(const unsigned char *pk, int pk_len) { char *utils_hex_to_string(const unsigned char *hex, int hex_len) {
char *pk_str = (char *) malloc(2*pk_len + 1); char *hex_str = (char *) malloc(2*hex_len + 1);
char* pos = pk_str; char* pos = hex_str;
for (int i = 0; i < pk_len; i++) { for (int i = 0; i < hex_len; i++) {
snprintf(pos, 3, "%2.2x", *(pk + i)); snprintf(pos, 3, "%2.2x", *(hex + i));
pos +=2; pos +=2;
} }
return pk_str; return hex_str;
} }
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line) { char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line) {

View File

@@ -25,7 +25,7 @@ int utils_read_file(char **dst, const char *pemstr);
int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen); int utils_hwaddr_raop(char *str, int strlen, const char *hwaddr, int hwaddrlen);
int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen); int utils_hwaddr_airplay(char *str, int strlen, const char *hwaddr, int hwaddrlen);
char *utils_parse_hex(const char *str, int str_len, int *data_len); char *utils_parse_hex(const char *str, int str_len, int *data_len);
char *utils_pk_to_string(const unsigned char *pk, int pk_len); char *utils_hex_to_string(const unsigned char *hex, int hex_len);
char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line); char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per_line);
char *utils_data_to_text(const char *data, int datalen); char *utils_data_to_text(const char *data, int datalen);
void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize); void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);

View File

@@ -27,6 +27,13 @@ UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
.IP .IP
client pin-registration; (option: use file "fn" for this) client pin-registration; (option: use file "fn" for this)
.TP .TP
\fB\-pw\fI [pwd]\fR Require use of password "pwd" to control client access.
.IP
(with no \fIpwd\fR, pin entry is required at \fIeach\fR connection.)
.IP
(option "-pw" after "-pin" overrides it, and vice versa.)
.TP
\fB\-vsync\fI[x]\fR Mirror mode: sync audio to video using timestamps (default) \fB\-vsync\fI[x]\fR Mirror mode: sync audio to video using timestamps (default)
.IP .IP
\fIx\fR is optional audio delay: millisecs, decimal, can be neg. \fIx\fR is optional audio delay: millisecs, decimal, can be neg.

View File

@@ -65,7 +65,7 @@
#include "renderers/video_renderer.h" #include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h" #include "renderers/audio_renderer.h"
#define VERSION "1.71" #define VERSION "1.712beta"
#define SECOND_IN_USECS 1000000 #define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL #define SECOND_IN_NSECS 1000000000UL
@@ -74,6 +74,7 @@
#define LOWEST_ALLOWED_PORT 1024 #define LOWEST_ALLOWED_PORT 1024
#define HIGHEST_PORT 65535 #define HIGHEST_PORT 65535
#define MISSED_FEEDBACK_LIMIT 15 #define MISSED_FEEDBACK_LIMIT 15
#define MIN_PASSWORD_LENGTH 4
#define DEFAULT_PLAYBIN_VERSION 3 #define DEFAULT_PLAYBIN_VERSION 3
#define BT709_FIX "capssetter caps=\"video/x-h264, colorimetry=bt709\"" #define BT709_FIX "capssetter caps=\"video/x-h264, colorimetry=bt709\""
#define SRGB_FIX " ! video/x-raw,colorimetry=sRGB,format=RGB ! " #define SRGB_FIX " ! video/x-raw,colorimetry=sRGB,format=RGB ! "
@@ -143,7 +144,9 @@ static std::vector<std::string> allowed_clients;
static std::vector<std::string> blocked_clients; static std::vector<std::string> blocked_clients;
static bool restrict_clients; static bool restrict_clients;
static bool setup_legacy_pairing = false; static bool setup_legacy_pairing = false;
static bool require_password = false; static unsigned char pin_pw = 0; /* 0: no client access control; 1: onscreen pin ; 2: require password (same password for all clients) 3: random pw*/
static std::string password = "";
static guint min_password_length = MIN_PASSWORD_LENGTH;
static unsigned short pin = 0; static unsigned short pin = 0;
static std::string keyfile = ""; static std::string keyfile = "";
static std::string mac_address = ""; static std::string mac_address = "";
@@ -665,6 +668,9 @@ static void print_info (char *name) {
printf(" default pin is random: optionally use fixed pin xxxx\n"); printf(" default pin is random: optionally use fixed pin xxxx\n");
printf("-reg [fn] Keep a register in $HOME/.uxplay.register to verify returning\n"); printf("-reg [fn] Keep a register in $HOME/.uxplay.register to verify returning\n");
printf(" client pin-registration; (option: use file \"fn\" for this)\n"); printf(" client pin-registration; (option: use file \"fn\" for this)\n");
printf("-pw [pwd] Require use of password to control client access;\n");
printf(" (with no pwd, pin entry is required at *each* connection.)\n");
printf(" (option \"-pw\" after \"-pin\" overrides it, and vice versa)\n");
printf("-vsync [x]Mirror mode: sync audio to video using timestamps (default)\n"); printf("-vsync [x]Mirror mode: sync audio to video using timestamps (default)\n");
printf(" x is optional audio delay: millisecs, decimal, can be neg.\n"); printf(" x is optional audio delay: millisecs, decimal, can be neg.\n");
printf("-vsync no Switch off audio/(server)video timestamp synchronization \n"); printf("-vsync no Switch off audio/(server)video timestamp synchronization \n");
@@ -1189,7 +1195,7 @@ static void parse_arguments (int argc, char *argv[]) {
exit(1); exit(1);
} else if (arg == "-pin") { } else if (arg == "-pin") {
setup_legacy_pairing = true; setup_legacy_pairing = true;
require_password = true; pin_pw = 1;
if (i < argc - 1 && *argv[i+1] != '-') { if (i < argc - 1 && *argv[i+1] != '-') {
unsigned int n = 9999; unsigned int n = 9999;
if (!get_value(argv[++i], &n)) { if (!get_value(argv[++i], &n)) {
@@ -1224,6 +1230,19 @@ static void parse_arguments (int argc, char *argv[]) {
keyfile.erase(); keyfile.erase();
keyfile.append("0"); keyfile.append("0");
} }
} else if (arg == "-pw") {
setup_legacy_pairing = false;
if (!option_has_value(i, argc, arg, argv[i+1])) {
pin_pw = 3;
} else if (i < argc - 1 && *argv[i+1] != '-') {
password.erase();
password.append(argv[++i]);
pin_pw = 2;
if (password.size() < min_password_length) {
fprintf(stderr, "invalid client-access password \"%s\": length must be at least %u characters\n", password.c_str(), min_password_length);
exit(1);
}
}
} else if (arg == "-dacp") { } else if (arg == "-dacp") {
dacpfile.erase(); dacpfile.erase();
if (i < argc - 1 && *argv[i+1] != '-') { if (i < argc - 1 && *argv[i+1] != '-') {
@@ -1504,12 +1523,16 @@ static void stop_dnssd() {
static int start_dnssd(std::vector<char> hw_addr, std::string name) { static int start_dnssd(std::vector<char> hw_addr, std::string name) {
int dnssd_error; int dnssd_error;
int require_pw = (require_password ? 1 : 0);
if (dnssd) { if (dnssd) {
LOGE("start_dnssd error: dnssd != NULL"); LOGE("start_dnssd error: dnssd != NULL");
return 2; return 2;
} }
dnssd = dnssd_init(name.c_str(), strlen(name.c_str()), hw_addr.data(), hw_addr.size(), &dnssd_error, require_pw); /* pin_pw controls client access
pin_pw = 1: client must enter pin displayed onscreen (first access only)
= 2: client must enter password (same password for all clients)
= 0: no access control
*/
dnssd = dnssd_init(name.c_str(), strlen(name.c_str()), hw_addr.data(), hw_addr.size(), &dnssd_error, pin_pw);
if (dnssd_error) { if (dnssd_error) {
LOGE("Could not initialize dnssd library!: error %d", dnssd_error); LOGE("Could not initialize dnssd library!: error %d", dnssd_error);
return 1; return 1;
@@ -1665,6 +1688,15 @@ extern "C" void display_pin(void *cls, char *pin) {
} }
} }
extern "C" const char *passwd(void *cls, int *len){
if (pin_pw == 3) {
*len = -1;
return NULL;
}
*len = password.size();
return password.c_str();
}
extern "C" void export_dacp(void *cls, const char *active_remote, const char *dacp_id) { extern "C" void export_dacp(void *cls, const char *active_remote, const char *dacp_id) {
if (dacpfile.length()) { if (dacpfile.length()) {
FILE *fp = fopen(dacpfile.c_str(), "w"); FILE *fp = fopen(dacpfile.c_str(), "w");
@@ -1791,6 +1823,7 @@ extern "C" void video_process (void *cls, raop_ntp_t *ntp, video_decode_struct *
data->ntp_time_remote = data->ntp_time_remote + remote_clock_offset; data->ntp_time_remote = data->ntp_time_remote + remote_clock_offset;
pts_mismatch = video_renderer_render_buffer(data->data, &(data->data_len), &(data->nal_count), &(data->ntp_time_remote)); pts_mismatch = video_renderer_render_buffer(data->data, &(data->data_len), &(data->nal_count), &(data->ntp_time_remote));
if (pts_mismatch) { if (pts_mismatch) {
LOGI("adjust timestamps by %8.6f secs", (double) (pts_mismatch / SECOND_IN_NSECS));
remote_clock_offset += pts_mismatch; remote_clock_offset += pts_mismatch;
} }
count++; count++;
@@ -2096,6 +2129,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_cbs.display_pin = display_pin; raop_cbs.display_pin = display_pin;
raop_cbs.register_client = register_client; raop_cbs.register_client = register_client;
raop_cbs.check_register = check_register; raop_cbs.check_register = check_register;
raop_cbs.passwd = passwd;
raop_cbs.export_dacp = export_dacp; raop_cbs.export_dacp = export_dacp;
raop_cbs.video_reset = video_reset; raop_cbs.video_reset = video_reset;
raop_cbs.video_set_codec = video_set_codec; raop_cbs.video_set_codec = video_set_codec;
@@ -2130,7 +2164,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1); if (show_client_FPS_data) raop_set_plist(raop, "clientFPSdata", 1);
if (audiodelay >= 0) raop_set_plist(raop, "audio_delay_micros", audiodelay); if (audiodelay >= 0) raop_set_plist(raop, "audio_delay_micros", audiodelay);
if (require_password) raop_set_plist(raop, "pin", (int) pin); if (pin_pw == 1) raop_set_plist(raop, "pin", (int) pin);
if (hls_support) raop_set_plist(raop, "hls", 1); if (hls_support) raop_set_plist(raop, "hls", 1);
/* network port selection (ports listed as "0" will be dynamically assigned) */ /* network port selection (ports listed as "0" will be dynamically assigned) */
@@ -2354,7 +2388,7 @@ int main (int argc, char *argv[]) {
video_converter.append(option); video_converter.append(option);
} }
if (require_password && registration_list) { if (pin_pw == 1 && registration_list) {
if (pairing_register == "") { if (pairing_register == "") {
const char * homedir = get_homedir(); const char * homedir = get_homedir();
if (homedir) { if (homedir) {
@@ -2365,7 +2399,7 @@ int main (int argc, char *argv[]) {
} }
/* read in public keys that were previously registered with pair-setup-pin */ /* read in public keys that were previously registered with pair-setup-pin */
if (require_password && registration_list && strlen(pairing_register.c_str())) { if (pin_pw == 1 && registration_list && strlen(pairing_register.c_str())) {
size_t len = 0; size_t len = 0;
std::string key; std::string key;
int clients = 0; int clients = 0;
@@ -2386,7 +2420,7 @@ int main (int argc, char *argv[]) {
} }
} }
if (require_password && keyfile == "0") { if (pin_pw == 1 && keyfile == "0") {
const char * homedir = get_homedir(); const char * homedir = get_homedir();
if (homedir) { if (homedir) {
keyfile.erase(); keyfile.erase();