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
id="uxplay-1.71-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
(now also runs on Windows).</h1>
id="uxplay-1.72-beta-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).</h1>
<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
developed at the GitHub site <a href="https://github.com/FDH2/UxPlay"
class="uri">https://github.com/FDH2/UxPlay</a> (where ALL user issues
should be posted, and latest versions can be found).</strong></h3>
<ul>
<li><em><strong>NEW in v1.71</strong>: Support for (YouTube) HLS (HTTP
Live Streaming) video with the new “-hls” option.</em> <strong>Only
streaming from the YouTube iOS app (in "m3u8" protocol) is currently
supported</strong>: (streaming using the AirPlay icon in a browser
window is <strong>not</strong> yet supported).Click on the airplay icon
in the YouTube app to stream video. (You may need to wait until
advertisements have finished or been skipped before clicking the YouTube
airplay icon.) <strong>Please report any issues with this new feature of
UxPlay</strong>. <em>The default video player for HLS is GStreamer
playbin v3: use “-hls 2” to revert to playbin v2 if some videos fail to
play</em>.</li>
<li><p><em><strong>NEW in v1.72</strong>: Improved Support for (YouTube)
HLS (HTTP Live Streaming) video with the new “-hls” option.</em>
<strong>Only streaming from the YouTube iOS app (in "m3u8" protocol) is
currently supported</strong>: (streaming using the AirPlay icon in a
browser window is <strong>not</strong> yet supported).Click on the
airplay icon in the YouTube app to stream video. <strong>Please report
any issues with this new feature of UxPlay</strong>.</p>
<p><em>The default video player for HLS is GStreamer playbin v3: use
“-hls 2” to revert to playbin v2 if some videos fail to play</em>.</p>
<ul>
<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>
<h2 id="highlights">Highlights:</h2>
<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
clients with MDM (Mobile Device Management, often present on
employer-owned devices) are required to use pin-authentication: UxPlay
will provide this even when running without the pin
option.</em></p></li>
will provide this even when running without the pin option.</em>
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
client drops the connection; since UxPlay-1.58, the option
<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
access). <em>(Add a line “reg” in the startup file if you wish to use
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
(<strong>now the default</strong>) uses timestamps to synchronize audio
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
what version UxPlay claims to be.</p>
<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
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
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

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).**
- ***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
(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
YouTube app to stream video. (You may need to wait until
advertisements have finished or been skipped before clicking the
YouTube airplay icon.) **Please report any issues with this new
feature of UxPlay**. _The default video player for HLS is
YouTube app to stream video.
**Please report any issues with this new feature of UxPlay**.
_The default video player for HLS is
GStreamer playbin v3: use "-hls 2" to revert 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); added support for
setting initial audion volume (--vol option), and output of audio-mode
metatdate to file (for display, -md option).
## Highlights:
- 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
with MDM (Mobile Device Management, often present on employer-owned
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
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
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**)
uses timestamps to synchronize audio with video on the server, with an
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.
# 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
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
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).**
- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming)
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 is **not** yet
- ***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 (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 YouTube app to stream
video. (You may need to wait until advertisements have finished or
been skipped before clicking the YouTube airplay icon.) **Please
report any issues with this new feature of UxPlay**. *The default
video player for HLS is GStreamer playbin v3: use "-hls 2" to revert
to playbin v2 if some videos fail to play*.
video. **Please report any issues with this new feature of UxPlay**.
*The default video player for HLS is GStreamer playbin v3: use "-hls
2" to revert 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:
@@ -499,7 +501,12 @@ below for help with this or other problems.
[Usage](#usage) for details, if you wish to use it. *Some clients
with MDM (Mobile Device Management, often present on employer-owned
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
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
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**)
uses timestamps to synchronize audio with video on the server, with an
optional audio delay in (decimal) milliseconds (*x* = "20.5" means
@@ -1672,9 +1690,16 @@ what version UxPlay claims to be.
# 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
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
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
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) {
if (!EVP_MD_CTX_reset(ctx->digest_ctx) ||
!EVP_DigestInit_ex(ctx->digest_ctx, EVP_sha512(), NULL)) {
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) {
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_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
}
#endif

View File

@@ -151,17 +151,21 @@ struct dnssd_s {
uint32_t features1;
uint32_t features2;
unsigned char require_pw;
unsigned char pin_pw;
};
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;
char *end;
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;
@@ -171,7 +175,7 @@ dnssd_init(const char* name, int name_len, const char* hw_addr, int hw_addr_len,
return NULL;
}
dnssd->require_pw = (unsigned char) require_pw;
dnssd->pin_pw = pin_pw;
features = strtoul(FEATURES_1, &end, 16);
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, "md", strlen(RAOP_MD), RAOP_MD);
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");
} else {
dnssd->TXTRecordSetValue(&dnssd->raop_record, "pw", strlen("false"), "false");
dnssd->TXTRecordSetValue(&dnssd->raop_record, "sf", 4, "0x84");
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, "ss", strlen(RAOP_SS), RAOP_SS);
@@ -361,18 +374,26 @@ dnssd_register_airplay(dnssd_t *dnssd, unsigned short port)
return -1;
}
// flags is a string representing a 20-bit flag (up to 3 hex digits)
dnssd->TXTRecordCreate(&dnssd->airplay_record, 0, NULL);
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, "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, "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, "srcvers", strlen(AIRPLAY_SRCVERS), AIRPLAY_SRCVERS);
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;
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_airplay(dnssd_t *dnssd, unsigned short port);

