diff --git a/common.h b/common.h index 3c4fba65..2172e287 100644 --- a/common.h +++ b/common.h @@ -296,7 +296,9 @@ typedef struct { #ifdef CONFIG_AIRPLAY_2 uint64_t airplay_features; + uint32_t airplay_statusflags; char *airplay_device_id; // for the Bonjour advertisement and the GETINFO PList + char *airplay_pin; // non-NULL, 4 char PIN, if required for pairing char *airplay_pk; // "pk" string in the Bonjour advertisement char *airplay_pi; // UUID in the Bonjour advertisement and the GETINFO Plist char *airplay_gid; // UUID in the Bonjour advertisement -- initially the same as the pi diff --git a/rtsp.c b/rtsp.c index ef9a0245..ade66145 100644 --- a/rtsp.c +++ b/rtsp.c @@ -1232,6 +1232,7 @@ void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *r debug(1, "GET /info Stage 1: response plist not created from XML!"); } else { plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features)); + plist_dict_set_item(response_plist, "statusFlags", plist_new_uint(config.airplay_statusflags)); plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id)); plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi)); plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name)); @@ -1256,6 +1257,7 @@ void handle_get_info(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *r plist_from_xml((const char *)plists_get_info_response_xml, plists_get_info_response_xml_len, &response_plist); plist_dict_set_item(response_plist, "features", plist_new_uint(config.airplay_features)); + plist_dict_set_item(response_plist, "statusFlags", plist_new_uint(config.airplay_statusflags)); plist_dict_set_item(response_plist, "deviceID", plist_new_string(config.airplay_device_id)); plist_dict_set_item(response_plist, "pi", plist_new_string(config.airplay_pi)); plist_dict_set_item(response_plist, "name", plist_new_string(config.service_name)); @@ -1416,6 +1418,40 @@ void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused) #endif #ifdef CONFIG_AIRPLAY_2 +void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { + int ret; + uint8_t *body; + size_t body_len; + struct pair_result *result; + debug(2, "Connection %d: pair-verify Content-Length %d", conn->connection_number, + req->contentlength); + + if (!conn->ap2_control_pairing.verify_ctx) { + conn->ap2_control_pairing.verify_ctx = pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, NULL, NULL, config.airplay_device_id); + if (!conn->ap2_control_pairing.verify_ctx) { + debug(1, "Error creating verify context"); + } + } + + ret = pair_verify(&body, &body_len, conn->ap2_control_pairing.verify_ctx, (const uint8_t *)req->content, req->contentlength); + if (ret < 0) { + debug(1, pair_verify_errmsg(conn->ap2_control_pairing.verify_ctx)); + } + + ret = pair_verify_result(&result, conn->ap2_control_pairing.verify_ctx); + if (ret == 0 && result->shared_secret_len > 0) { + conn->ap2_control_pairing.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, result->shared_secret, result->shared_secret_len); + if (!conn->ap2_control_pairing.cipher_ctx) { + debug(1, "Error setting up rtsp control channel ciphering\n"); + } + } + + resp->content = (char *)body; // these will be freed when the data is sent + resp->contentlength = body_len; + msg_add_header(resp, "Content-Type", "application/octet-stream"); + debug_log_rtsp_message(2, "pair-verify response", resp); +} + void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int ret; uint8_t *body; @@ -1425,7 +1461,7 @@ void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *re req->contentlength); if (!conn->ap2_control_pairing.setup_ctx) { - conn->ap2_control_pairing.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, NULL, NULL, NULL, config.airplay_device_id); + conn->ap2_control_pairing.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.airplay_pin, NULL, NULL, config.airplay_device_id); if (!conn->ap2_control_pairing.setup_ctx) { debug(1, "Error creating setup context"); resp->respcode = 451; @@ -1564,6 +1600,8 @@ void handle_post(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; if (strcmp(req->path, "/pair-setup") == 0) { handle_pair_setup(conn, req, resp); + } else if (strcmp(req->path, "/pair-verify") == 0) { + handle_pair_verify(conn, req, resp); } else if (strcmp(req->path, "/fp-setup") == 0) { handle_fp_setup(conn, req, resp); } else { @@ -4062,17 +4100,18 @@ void *rtsp_listen_loop(__attribute((unused)) void *arg) { snprintf(deviceIdString, sizeof(deviceIdString) - 1, "deviceid=%s", config.airplay_device_id); *p++ = deviceIdString; // features is a 64 bit number, least significant 32 bits - char featuresString[64]; uint64_t features_hi = config.airplay_features; features_hi = (features_hi >> 32) & 0xffffffff; uint64_t features_lo = config.airplay_features; features_lo = features_lo & 0xffffffff; + // @mikebrady I don't think the -1 here (and other places) is necessary snprintf(featuresString, sizeof(featuresString) - 1, "features=0x%" PRIx64 ",0x%" PRIx64 "", features_lo, features_hi); - *p++ = featuresString; - *p++ = "flags=0x4"; + char statusflagsString[32]; + snprintf(statusflagsString, sizeof(statusflagsString), "flags=0x%" PRIx32, config.airplay_statusflags); + *p++ = statusflagsString; *p++ = "protovers=1.1"; *p++ = "acl=0"; *p++ = "rsf=0x0"; diff --git a/shairport.c b/shairport.c index 2e9dff2a..303a282a 100644 --- a/shairport.c +++ b/shairport.c @@ -1577,6 +1577,13 @@ int main(int argc, char **argv) { // 496155702020608 this setting here is the source of both the plist features response and the // mDNS string. config.airplay_features = 0x1C340405F4A00; + // Advertised with mDNS and returned with GET /info, see https://openairplay.github.io/airplay-spec/status_flags.html + // 0x4: Audio cable attached, no PIN required (transient pairing), no Homekit access control + // 0x204: Audio cable attached, OneTimePairingRequired + // 0x604: Audio cable attached, OneTimePairingRequired, allow Homekit access control + config.airplay_statusflags = 0x4; + // Set to NULL to work with transient pairing + config.airplay_pin = NULL; // get a device id -- the first non-local MAC address, not necessarily the one in use config.airplay_device_id = get_device_id(); if (config.airplay_device_id) {