diff --git a/lib/raop.h b/lib/raop.h index 493f1b0..1dd9189 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -68,6 +68,7 @@ struct raop_callbacks_s { void (*audio_set_progress)(void *cls, unsigned int start, unsigned int curr, unsigned int end); void (*audio_get_format)(void *cls, unsigned char *ct, unsigned short *spf, bool *usingScreen, bool *isMedia, uint64_t *audioFormat); void (*video_report_size)(void *cls, float *width_source, float *height_source, float *width, float *height); + void (*report_client_request) (void *cls, char *deviceid, char *model, char *name, bool *admit); }; typedef struct raop_callbacks_s raop_callbacks_t; raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote, int remote_addr_len, diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 5b01f64..ca6189c 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -354,6 +354,33 @@ raop_handler_setup(raop_conn_t *conn, // First setup char* eiv = NULL; uint64_t eiv_len = 0; + + char *deviceID = NULL; + char *model = NULL; + char *name = NULL; + bool admit_client = true; + plist_t req_deviceid_node = plist_dict_get_item(req_root_node, "deviceID"); + plist_get_string_val(req_deviceid_node, &deviceID); + plist_t req_model_node = plist_dict_get_item(req_root_node, "model"); + plist_get_string_val(req_model_node, &model); + plist_t req_name_node = plist_dict_get_item(req_root_node, "name"); + plist_get_string_val(req_name_node, &name); + if (conn->raop->callbacks.report_client_request) { + conn->raop->callbacks.report_client_request(conn->raop->callbacks.cls, deviceID, model, name, &admit_client); + } + free (deviceID); + deviceID = NULL; + free (model); + model = NULL; + free (name); + name = NULL; + if (admit_client == false) { + /* client is not authorized to connect */ + plist_free(res_root_node); + plist_free(req_root_node); + return; + } + plist_get_data_val(req_eiv_node, &eiv, &eiv_len); memcpy(aesiv, eiv, 16); free(eiv); diff --git a/uxplay.1 b/uxplay.1 index c40a5c7..abf535d 100644 --- a/uxplay.1 +++ b/uxplay.1 @@ -1,11 +1,11 @@ -.TH UXPLAY "1" "June 2023" "1.65" "User Commands" +.TH UXPLAY "1" "September 2023" "1.66" "User Commands" .SH NAME uxplay \- start AirPlay server .SH SYNOPSIS .B uxplay [\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...] .SH DESCRIPTION -UxPlay 1.65: An open\-source AirPlay mirroring (+ audio streaming) server: +UxPlay 1.66: An open\-source AirPlay mirroring (+ audio streaming) server: .SH OPTIONS .TP .B @@ -92,6 +92,16 @@ UxPlay 1.65: An open\-source AirPlay mirroring (+ audio streaming) server: .TP \fB\-nohold\fR Drop current connection when new client connects. .TP +\fB\-restrict\fR Restrict clients to those specified by "-allow clientID". +.IP + Uxplay displays clientID when a client attempts connection. +.IP + Use "-restrict no" for no client restrictions (default). +.PP +\fB\-allow\fR id Permit clientID = id to connect if restrictions are imposed. +.TP +\fB\-block\fR id Always block connections from clientID = id. +.TP \fB\-FPSdata\fR Show video-streaming performance reports sent by client. .TP \fB\-fps\fR n Set maximum allowed streaming framerate, default 30 diff --git a/uxplay.cpp b/uxplay.cpp index 3dddaa4..b15f3e6 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -59,7 +59,7 @@ #include "renderers/video_renderer.h" #include "renderers/audio_renderer.h" -#define VERSION "1.65" +#define VERSION "1.66" #define SECOND_IN_USECS 1000000 #define SECOND_IN_NSECS 1000000000UL @@ -121,6 +121,9 @@ static int max_connections = 2; static unsigned short raop_port; static unsigned short airplay_port; static uint64_t remote_clock_offset = 0; +static std::vector allowed_clients; +static std::vector blocked_clients; +static bool restrict_clients; /* 95 byte png file with a 1x1 white square (single pixel): placeholder for coverart*/ static const unsigned char empty_image[] = { @@ -442,6 +445,11 @@ static void print_info (char *name) { printf("-reset n Reset after 3n seconds client silence (default %d, 0=never)\n", NTP_TIMEOUT_LIMIT); printf("-nc do Not Close video window when client stops mirroring\n"); printf("-nohold Drop current connection when new client connects.\n"); + printf("-restrict Restrict clients to those specified by \"-allow \"\n"); + printf(" UxPlay displays clientID when a client attempts connection\n"); + printf(" Use \"-restrict no\" for no client restrictions (default)\n"); + printf("-allow Permit clientID = to connect if restrictions are imposed\n"); + printf("-block Always block connections from clientID = \n"); printf("-FPSdata Show video-streaming performance reports sent by client.\n"); printf("-fps n Set maximum allowed streaming framerate, default 30\n"); printf("-f {H|V|I}Horizontal|Vertical flip, or both=Inversion=rotate 180 deg\n"); @@ -599,7 +607,24 @@ static void parse_arguments (int argc, char *argv[]) { // Parse arguments for (int i = 1; i < argc; i++) { std::string arg(argv[i]); - if (arg == "-n") { + if (arg == "-allow") { + if (!option_has_value(i, argc, arg, argv[i+1])) exit(1); + i++; + allowed_clients.push_back(argv[i]); + } else if (arg == "-block") { + if (!option_has_value(i, argc, arg, argv[i+1])) exit(1); + i++; + blocked_clients.push_back(argv[i]); + } else if (arg == "-restrict") { + if (i < argc - 1) { + if (strlen(argv[i+1]) == 2 && strncmp(argv[i+1], "no", 2) == 0) { + restrict_clients = false; + i++; + continue; + } + } + restrict_clients = true; + } else if (arg == "-n") { if (!option_has_value(i, argc, arg, argv[i+1])) exit(1); server_name = std::string(argv[++i]); } else if (arg == "-nh") { @@ -1040,6 +1065,29 @@ static int start_dnssd(std::vector hw_addr, std::string name) { return 0; } +static bool check_client(char *deviceid) { + bool ret = false; + int list = allowed_clients.size(); + for (int i = 0; i < list ; i++) { + if (!strcmp(deviceid,allowed_clients[i].c_str())) { + ret = true; + break; + } + } + return ret; +} + +static bool check_blocked_client(char *deviceid) { + bool ret = false; + int list = blocked_clients.size(); + for (int i = 0; i < list ; i++) { + if (!strcmp(deviceid,blocked_clients[i].c_str())) { + ret = true; + break; + } + } + return ret; +} // Server callbacks extern "C" void conn_init (void *cls) { @@ -1079,6 +1127,23 @@ extern "C" void conn_teardown(void *cls, bool *teardown_96, bool *teardown_110) } } +extern "C" void report_client_request(void *cls, char *deviceid, char * model, char *name, bool * admit) { + LOGI("connection request from %s (%s) with deviceID = %s\n", name, model, deviceid); + if (restrict_clients) { + *admit = check_client(deviceid); + if (*admit == false) { + LOGI("client connections have been restricted to those with listed deviceID,\nuse \"-allow %s\" to allow this client to connect.\n", + deviceid); + } + } else { + *admit = true; + } + if (check_blocked_client(deviceid)) { + *admit = false; + LOGI("*** attempt to connect by blocked client (clientID %s): DENIED\n", deviceid); + } +} + extern "C" void audio_process (void *cls, raop_ntp_t *ntp, audio_decode_struct *data) { if (dump_audio) { dump_audio_to_file(data->data, data->data_len, (data->data)[0] & 0xf0); @@ -1259,7 +1324,8 @@ int start_raop_server (unsigned short display[5], unsigned short tcp[3], unsigne raop_cbs.video_report_size = video_report_size; raop_cbs.audio_set_metadata = audio_set_metadata; raop_cbs.audio_set_coverart = audio_set_coverart; - + raop_cbs.report_client_request = report_client_request; + /* set max number of connections = 2 to protect against capture by new client */ raop = raop_init(max_connections, &raop_cbs); if (raop == NULL) {