View File

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

View File

@@ -16,6 +16,7 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
@@ -195,6 +196,168 @@ pairing_session_get_public_key(pairing_session_t *session, unsigned char ecdh_ke
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
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);
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);
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

View File

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

View File

@@ -90,6 +90,7 @@ struct raop_callbacks_s {
void (*display_pin) (void *cls, char * pin);
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);
const char* (*passwd) (void *cls, int *len);
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
int (*video_set_codec)(void *cls, video_codec_t codec);
/* for HLS video player controls */

View File

@@ -25,6 +25,7 @@
#include <plist/plist.h>
#define AUDIO_SAMPLE_RATE 44100 /* all supported AirPlay audio format use this sample rate */
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000
typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *,
http_response_t *, char **, int *);
@@ -36,6 +37,33 @@ raop_handler_info(raop_conn_t *conn,
{
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();
/* 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");
// 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;
uint64_t eiv_len = 0;
@@ -1047,7 +1150,7 @@ raop_handler_teardown(raop_conn_t *conn,
uint64_t val;
int count = plist_array_get_size(req_streams_node);
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_get_uint_val(req_stream_type_node, &val);
if (val == 96) {

View File

@@ -186,14 +186,14 @@ char *utils_parse_hex(const char *str, int str_len, int *data_len) {
return data;
}
char *utils_pk_to_string(const unsigned char *pk, int pk_len) {
char *pk_str = (char *) malloc(2*pk_len + 1);
char* pos = pk_str;
for (int i = 0; i < pk_len; i++) {
snprintf(pos, 3, "%2.2x", *(pk + i));
char *utils_hex_to_string(const unsigned char *hex, int hex_len) {
char *hex_str = (char *) malloc(2*hex_len + 1);
char* pos = hex_str;
for (int i = 0; i < hex_len; i++) {
snprintf(pos, 3, "%2.2x", *(hex + i));
pos +=2;
}
return pk_str;
return hex_str;
}
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_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_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_text(const char *data, int datalen);
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
client pin-registration; (option: use file "fn" for this)
.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)
.IP
\fIx\fR is optional audio delay: millisecs, decimal, can be neg.

View File

@@ -65,7 +65,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
#define VERSION "1.71"
#define VERSION "1.712beta"
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL
@@ -74,6 +74,7 @@
#define LOWEST_ALLOWED_PORT 1024
#define HIGHEST_PORT 65535
#define MISSED_FEEDBACK_LIMIT 15
#define MIN_PASSWORD_LENGTH 4
#define DEFAULT_PLAYBIN_VERSION 3
#define BT709_FIX "capssetter caps=\"video/x-h264, colorimetry=bt709\""
#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 bool restrict_clients;
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 std::string keyfile = "";
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("-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("-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(" x is optional audio delay: millisecs, decimal, can be neg.\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);
} else if (arg == "-pin") {
setup_legacy_pairing = true;
require_password = true;
pin_pw = 1;
if (i < argc - 1 && *argv[i+1] != '-') {
unsigned int n = 9999;
if (!get_value(argv[++i], &n)) {
@@ -1224,6 +1230,19 @@ static void parse_arguments (int argc, char *argv[]) {
keyfile.erase();
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") {
dacpfile.erase();
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) {
int dnssd_error;
int require_pw = (require_password ? 1 : 0);
if (dnssd) {
LOGE("start_dnssd error: dnssd != NULL");
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) {
LOGE("Could not initialize dnssd library!: error %d", dnssd_error);
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) {
if (dacpfile.length()) {
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;
pts_mismatch = video_renderer_render_buffer(data->data, &(data->data_len), &(data->nal_count), &(data->ntp_time_remote));
if (pts_mismatch) {
LOGI("adjust timestamps by %8.6f secs", (double) (pts_mismatch / SECOND_IN_NSECS));
remote_clock_offset += pts_mismatch;
}
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.register_client = register_client;
raop_cbs.check_register = check_register;
raop_cbs.passwd = passwd;
raop_cbs.export_dacp = export_dacp;
raop_cbs.video_reset = video_reset;
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 (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);
/* 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);
}
if (require_password && registration_list) {
if (pin_pw == 1 && registration_list) {
if (pairing_register == "") {
const char * homedir = get_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 */
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;
std::string key;
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();
if (homedir) {
keyfile.erase();