/* * RTSP protocol handler. This file is part of Shairport Sync * Copyright (c) James Laird 2013 * Modifications, including those associated with audio synchronization, multithreading and * metadata handling copyright (c) Mike Brady 2014--2026 * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "activity_monitor.h" #include "config.h" #include "utilities/network_utilities.h" #include "utilities/rtsp_message_utilities.h" #ifdef CONFIG_OPENSSL #include #include #endif #ifdef CONFIG_MBEDTLS #include #include #endif #ifdef CONFIG_POLARSSL #include #endif #include "bonjour_strings.h" #include "common.h" #include "player.h" #include "rtp.h" #include "rtsp.h" #ifdef CONFIG_METADATA #include "metadata/core.h" #include "metadata/pc_queue.h" #endif #ifdef CONFIG_METADATA_HUB #include "metadata/hub.h" #endif #ifdef AF_INET6 #define INETx_ADDRSTRLEN INET6_ADDRSTRLEN #else #define INETx_ADDRSTRLEN INET_ADDRSTRLEN #endif #ifdef CONFIG_AIRPLAY_2 #include "ap2_buffered_audio_processor.h" #include "ap2_event_receiver.h" #include "pair_ap/pair.h" #include "plists/get_info_response.h" #include "ptp-utilities.h" #include #ifdef HAVE_LIBPLIST_GE_2_3_0 #define plist_from_memory(plist_data, length, plist) \ plist_from_memory((plist_data), (length), (plist), NULL) #endif #endif #ifdef CONFIG_CONVOLUTION #include "FFTConvolver/convolver.h" #endif #ifdef CONFIG_DBUS_INTERFACE #include "dbus-service.h" #endif #include "mdns.h" #include "utilities/network_utilities.h" enum rtsp_read_request_response { rtsp_read_request_response_ok, rtsp_read_request_response_pending, rtsp_read_request_response_immediate_shutdown_requested, rtsp_read_request_response_bad_packet, rtsp_read_request_response_channel_closed, rtsp_read_request_response_read_error, rtsp_read_request_response_error }; rtsp_conn_info *principal_conn = NULL; rtsp_conn_info **conns = NULL; // always lock this when accessing the principal conn value // use a read lock when consulting and holding it // use a write lock if you want to change it pthread_rwlock_t principal_conn_lock = PTHREAD_RWLOCK_INITIALIZER; // always lock this when accessing the list of connection threads pthread_mutex_t conns_lock = PTHREAD_MUTEX_INITIALIZER; // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) // static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; // static int please_shutdown = 0; // static pthread_t playing_thread = 0; int RTSP_connection_index = 1; // keep track of the threads we have spawned so we can join() them static int nconns = 0; static void track_thread(rtsp_conn_info *conn) { pthread_mutex_lock(&conns_lock); // look for an empty slot first int i = 0; int found = 0; while ((i < nconns) && (found == 0)) { if (conns[i] == NULL) found = 1; else i++; } if (found != 0) { conns[i] = conn; } else { // make space for a new element conns = realloc(conns, sizeof(rtsp_conn_info *) * (nconns + 1)); if (conns) { conns[nconns] = conn; nconns++; } else { die("could not reallocate memory for conns"); } } pthread_mutex_unlock(&conns_lock); } // note: connection numbers start at 1, so an except_this_one value of zero means "all threads" void cancel_all_RTSP_threads(airplay_stream_c stream_category, int except_this_one) { // if the stream category is unspecified_stream_category // all categories are elegible for cancellation // otherwise just the category itself pthread_mutex_lock(&conns_lock); int i; for (i = 0; i < nconns; i++) { if ((conns[i] != NULL) && (conns[i]->running != 0) && (conns[i]->connection_number != except_this_one) && ((conns[i]->airplay_stream_category == stream_category) || (stream_category == unspecified_stream_category))) { pthread_cancel(conns[i]->thread); debug(2, "Connection %d: %s cancelled.", conns[i]->connection_number, get_category_string(conns[i]->airplay_stream_category)); } } for (i = 0; i < nconns; i++) { if ((conns[i] != NULL) && (conns[i]->connection_number != except_this_one) && ((conns[i]->airplay_stream_category == stream_category) || (stream_category == unspecified_stream_category))) { debug(2, "Connection %d: %s joining....", conns[i]->connection_number, get_category_string(conns[i]->airplay_stream_category)); pthread_join(conns[i]->thread, NULL); debug(2, "Connection %d: %s joined.", conns[i]->connection_number, get_category_string(conns[i]->airplay_stream_category)); free(conns[i]); conns[i] = NULL; } } pthread_mutex_unlock(&conns_lock); } int old_connection_count = -1; void cleanup_threads(void) { void *retval; int i; int connection_count = 0; // debug(2, "culling threads."); pthread_mutex_lock(&conns_lock); for (i = 0; i < nconns; i++) { if ((conns[i] != NULL) && (conns[i]->running == 0)) { debug(4, "found RTSP connection thread %d in a non-running state.", conns[i]->connection_number); pthread_join(conns[i]->thread, &retval); debug(4, "Connection %d: deleted in cleanup.", conns[i]->connection_number); free(conns[i]); conns[i] = NULL; } if (conns[i] != NULL) { debug(4, "Airplay Volume for connection %d is %.6f.", conns[i]->connection_number, suggested_volume(conns[i])); connection_count++; } } pthread_mutex_unlock(&conns_lock); if (old_connection_count != connection_count) { if (connection_count == 0) { debug(4, "No active connections."); } else if (connection_count == 1) debug(4, "One active connection."); else debug(4, "%d active connections.", connection_count); old_connection_count = connection_count; } debug(4, "Airplay Volume for new connections is %.6f.", suggested_volume(NULL)); } int terminate_conn(int connection_number) { // this will look for a connection by number, cancel it, join it and delete it. int found = 0; pthread_mutex_lock(&conns_lock); // look for an empty slot first int i = 0; while ((i < nconns) && (found == 0)) { if ((conns[i] != NULL) && (conns[i]->connection_number == connection_number)) { pthread_cancel(conns[i]->thread); pthread_join(conns[i]->thread, NULL); conns[i] = NULL; found = 1; } else i++; } pthread_mutex_unlock(&conns_lock); return found; } // The principal_conn variable points to the connection that // controls the mDNS status and flags and that is potentially // in control of the playing subsystem to output audio to a backend // the principal_conn variable may be NULL // the principal_conn is set by an ANNOUNCE message (Classic AirPlay) or // by the initial SETUP (of a connection, not of a play session) message (AirPlay 2) and cleared // when a session is terminated (AirPlay 2) // In AirPlay 2, only one PTP connection can be live at any time, and it is the principal_conn. // This is because, in AirPlay 2, the principal_conn connection // also has control of the mDNS interface, and thus determines the state of the player as seen by // other devices. // If a conn has play lock, kill the connection completely. void stop_play() { pthread_rwlock_wrlock(&principal_conn_lock); if (principal_conn != NULL) { debug(1, "Connection %d: stop and close this connection.", principal_conn->connection_number); terminate_conn(principal_conn->connection_number); #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay if (principal_conn->airplay_gid) { free(principal_conn->airplay_gid); principal_conn->airplay_gid = NULL; // stop using the client's GID as our GID. } build_bonjour_strings(principal_conn); if (config.service_type == APST_airplay2) { mdns_update(NULL, secondary_txt_records); } #endif principal_conn = NULL; // let it go debug(1, "Connection successfully closed."); } pthread_rwlock_unlock(&principal_conn_lock); } void release_play_lock(rtsp_conn_info *conn) { // no need thread cancellation points in here pthread_rwlock_wrlock(&principal_conn_lock); if ((principal_conn == conn) || (conn == NULL)) { // if we have the player if (principal_conn != NULL) { #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay if (principal_conn->airplay_gid) { free(principal_conn->airplay_gid); principal_conn->airplay_gid = NULL; // stop using the client's GID as our GID. } build_bonjour_strings(principal_conn); if (config.service_type == APST_airplay2) { mdns_update(NULL, secondary_txt_records); } #endif debug(2, "Connection %d: %s released principal_conn.", conn->connection_number, get_category_string(conn->airplay_stream_category)); } principal_conn = NULL; // let it go } pthread_rwlock_unlock(&principal_conn_lock); } // stop the current principal_conn from playing if necessary and make conn the principal_conn. // result of trying to acquire or release the play lock typedef enum { play_lock_released, play_lock_already_released, play_lock_already_acquired, play_lock_acquired_without_breaking_in, play_lock_acquired_by_breaking_in, play_lock_aquisition_failed } play_lock_r; play_lock_r get_play_lock(rtsp_conn_info *conn, int allow_session_interruption) { play_lock_r response = play_lock_aquisition_failed; if (conn != NULL) { debug(2, "Connection %d: %s get_play_lock with allow_session_interruption of %d.", conn->connection_number, get_category_string(conn->airplay_stream_category), allow_session_interruption); pthread_rwlock_wrlock(&principal_conn_lock); pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); if (principal_conn == conn) { debug(2, "Connection %d: %s already has principal_conn.", principal_conn->connection_number, get_category_string(conn->airplay_stream_category)); } else { if (principal_conn != NULL) debug(2, "Connection %d: %s is requested to relinquish principal_conn.", principal_conn->connection_number, get_category_string(conn->airplay_stream_category)); if (conn != NULL) debug(2, "Connection %d: %s request to acquire principal_conn.", conn->connection_number, get_category_string(conn->airplay_stream_category)); } if (principal_conn == conn) { if (conn == NULL) response = play_lock_already_released; else response = play_lock_already_acquired; } else if (principal_conn == NULL) { // already unlocked, and principal conn not NULL principal_conn = conn; #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay #endif response = play_lock_acquired_without_breaking_in; } else if (allow_session_interruption != 0) { // principal conn not NULL, // important -- demote the principal conn before cancelling it /* if (principal_conn->fd > 0) { debug(2, "Connection %d: %s is acquiring play_lock and is forcing termination of Connection " "%d %s. Closing " "RTSP connection socket %d: " "from %s:%u to self at " "%s:%u.", conn->connection_number, get_category_string(conn->airplay_stream_category), principal_conn->connection_number, get_category_string(principal_conn->airplay_stream_category), principal_conn->fd, principal_conn->client_ip_string, principal_conn->client_rtsp_port, principal_conn->self_ip_string, principal_conn->self_rtsp_port); safe_socket_close(&principal_conn->fd); usleep(1000000); } */ debug(4, "Connection %d: about to be terminated.", principal_conn->connection_number); rtsp_conn_info *previous_principal_conn = principal_conn; principal_conn = conn; // make the conn the new principal_conn terminate_conn(previous_principal_conn->connection_number); debug(4, "Connection successfully terminated."); if (principal_conn == NULL) { #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags &= (0xffffffff - (1 << 11)); // DeviceSupportsRelay if (conn->airplay_gid) { free(conn->airplay_gid); conn->airplay_gid = NULL; // stop using the client's GID as our GID. } build_bonjour_strings(conn); if (config.service_type == APST_airplay2) { mdns_update(NULL, secondary_txt_records); } #endif response = play_lock_released; } else { #ifdef CONFIG_AIRPLAY_2 config.airplay_statusflags |= (1 << 11); // DeviceSupportsRelay #endif response = play_lock_acquired_by_breaking_in; } // usleep(1000000); // don't know why this delay is needed. } if ((principal_conn != NULL) && (response != play_lock_already_acquired)) debug(2, "Connection %d: %s has principal_conn.", conn->connection_number, get_category_string(conn->airplay_stream_category)); pthread_cleanup_pop(1); // release the principal_conn lock } else { debug(1, "Connection %d: %s get_play_lock must have a non-NULL conn.", conn->connection_number, get_category_string(conn->airplay_stream_category)); } return response; } // park a null at the line ending, and return the next line pointer // accept \r, \n, or \r\n static char *nextline(char *in, int inbuf) { char *out = NULL; while (inbuf) { if (*in == '\r') { *in++ = 0; out = in; inbuf--; } if ((*in == '\n') && (inbuf)) { *in++ = 0; out = in; } if (out) break; in++; inbuf--; } return out; } #ifdef CONFIG_AIRPLAY_2 static void buf_add(sized_buffer *buf, uint8_t *in, size_t in_len) { if (buf->length + in_len > buf->size) { buf->size = buf->length + in_len + 2048; // Extra headroom to avoid future memcpy's buf->data = realloc(buf->data, buf->size); } memcpy(buf->data + buf->length, in, in_len); buf->length += in_len; } static void buf_drain(sized_buffer *buf, ssize_t len) { if (len < 0 || (size_t)len >= buf->length) { free(buf->data); memset(buf, 0, sizeof(sized_buffer)); return; } memmove(buf->data, buf->data + len, buf->length - len); buf->length -= len; } static size_t buf_remove(sized_buffer *buf, uint8_t *out, size_t out_len) { size_t bytes = (buf->length > out_len) ? out_len : buf->length; memcpy(out, buf->data, bytes); buf_drain(buf, bytes); return bytes; } ssize_t read_encrypted(int fd, pair_cipher_bundle *ctx, void *buf, size_t count) { ssize_t response = 0; // If there is leftover decoded content from the last pass just return that if (ctx->plaintext_read_buffer.length > 0) { response = buf_remove(&ctx->plaintext_read_buffer, buf, count); } else { // Otherwise read stuff in... uint8_t in[4096]; uint8_t *plain = NULL; // may be allocated and reallocated by pair_decrypt pthread_cleanup_push(malloc_cleanup, &plain); size_t plain_len = 0; do { response = read(fd, in, sizeof(in)); if (response > 0) { buf_add(&ctx->encrypted_read_buffer, in, response); ssize_t consumed = pair_decrypt(&plain, &plain_len, ctx->encrypted_read_buffer.data, ctx->encrypted_read_buffer.length, ctx->cipher_ctx); if (consumed < 0) { debug(1, "read_encrypted: abnormal exit from pair_decrypt: %zd.", consumed); response = -1; } else { buf_drain(&ctx->encrypted_read_buffer, consumed); } } } while ((plain_len == 0) && (response > 0)); if (response >= 0) { buf_add(&ctx->plaintext_read_buffer, plain, plain_len); response = buf_remove(&ctx->plaintext_read_buffer, buf, count); } pthread_cleanup_pop(1); } return response; } ssize_t write_encrypted(int fd, pair_cipher_bundle *ctx, const void *buf, size_t count) { uint8_t *encrypted; size_t encrypted_len; ssize_t ret = pair_encrypt(&encrypted, &encrypted_len, buf, count, ctx->cipher_ctx); if (ret < 0) { debug(1, "%s", pair_cipher_errmsg(ctx->cipher_ctx)); return -1; } size_t remain = encrypted_len; // debug(1, "write to the \"%s\" channel", ctx->description); // debug_print_buffer(1, (void *)buf, count); // debug(1, "write encrypted:"); // debug_print_buffer(1, encrypted, encrypted_len); while (remain > 0) { ssize_t wrote = write(fd, encrypted + (encrypted_len - remain), remain); if (wrote <= 0) { free(encrypted); return wrote; } remain -= wrote; } free(encrypted); return count; } #endif ssize_t read_from_rtsp_connection(rtsp_conn_info *conn, void *buf, size_t count) { if (count == 0) debug(1, "asking to read zero bytes!"); ssize_t result = 0; // closed if (conn->fd > 0) { #ifdef CONFIG_AIRPLAY_2 if (conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { conn->ap2_pairing_context.control_cipher_bundle.is_encrypted = 1; result = read_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, buf, count); } else { result = read(conn->fd, buf, count); } #else result = read(conn->fd, buf, count); // In AP1, the RTSP connection is closed in this way, so it's not unexpected #endif if ((result <= 0) && (errno != 0)) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(3, "read_from_rtsp_connection error %d \"%s\" attempting to read up to %zu bytes.", errno, errorstring, count); } } else { debug(1, "Connection %d: attempt to read from a closed RTSP connection.", conn->connection_number); } return result; } #ifdef CONFIG_AIRPLAY_2 void set_client_as_ptp_clock(rtsp_conn_info *conn) { char timing_list_message[4096] = ""; strncat(timing_list_message, "T ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); strncat(timing_list_message, (const char *)&conn->client_ip_string, sizeof(timing_list_message) - 1 - strlen(timing_list_message)); ptp_send_control_message_string(timing_list_message); } #endif enum rtsp_read_request_response rtsp_read_request(rtsp_conn_info *conn, rtsp_message **the_packet) { enum rtsp_read_request_response reply = rtsp_read_request_response_pending; *the_packet = NULL; // need this for error handling ssize_t buflen = 4096; #ifdef CONFIG_METADATA if ((config.metadata_enabled != 0) && (config.get_coverart != 0)) buflen = 1024 * 256; // big enough for typical picture data, which will be base64 encoded #endif int release_buffer = 0; // on exit, don't deallocate the buffer if everything was okay char *buf = malloc(buflen + 1); // add a NUL at the end if (buf == NULL) { debug(1, "Connection %d: rtsp_read_request: can't get a buffer.", conn->connection_number); reply = rtsp_read_request_response_error; } else { debug(4, "buf is allocated at 0x%" PRIxPTR ".", (uintptr_t)buf); pthread_cleanup_push(malloc_cleanup, &buf); ssize_t nread; ssize_t inbuf = 0; int msg_size = -1; while ((msg_size < 0) && (reply == rtsp_read_request_response_pending)) { if (conn->stop != 0) { debug(3, "Connection %d: shutdown requested by client.", conn->connection_number); reply = rtsp_read_request_response_immediate_shutdown_requested; // goto shutdown; } else { nread = read_from_rtsp_connection(conn, buf + inbuf, buflen - inbuf); if (nread <= 0) { // ETIMEDOUT seems to be from the keepalive having failed. // But it does seem as it it's not always sent, e.g. if another read() is outstanding (?) // EAGAIN seems to be simply from the read() request timing out. if (errno == ETIMEDOUT) { debug(1, "Connection %d has disappeared. As Yeats almost said, \"Too long a " "silence / can make a stone " "of the heart\". ETIMEOUT", conn->connection_number); reply = rtsp_read_request_response_immediate_shutdown_requested; } else if (nread == 0) { if (errno == 0) { // a blocking read that returns zero means eof -- implies connection closed by client debug(2, "Connection %d RTSP closed by client.", conn->connection_number); } else { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(2, "Connection %d RTSP port closed by client with error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); } safe_socket_close(&conn->fd); // close it from our end too... reply = rtsp_read_request_response_channel_closed; } else { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); reply = rtsp_read_request_response_read_error; } // goto shutdown; } else { /* // this outputs the message received { void *pt = malloc(nread+1); memset(pt, 0, nread+1); memcpy(pt, buf + inbuf, nread); debug(1, "Incoming string on port: \"%s\"",pt); free(pt); } */ inbuf += nread; char *next; while ((reply == rtsp_read_request_response_pending) && (msg_size < 0) && (next = nextline(buf, inbuf))) { msg_size = msg_handle_line(the_packet, buf); if (!(*the_packet)) { debug(1, "Connection %d: rtsp_read_request can't find an RTSP header.", conn->connection_number); reply = rtsp_read_request_response_bad_packet; // goto shutdown; } else { inbuf -= next - buf; if (inbuf) memmove(buf, next, inbuf); } } } } } if ((reply == rtsp_read_request_response_pending) && (msg_size > 0)) { // more input is needed... uint64_t threshold_time = get_absolute_time_in_ns() + ((uint64_t)15000000000); // i.e. fifteen seconds from now int warning_message_sent = 0; if (msg_size > buflen) { buf = realloc(buf, msg_size + 1); if (buf == NULL) { warn("Connection %d: too much content.", conn->connection_number); reply = rtsp_read_request_response_error; // goto shutdown; } else { debug(4, "buf is reallocated at 0x%" PRIxPTR ".", (uintptr_t)buf); buflen = msg_size; } } // const size_t max_read_chunk = 1024 * 1024 / 16; while ((inbuf < msg_size) && (reply == rtsp_read_request_response_pending)) { // we are going to read the stream in chunks and time how long it takes to // do so. // If it's taking too long, (and we find out about it), we will send an // error message as // metadata if (warning_message_sent == 0) { uint64_t time_now = get_absolute_time_in_ns(); if (time_now > threshold_time) { // it's taking too long debug(1, "Error receiving metadata from source -- transmission seems " "to be stalled."); #ifdef CONFIG_METADATA send_ssnc_metadata('stal', NULL, 0, 1); #endif warning_message_sent = 1; } } if (conn->stop != 0) { debug(1, "RTSP shutdown requested."); reply = rtsp_read_request_response_immediate_shutdown_requested; // goto shutdown; } else { size_t read_chunk = msg_size - inbuf; // if (read_chunk > max_read_chunk) // read_chunk = max_read_chunk; // usleep(80000); // wait about 80 milliseconds between reads of up to max_read_chunk nread = read_from_rtsp_connection(conn, buf + inbuf, read_chunk); if (nread <= 0) { // ETIMEDOUT seems to be from the keepalive having failed. // But it does seem as it it's not always sent, e.g. if another read() is outstanding // (?) EAGAIN seems to be simply from the read() request timing out. if (errno == ETIMEDOUT) { debug(1, "Connection %d has disappeared. As Yeats almost said, \"Too long a " "silence / can make a stone " "of the heart\". ETIMEOUT", conn->connection_number); reply = rtsp_read_request_response_immediate_shutdown_requested; // Note: the socket will be closed when the thread exits } else if (nread == 0) { if (errno == 0) { // a blocking read that returns zero means eof -- implies connection closed by // client debug(1, "Connection %d closed by client.", conn->connection_number); } else { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "Connection %d closed by client with error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); } reply = rtsp_read_request_response_channel_closed; // Note: the socket will be closed when the thread exits } else { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "Connection %d: rtsp_read_request_response_read_error %d: \"%s\".", conn->connection_number, errno, (char *)errorstring); reply = rtsp_read_request_response_read_error; } // goto shutdown; } else { inbuf += nread; } } } } if (reply == rtsp_read_request_response_pending) { reply = rtsp_read_request_response_ok; rtsp_message *msg = *the_packet; msg->contentlength = inbuf; msg->content = buf; char *jp = inbuf + buf; *jp = '\0'; *the_packet = msg; } // shutdown: if (reply != rtsp_read_request_response_ok) { if (*the_packet != NULL) { debug(3, "Freeing the_packet"); msg_free(the_packet); } release_buffer = 1; // allow the buffer to be released } pthread_cleanup_pop(release_buffer); } return reply; } int msg_write_response(rtsp_conn_info *conn, rtsp_message *resp) { char pkt[4096]; int pktfree = sizeof(pkt); char *p = pkt; int n; unsigned int i; struct response_t { int code; char *string; }; struct response_t responses[] = {{200, "OK"}, {400, "Bad Request"}, {403, "Unauthorized"}, {404, "Not Found"}, {451, "Unavailable"}, {456, "Header Field Not Valid for Resource"}, {470, "Connection Authorization Required"}, {500, "Internal Server Error"}, {501, "Not Implemented"}}; // 451 is really "Unavailable For Legal Reasons"! int found = 0; char *respcode_text = "Unauthorized"; for (i = 0; i < sizeof(responses) / sizeof(struct response_t); i++) { if (resp->respcode == responses[i].code) { found = 1; respcode_text = responses[i].string; } } if (found == 0) debug(1, "can't find text for response code %d. Using \"%s\" instead.", resp->respcode, respcode_text); n = snprintf(p, pktfree, "RTSP/1.0 %d %s\r\n", resp->respcode, respcode_text); pktfree -= n; p += n; for (i = 0; i < resp->nheaders; i++) { // debug(3, " %s: %s.", resp->name[i], resp->value[i]); n = snprintf(p, pktfree, "%s: %s\r\n", resp->name[i], resp->value[i]); pktfree -= n; p += n; if (pktfree <= 1024) { debug(1, "Attempted to write overlong RTSP packet 1"); return -1; } } // Here, if there's content, write the Content-Length header ... // if (resp->contentlength) { { // debug(2, "Responding with content of length %d", resp->contentlength); n = snprintf(p, pktfree, "Content-Length: %d\r\n", resp->contentlength); pktfree -= n; p += n; if (pktfree <= 1024) { debug(1, "Attempted to write overlong RTSP packet 2"); return -2; } } n = snprintf(p, pktfree, "\r\n"); pktfree -= n; p += n; if (resp->contentlength) { memcpy(p, resp->content, resp->contentlength); pktfree -= resp->contentlength; p += resp->contentlength; } if (pktfree <= 1024) { debug(1, "Attempted to write overlong RTSP packet 3"); return -3; } // here, if the link is encrypted, better do it #ifdef CONFIG_AIRPLAY_2 ssize_t reply; if (conn->ap2_pairing_context.control_cipher_bundle.is_encrypted) { reply = write_encrypted(conn->fd, &conn->ap2_pairing_context.control_cipher_bundle, pkt, p - pkt); } else { reply = write(conn->fd, pkt, p - pkt); } #else ssize_t reply = write(conn->fd, pkt, p - pkt); #endif if (reply == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "msg_write_response error %d: \"%s\".", errno, (char *)errorstring); return -4; } if (reply != p - pkt) { debug(1, "msg_write_response error -- requested bytes: %zd not fully written: %zd.", p - pkt, reply); return -5; } return 0; } #ifdef CONFIG_AIRPLAY_2 void handle_record_2(rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: RECORD (AP2) on %s", conn->connection_number, get_category_string(conn->airplay_stream_category)); debug_log_rtsp_message_conn(conn, 2, "RECORD (AP2) incoming message", req); msg_add_header(resp, "Audio-Latency", "0"); resp->respcode = 200; } #endif void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(1, "Connection %d: RECORD", conn->connection_number); if ((conn != NULL) && (principal_conn == conn)) { if (conn->player_thread) warn("Connection %d: RECORD: Duplicate RECORD message -- ignored", conn->connection_number); else { activity_monitor_signify_activity(1); player_play(conn); // the thread better be 0 } resp->respcode = 200; // I think this is for telling the client what the absolute minimum latency // actually is, // and when the client specifies a latency, it should be added to this figure. // Thus, [the old version of] AirPlay's latency figure of 77175, when added to 11025 gives you // exactly 88200 // and iTunes' latency figure of 88553, when added to 11025 gives you 99578, // pretty close to the 99400 we guessed. msg_add_header(resp, "Audio-Latency", "11025"); char *p; uint32_t rtptime = 0; char *hdr = msg_get_header(req, "RTP-Info"); if (hdr) { // debug(1,"FLUSH message received: \"%s\".",hdr); // get the rtp timestamp p = strstr(hdr, "rtptime="); if (p) { p = strchr(p, '='); if (p) { rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 // rtptime--; // debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime); player_flush(rtptime, conn); } } } } else { warn("Connection %d RECORD received without having the player (no ANNOUNCE?)", conn->connection_number); resp->respcode = 451; } } #ifdef CONFIG_AIRPLAY_2 int add_pstring_to_malloc(const char *s, void **allocation, size_t *size) { int response = 0; void *p = *allocation; if (p == NULL) { p = malloc(strlen(s) + 1); if (p == NULL) { debug(1, "error allocating memory"); } else { *allocation = p; *size = *size + strlen(s) + 1; uint8_t *b = (uint8_t *)p; *b = strlen(s); p = p + 1; memcpy(p, s, strlen(s)); response = 1; } } else { p = realloc(p, *size + strlen(s) + 1); if (p == NULL) { // assuming we never allocate a zero byte space debug(1, "error reallocating memory"); } else { *allocation = p; uint8_t *b = (uint8_t *)p + *size; *b = strlen(s); p = p + *size + 1; memcpy(p, s, strlen(s)); *size = *size + strlen(s) + 1; response = 1; } } return response; } void generateTxtDataValueInfo(rtsp_conn_info *conn, void **response, size_t *responseLength) { void *qualifier_response_data = NULL; size_t qualifier_response_data_length = 0; char localString[256]; if (add_pstring_to_malloc("acl=0", &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc("btaddr=00:00:00:00:00:00", &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "deviceid=%s", config.airplay_device_id), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "fex=%s", config.airplay_fex), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); /* uint64_t features_hi = 0x0001C340445D0A00L; features_hi = (features_hi >> 32) & 0xffffffff; uint64_t features_lo = 0x0001C340445D0A00L; features_lo = features_lo & 0xffffffff; */ 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; if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "features=0x%" PRIX64 ",0x%" PRIX64 "", features_lo, features_hi), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); // if (add_pstring_to_malloc("rsf=0x0", &qualifier_response_data, // &qualifier_response_data_length) == 0) // debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "flags=0x%x", config.airplay_statusflags), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if ((conn != NULL) && (conn->airplay_gid != 0)) { snprintf(localString, sizeof(localString), "gid=%s", conn->airplay_gid); } else { snprintf(localString, sizeof(localString), "gid=%s", config.airplay_pi); } if (add_pstring_to_malloc(localString, &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); int gcgl = 0; if (conn != NULL) gcgl = conn->groupContainsGroupLeader; // should have igl here; if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "igl=%d", 0), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "gcgl=%d", gcgl), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "pgid=%s", config.airplay_pgid), &qualifier_response_data, &qualifier_response_data_length) == 0) if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "pgcgl=%d", gcgl), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "model=%s", config.model), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc("protovers=1.1", &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "pi=%s", config.airplay_pi), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "psi=%s", config.airplay_psi), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "pk=%s", config.pk_string), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc( bnprintf(localString, sizeof(localString), "srcvers=%s", config.srcvers), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc(bnprintf(localString, sizeof(localString), "osvers=%s", config.osvers), &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); if (add_pstring_to_malloc("vv=2", &qualifier_response_data, &qualifier_response_data_length) == 0) debug(1, "Problem"); *response = qualifier_response_data; *responseLength = qualifier_response_data_length; } plist_t generateInfoPlist(rtsp_conn_info *conn) { plist_t response_plist = NULL; plist_from_memory((const char *)get_info_response_plist, get_info_response_plist_len, &response_plist); if (response_plist == NULL) { debug(1, "generateInfoPlist plist not created!"); } else { pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); // debug(1,"qualifier_response_data_length: %u.", qualifier_response_data_length); plist_dict_set_item(response_plist, "psi", plist_new_string(config.airplay_psi)); plist_dict_set_item(response_plist, "featuresEx", plist_new_string(config.airplay_fex)); 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)); plist_dict_set_item(response_plist, "model", plist_new_string(config.model)); plist_dict_set_item(response_plist, "pk", plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk))); char senderAddress[256]; snprintf(senderAddress, sizeof(senderAddress), "%s:%u", conn->client_ip_string, conn->client_rtsp_port); plist_dict_set_item(response_plist, "senderAddress", plist_new_string(senderAddress)); plist_dict_set_item(response_plist, "initialVolume", plist_new_real(suggested_volume(conn))); plist_dict_set_item(response_plist, "sourceVersion", plist_new_string(config.srcvers)); pthread_cleanup_pop(1); // release the principal_conn lock // Create a dictionary of supported formats for the bufferStream uint64_t bufferStreamFormats = 0L; // bufferStreamFormats = 0xF7FE000E00000000; // don't know what these do (from the HPm) plist_t supported_formats_plist = plist_new_dict(); if (supported_formats_plist != NULL) { plist_dict_set_item(supported_formats_plist, "audioStream", plist_new_uint(21235712)); { bufferStreamFormats |= 0x00000400000L; // AAC-LC/44.1K/F24/2 bufferStreamFormats |= 0x40000; // ALAC/44100/S16/2 } { bufferStreamFormats |= 0x00000200000L; // ALAC/48K/F24/2 bufferStreamFormats |= 0x00000800000L; // AAC-LC/48K/F24/2 } { if (config.eight_channel_layout != 0) bufferStreamFormats |= 0x10000000000L; // AAC-LC/48K/F24/7.1 if (config.six_channel_layout != 0) bufferStreamFormats |= 0x08000000000L; // AAC-LC/48K/F24/5.1 } plist_dict_set_item(supported_formats_plist, "bufferStream", plist_new_uint(bufferStreamFormats)); debug(4, "bufferedStream formats: 0x%" PRIX64 ".", bufferStreamFormats); plist_dict_set_item(response_plist, "supportedFormats", supported_formats_plist); } } return response_plist; } void handle_get_info(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message(3, "GET /info:", req); if (rtsp_message_contains_plist(req)) { // it's stage one // get version of AirPlay -- it might be too old. Not using it yet. char *hdr = msg_get_header(req, "User-Agent"); if (hdr) { if (strstr(hdr, "AirPlay/") == hdr) { hdr = hdr + strlen("AirPlay/"); // double airplay_version = 0.0; // airplay_version = atof(hdr); debug(3, "Connection %d: GET_INFO: Source AirPlay Version is: %s.", conn->connection_number, hdr); } } // in Stage 1, look for the DACP and Active-Remote char *ar = msg_get_header(req, "Active-Remote"); if (ar) { debug(3, "Connection %d: GET /info -- Active-Remote string seen: \"%s\".", conn->connection_number, ar); // get the active remote if (conn->dacp_active_remote) // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); #endif } else { debug(3, "Connection %d: GET /info -- doesn't include Active-Remote information.", conn->connection_number); if (conn->dacp_active_remote) { // this is in case GET /info was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } } ar = msg_get_header(req, "DACP-ID"); if (ar) { debug(3, "Connection %d: GET /info -- DACP-ID string seen: \"%s\".", conn->connection_number, ar); if (conn->dacp_id) // this is in case SETUP was previously called free(conn->dacp_id); conn->dacp_id = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); #endif } else { debug(3, "Connection %d: GET /info -- doesn't include DACP-ID string information.", conn->connection_number); if (conn->dacp_id) { // this is in case GET /info was previously called free(conn->dacp_id); conn->dacp_id = NULL; } } plist_t info_plist = NULL; plist_from_memory(req->content, req->contentlength, &info_plist); plist_t qualifier = plist_dict_get_item(info_plist, "qualifier"); if (qualifier == NULL) { debug(1, "GET /info Stage 1: plist->qualifier was NULL"); goto user_fail; } if (plist_array_get_size(qualifier) < 1) { debug(1, "GET /info Stage 1: plist->qualifier array length < 1"); goto user_fail; } plist_t qualifier_array_value = plist_array_get_item(qualifier, 0); char *qualifier_array_val_cstr; plist_get_string_val(qualifier_array_value, &qualifier_array_val_cstr); if (qualifier_array_val_cstr == NULL) { debug(1, "GET /info Stage 1: first item in qualifier array not a string"); goto user_fail; } debug(3, "GET /info Stage 1: qualifier: %s", qualifier_array_val_cstr); plist_free(info_plist); free(qualifier_array_val_cstr); plist_t response_plist = generateInfoPlist(conn); if (response_plist == NULL) goto user_fail; void *txtData = NULL; size_t txtDataLength = 0; generateTxtDataValueInfo(conn, &txtData, &txtDataLength); plist_dict_set_item(response_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength)); free(txtData); plist_to_bin(response_plist, &resp->content, &resp->contentlength); if (resp->contentlength == 0) debug(1, "GET /info Stage 1: response bplist not created!"); plist_free(response_plist); /* free(qualifier_response_data); */ msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); resp->respcode = 200; debug_log_rtsp_message(3, "GET /info Stage 1 Response:", resp); return; user_fail: resp->respcode = 400; return; } else { // stage two plist_t response_plist = generateInfoPlist(conn); if (response_plist == NULL) goto user_fail; void *txtData = NULL; size_t txtDataLength = 0; generateTxtDataValueInfo(conn, &txtData, &txtDataLength); plist_dict_set_item(response_plist, "txtAirPlay", plist_new_data(txtData, txtDataLength)); free(txtData); plist_to_bin(response_plist, &resp->content, &resp->contentlength); plist_free(response_plist); msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); resp->respcode = 200; debug_log_rtsp_message(3, "GET /info Stage 2 Response", resp); return; } } void handle_flushbuffered(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: FLUSHBUFFERED %s : Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, "FLUSHBUFFERED request", req); uint64_t flushFromSeq = 0; uint64_t flushFromTS = 0; uint64_t flushUntilSeq = 0; uint64_t flushUntilTS = 0; int flushFromValid = 0; plist_t messagePlist = plist_from_rtsp_content(req); if (messagePlist != NULL) { plist_t item = plist_dict_get_item(messagePlist, "flushFromSeq"); if (item == NULL) { debug(4, "Can't find a flushFromSeq"); } else { flushFromValid = 1; plist_get_uint_val(item, &flushFromSeq); debug(3, "flushFromSeq is %" PRId64 ".", flushFromSeq & 0x7fffff); } item = plist_dict_get_item(messagePlist, "flushFromTS"); if (item == NULL) { if (flushFromValid != 0) debug(1, "flushFromSeq without flushFromTS!"); else debug(4, "Can't find a flushFromTS"); } else { plist_get_uint_val(item, &flushFromTS); if (flushFromValid == 0) debug(1, "flushFromTS without flushFromSeq!"); debug(3, "flushFromTS is %" PRId64 ".", flushFromTS); } item = plist_dict_get_item(messagePlist, "flushUntilSeq"); if (item == NULL) { debug(1, "Can't find the flushUntilSeq"); } else { plist_get_uint_val(item, &flushUntilSeq); debug(4, "flushUntilSeq is %" PRId64 ".", flushUntilSeq & 0x7fffff); } item = plist_dict_get_item(messagePlist, "flushUntilTS"); if (item == NULL) { debug(1, "Can't find the flushUntilTS"); } else { plist_get_uint_val(item, &flushUntilTS); debug(4, "flushUntilTS is %" PRId64 ".", flushUntilTS); } pthread_mutex_lock(&conn->flush_mutex); if (flushFromValid == 0) { // an immediate flush is requested conn->ap2_immediate_flush_requested = 1; conn->ap2_immediate_flush_until_sequence_number = flushUntilSeq & 0x7fffff; conn->ap2_immediate_flush_until_rtp_timestamp = flushUntilTS; debug(2, "Connection %d: immediate flush request created: flushUntilTS: %" PRIu64 ", flushUntilSeq: %" PRIu64 ".", conn->connection_number, flushUntilTS, flushUntilSeq & 0x7fffff); conn->ap2_play_enabled = 0; // stop trying to play audio // ptp_send_control_message_string( // "P"); // "P"ause signify clock no longer valid and will be restarted by a subsequent // play // debug(1, "FLUSHBUFFERED calling reset_ptp_anchor_info"); reset_ptp_anchor_info( conn); // stop the clock for an immediate flush until it is restarted using SETRATEANCHORI } else { // look for a record slot that isn't in use unsigned int i = 0; unsigned int found = 0; while ((i < MAX_DEFERRED_FLUSH_REQUESTS) && (found == 0)) { if (conn->ap2_deferred_flush_requests[i].inUse == 0) { found = 1; } else { i++; } } if (found != 0) { conn->ap2_deferred_flush_requests[i].inUse = 1; conn->ap2_deferred_flush_requests[i].active = 0; conn->ap2_deferred_flush_requests[i].flushFromSeq = flushFromSeq & 0x7fffff; conn->ap2_deferred_flush_requests[i].flushFromTS = flushFromTS; conn->ap2_deferred_flush_requests[i].flushUntilSeq = flushUntilSeq & 0x7fffff; conn->ap2_deferred_flush_requests[i].flushUntilTS = flushUntilTS; debug(2, "Connection %d: deferred flush request created: flushFromSeq: %" PRIu64 ", flushUntilSeq: %" PRIu64 ".", conn->connection_number, flushFromSeq, flushUntilSeq); } else { debug(1, "Connection %d: no more room for deferred flush request records", conn->connection_number); } } pthread_mutex_unlock(&conn->flush_mutex); plist_free(messagePlist); } resp->respcode = 200; } void handle_setrate(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(1, "Connection %d: SETRATE %s : Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(1, "SETRATE request -- unimplemented", req); resp->respcode = 501; // Not Implemented } void handle_unimplemented_ap1(__attribute((unused)) rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message(1, "request not recognised for AirPlay 1 operation", req); resp->respcode = 501; } void handle_setrateanchori(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: SETRATEANCHORI %s :: Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, "SETRATEANCHORI", req); plist_t messagePlist = plist_from_rtsp_content(req); if (messagePlist != NULL) { pthread_cleanup_push(plist_cleanup, (void *)messagePlist); plist_t item = plist_dict_get_item(messagePlist, "networkTimeSecs"); if (item != NULL) { plist_t item_2 = plist_dict_get_item(messagePlist, "networkTimeTimelineID"); if (item_2 == NULL) { debug(1, "Can't identify the Clock ID of the player."); } else { uint64_t nid; plist_get_uint_val(item_2, &nid); debug(3, "networkTimeTimelineID \"%" PRIx64 "\".", nid); conn->networkTimeTimelineID = nid; } uint64_t networkTimeSecs; plist_get_uint_val(item, &networkTimeSecs); debug(3, "anchor networkTimeSecs is %" PRIu64 ".", networkTimeSecs); item = plist_dict_get_item(messagePlist, "networkTimeFrac"); uint64_t networkTimeFrac; plist_get_uint_val(item, &networkTimeFrac); debug(3, "anchor networkTimeFrac is 0%" PRIu64 ".", networkTimeFrac); // it looks like the networkTimeFrac is a fraction where the msb is work 1/2, the // next 1/4 and so on // now, convert the network time and fraction into nanoseconds networkTimeFrac = networkTimeFrac >> 32; // reduce precision to about 1/4 nanosecond networkTimeFrac = networkTimeFrac * 1000000000; networkTimeFrac = networkTimeFrac >> 32; // we should now be left with the ns networkTimeSecs = networkTimeSecs * 1000000000; // turn the whole seconds into ns uint64_t anchorTimeNanoseconds = networkTimeSecs + networkTimeFrac; debug(3, "anchorTimeNanoseconds looks like %" PRIu64 ".", anchorTimeNanoseconds); item = plist_dict_get_item(messagePlist, "rtpTime"); uint64_t rtpTime; plist_get_uint_val(item, &rtpTime); // debug(1, "anchor rtpTime is %" PRId64 ".", rtpTime); uint32_t anchorRTPTime = rtpTime; // Store the raw anchor; apply the latency offset when it is used. set_ptp_anchor_info(conn, conn->networkTimeTimelineID, anchorRTPTime, anchorTimeNanoseconds); } item = plist_dict_get_item(messagePlist, "rate"); if (item != NULL) { uint64_t rate; plist_get_uint_val(item, &rate); debug(3, "anchor rate 0x%016" PRIx64 ".", rate); pthread_mutex_lock_and_cleanup_push(&conn->flush_mutex); conn->ap2_rate = rate; if ((rate & 1) != 0) { ptp_send_control_message_string( "B"); // signify clock dependability period is "B"eginning (or resuming) debug(2, "Connection %d: SETRATEANCHORI Start playing, with anchor clock %" PRIx64 ".", conn->connection_number, conn->networkTimeTimelineID); activity_monitor_signify_activity(1); #ifdef CONFIG_METADATA send_ssnc_metadata('pres', NULL, 0, 1); // resume -- contains cancellation points #endif conn->ap2_play_enabled = 1; } else { reset_ptp_anchor_info(conn); ptp_send_control_message_string("P"); // signify play is "P"ausing debug(2, "Connection %d: SETRATEANCHORI Pause playing.", conn->connection_number); conn->ap2_play_enabled = 0; activity_monitor_signify_activity(0); #ifdef CONFIG_METADATA send_ssnc_metadata('paus', NULL, 0, 1); // pause -- contains cancellation points #endif // if (config.output->stop) { // debug(1, "Connection %d: SETRATEANCHORI would stop the output backend.", // conn->connection_number); config.output->stop(); // } } pthread_cleanup_pop(1); // unlock the conn->flush_mutex } pthread_cleanup_pop(1); // plist_free the messagePlist; } else { debug(1, "missing plist!"); } resp->respcode = 200; } void handle_get(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: GET %s :: Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, "GET request", req); if (strcmp(req->path, "/info") == 0) { handle_get_info(conn, req, resp); } else { debug(1, "Unhandled GET, path \"%s\".", req->path); resp->respcode = 501; // Not Implemented } } #else void handle_get(__attribute((unused)) rtsp_conn_info *conn, __attribute((unused)) rtsp_message *req, __attribute((unused)) rtsp_message *resp) { debug(1, "Connection %d: GET %s Content-Length %d", conn->connection_number, req->path, req->contentlength); resp->respcode = 501; // 501 is not implemented } void handle_post(rtsp_conn_info *conn, rtsp_message *req, __attribute((unused)) rtsp_message *resp) { debug(1, "Connection %d: AP1 POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); resp->respcode = 501; // 501 is not implemented } #endif #ifdef CONFIG_AIRPLAY_2 struct pairings { char device_id[PAIR_AP_DEVICE_ID_LEN_MAX]; uint8_t public_key[32]; struct pairings *next; } *pairings; static struct pairings *pairing_find(const char *device_id) { for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { if (strcmp(device_id, pairing->device_id) == 0) return pairing; } return NULL; } static void pairing_add(uint8_t public_key[32], const char *device_id) { struct pairings *pairing = calloc(1, sizeof(struct pairings)); snprintf(pairing->device_id, sizeof(pairing->device_id), "%s", device_id); memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); pairing->next = pairings; pairings = pairing; } static void pairing_remove(struct pairings *pairing) { if (pairing == pairings) { pairings = pairing->next; } else { struct pairings *iter; for (iter = pairings; iter && (iter->next != pairing); iter = iter->next) ; /* EMPTY */ if (iter) iter->next = pairing->next; } free(pairing); } static int pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg __attribute__((unused))) { debug(1, "pair-add cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); if (pairing) { memcpy(pairing->public_key, public_key, sizeof(pairing->public_key)); return 0; } pairing_add(public_key, device_id); return 0; } static int pairing_remove_cb(uint8_t public_key[32] __attribute__((unused)), const char *device_id, void *cb_arg __attribute__((unused))) { debug(1, "pair-remove cb for %s", device_id); struct pairings *pairing = pairing_find(device_id); if (!pairing) { debug(1, "pair-remove callback for device \"%s\".", device_id); return -1; } pairing_remove(pairing); return 0; } static void pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg __attribute__((unused))) { debug(1, "pair-list cb"); for (struct pairings *pairing = pairings; pairing; pairing = pairing->next) { enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg); } } void handle_pair_add(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(1, "Connection %d from \"%s\": handle_pair_add", conn->connection_number, conn->ap2_client_name); debug_log_rtsp_message_conn(conn, 1, "pair-add request", req); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_add(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_add_cb, NULL, (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-add returned an error"); resp->respcode = 451; return; } 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_conn(conn, 1, "pair-add response", resp); } void handle_pair_list(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(1, "Connection %d from \"%s\": handle_pair_list", conn->connection_number, conn->ap2_client_name); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_list(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_list_cb, NULL, (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-list returned an error"); resp->respcode = 451; return; } 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_conn(conn, 1, "pair-list response", resp); } void handle_pair_remove(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(1, "Connection %d from \"%s\": handle_pair_remove", conn->connection_number, conn->ap2_client_name); uint8_t *body = NULL; size_t body_len = 0; int ret = pair_remove(PAIR_SERVER_HOMEKIT, &body, &body_len, pairing_remove_cb, NULL, (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "pair-remove returned an error"); resp->respcode = 451; return; } 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_conn(conn, 1, "pair-remove response", resp); } void handle_pair_verify(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } // try to pick up the stages uint8_t *b = (uint8_t *)req->content; char mstage = '-'; if ((req->contentlength >= 3) && (b[0] == 6) && (b[1] == 1) && (b[1] <= 9)) { mstage = '0' + b[2]; } debug(1, "Connection %d from \"%s\": handle_pair_verify, stage M%c, Content-Length %d", conn->connection_number, conn->ap2_client_name, mstage, req->contentlength); debug_log_rtsp_message_conn(conn, 2, "pair-verify request", req); int ret; uint8_t *body = NULL; size_t body_len = 0; // struct pair_result *result; if (!conn->ap2_pairing_context.verify_ctx) { conn->ap2_pairing_context.verify_ctx = pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, NULL, NULL, config.airplay_device_id); if (!conn->ap2_pairing_context.verify_ctx) { debug(1, "Error creating verify context"); resp->respcode = 500; // Internal Server Error goto out; } } ret = pair_verify(&body, &body_len, conn->ap2_pairing_context.verify_ctx, (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "%s", pair_verify_errmsg(conn->ap2_pairing_context.verify_ctx)); resp->respcode = 470; // Connection Authorization Required goto out; } /* ret = pair_verify_result(&result, conn->ap2_pairing_context.verify_ctx); if (ret == 0 && result->shared_secret_len > 0) { conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 3, result->shared_secret, result->shared_secret_len); if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { debug(1, "Error setting up rtsp control channel ciphering\n"); goto out; } conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 4, result->shared_secret, result->shared_secret_len); if (!conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx) { debug(1, "Error setting up rtsp event channel ciphering\n"); goto out; } } */ out: resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; if (body) msg_add_header(resp, "Content-Type", "application/octet-stream"); debug_log_rtsp_message_conn(conn, 2, "pair-verify response", resp); } void handle_pair_pin_start(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(4, "Connection %d from \"%s\": handle_pair_pin_start, Content-Length %d", conn->connection_number, conn->ap2_client_name, req->contentlength); debug_log_rtsp_message_conn(conn, 4, "handle_pair_pin_start", req); uint8_t *body = NULL; size_t body_len = 0; resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; if (body != NULL) msg_add_header(resp, "Content-Type", "application/octet-stream"); debug_log_rtsp_message(4, "pair-pin-start response", resp); } void handle_pair_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(2, "Connection %d from \"%s\": handle_pair_setup, Content-Length %d", conn->connection_number, conn->ap2_client_name, req->contentlength); debug_log_rtsp_message_conn(conn, 2, "pair-setup request", req); int ret; uint8_t *body = NULL; size_t body_len = 0; debug(3, "Connection %d: handle_pair_setup Content-Length %d", conn->connection_number, req->contentlength); debug_log_rtsp_message(3, "handle_pair_setup", req); if (!conn->ap2_pairing_context.setup_ctx) { conn->ap2_pairing_context.setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, config.password, NULL, NULL, config.airplay_device_id); if (!conn->ap2_pairing_context.setup_ctx) { debug(1, "Error creating setup context"); resp->respcode = 500; // Internal Server Error goto out; } } ret = pair_setup(&body, &body_len, conn->ap2_pairing_context.setup_ctx, (const uint8_t *)req->content, req->contentlength); if (ret < 0) { debug(1, "%s", pair_setup_errmsg(conn->ap2_pairing_context.setup_ctx)); resp->respcode = 470; // Connection Authorization Required goto out; } ret = pair_setup_result(NULL, &conn->pair_setup_result, conn->ap2_pairing_context.setup_ctx); if (ret == 0 && conn->pair_setup_result->shared_secret_len > 0) { // Transient pairing completed (pair-setup step 2), prepare encryption, but // don't activate yet, the response to this request is still plaintext conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 3, conn->pair_setup_result->shared_secret, conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix if (!conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx) { debug(1, "Error setting up rtsp control channel ciphering"); goto out; } conn->ap2_pairing_context.control_cipher_bundle.description = strdup("Control Stream"); conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 4, conn->pair_setup_result->shared_secret, conn->pair_setup_result->shared_secret_len, ""); // last argument is the (possible) dynamic salt suffix if (!conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx) { debug(1, "Error setting up rtsp event channel ciphering"); goto out; } conn->ap2_pairing_context.event_cipher_bundle.description = strdup("Event Stream"); } out: resp->content = (char *)body; // these will be freed when the data is sent resp->contentlength = body_len; if (body) msg_add_header(resp, "Content-Type", "application/octet-stream"); debug_log_rtsp_message_conn(conn, 2, "pair-setup response", resp); } void handle_fp_setup(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { if (conn->ap2_client_name) free(conn->ap2_client_name); conn->ap2_client_name = strdup(hdr); } debug(2, "Connection %d from \"%s\": handle_fp_setup,", conn->connection_number, conn->ap2_client_name); debug_log_rtsp_message_conn(conn, 2, "fp-setup request", req); /* Fairplay magic */ static uint8_t server_fp_reply1[] = "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x00\x0f\x9f\x3f\x9e\x0a" "\x25\x21\xdb\xdf\x31\x2a\xb2\xbf\xb2\x9e\x8d\x23\x2b\x63\x76\xa8\xc8\x18\x70" "\x1d\x22\xae\x93\xd8\x27\x37\xfe\xaf\x9d\xb4\xfd\xf4\x1c\x2d\xba\x9d\x1f\x49" "\xca\xaa\xbf\x65\x91\xac\x1f\x7b\xc6\xf7\xe0\x66\x3d\x21\xaf\xe0\x15\x65\x95" "\x3e\xab\x81\xf4\x18\xce\xed\x09\x5a\xdb\x7c\x3d\x0e\x25\x49\x09\xa7\x98\x31" "\xd4\x9c\x39\x82\x97\x34\x34\xfa\xcb\x42\xc6\x3a\x1c\xd9\x11\xa6\xfe\x94\x1a" "\x8a\x6d\x4a\x74\x3b\x46\xc3\xa7\x64\x9e\x44\xc7\x89\x55\xe4\x9d\x81\x55\x00" "\x95\x49\xc4\xe2\xf7\xa3\xf6\xd5\xba"; static uint8_t server_fp_reply2[] = "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x01\xcf\x32\xa2\x57\x14" "\xb2\x52\x4f\x8a\xa0\xad\x7a\xf1\x64\xe3\x7b\xcf\x44\x24\xe2\x00\x04\x7e\xfc" "\x0a\xd6\x7a\xfc\xd9\x5d\xed\x1c\x27\x30\xbb\x59\x1b\x96\x2e\xd6\x3a\x9c\x4d" "\xed\x88\xba\x8f\xc7\x8d\xe6\x4d\x91\xcc\xfd\x5c\x7b\x56\xda\x88\xe3\x1f\x5c" "\xce\xaf\xc7\x43\x19\x95\xa0\x16\x65\xa5\x4e\x19\x39\xd2\x5b\x94\xdb\x64\xb9" "\xe4\x5d\x8d\x06\x3e\x1e\x6a\xf0\x7e\x96\x56\x16\x2b\x0e\xfa\x40\x42\x75\xea" "\x5a\x44\xd9\x59\x1c\x72\x56\xb9\xfb\xe6\x51\x38\x98\xb8\x02\x27\x72\x19\x88" "\x57\x16\x50\x94\x2a\xd9\x46\x68\x8a"; static uint8_t server_fp_reply3[] = "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x02\xc1\x69\xa3\x52\xee" "\xed\x35\xb1\x8c\xdd\x9c\x58\xd6\x4f\x16\xc1\x51\x9a\x89\xeb\x53\x17\xbd\x0d" "\x43\x36\xcd\x68\xf6\x38\xff\x9d\x01\x6a\x5b\x52\xb7\xfa\x92\x16\xb2\xb6\x54" "\x82\xc7\x84\x44\x11\x81\x21\xa2\xc7\xfe\xd8\x3d\xb7\x11\x9e\x91\x82\xaa\xd7" "\xd1\x8c\x70\x63\xe2\xa4\x57\x55\x59\x10\xaf\x9e\x0e\xfc\x76\x34\x7d\x16\x40" "\x43\x80\x7f\x58\x1e\xe4\xfb\xe4\x2c\xa9\xde\xdc\x1b\x5e\xb2\xa3\xaa\x3d\x2e" "\xcd\x59\xe7\xee\xe7\x0b\x36\x29\xf2\x2a\xfd\x16\x1d\x87\x73\x53\xdd\xb9\x9a" "\xdc\x8e\x07\x00\x6e\x56\xf8\x50\xce"; static uint8_t server_fp_reply4[] = "\x46\x50\x4c\x59\x03\x01\x02\x00\x00\x00\x00\x82\x02\x03\x90\x01\xe1\x72\x7e" "\x0f\x57\xf9\xf5\x88\x0d\xb1\x04\xa6\x25\x7a\x23\xf5\xcf\xff\x1a\xbb\xe1\xe9" "\x30\x45\x25\x1a\xfb\x97\xeb\x9f\xc0\x01\x1e\xbe\x0f\x3a\x81\xdf\x5b\x69\x1d" "\x76\xac\xb2\xf7\xa5\xc7\x08\xe3\xd3\x28\xf5\x6b\xb3\x9d\xbd\xe5\xf2\x9c\x8a" "\x17\xf4\x81\x48\x7e\x3a\xe8\x63\xc6\x78\x32\x54\x22\xe6\xf7\x8e\x16\x6d\x18" "\xaa\x7f\xd6\x36\x25\x8b\xce\x28\x72\x6f\x66\x1f\x73\x88\x93\xce\x44\x31\x1e" "\x4b\xe6\xc0\x53\x51\x93\xe5\xef\x72\xe8\x68\x62\x33\x72\x9c\x22\x7d\x82\x0c" "\x99\x94\x45\xd8\x92\x46\xc8\xc3\x59"; static uint8_t server_fp_header[] = "\x46\x50\x4c\x59\x03\x01\x04\x00\x00\x00\x00\x14"; resp->respcode = 200; // assume it's handled // uint8_t *out; // size_t out_len; int version_pos = 4; int mode_pos = 14; int type_pos = 5; int seq_pos = 6; int setup_message_type = 1; int setup1_message_seq = 1; int setup2_message_seq = 3; int setup2_suffix_len = 20; // int ret; // response and len are dummy values and can be ignored // debug(1, "Version: %02x, mode: %02x, type: %02x, seq: %02x", req->content[version_pos], // req->content[mode_pos], req->content[type_pos], req->content[seq_pos]); if (req->content[version_pos] != 3 || req->content[type_pos] != setup_message_type) { debug(1, "Unsupported FP version."); } char *response = NULL; size_t len = 0; if (req->content[seq_pos] == setup1_message_seq) { // All replies are the same length. -1 to account for the NUL byte at the end. len = sizeof(server_fp_reply1) - 1; if (req->content[mode_pos] == 0) response = memdup(server_fp_reply1, len); if (req->content[mode_pos] == 1) response = memdup(server_fp_reply2, len); if (req->content[mode_pos] == 2) response = memdup(server_fp_reply3, len); if (req->content[mode_pos] == 3) response = memdup(server_fp_reply4, len); } else if (req->content[seq_pos] == setup2_message_seq) { // -1 to account for the NUL byte at the end. len = sizeof(server_fp_header) - 1 + setup2_suffix_len; response = malloc(len); if (response) { memcpy(response, server_fp_header, sizeof(server_fp_header) - 1); memcpy(response + sizeof(server_fp_header) - 1, req->content + req->contentlength - setup2_suffix_len, setup2_suffix_len); } } if (response == NULL) { debug(1, "Cannot create a response."); } resp->content = response; // these will be freed when the data is sent resp->contentlength = len; msg_add_header(resp, "Content-Type", "application/octet-stream"); } /* Identifier 21cc689d-d5de-4814-872c-71d1426b57e0 Enable_HK_Access_Control PublicKey qXJDhhL5F3OACL+HO7LVLQVdy0OJtavepjpF720PaOQ= Device_Name MyDevice Access_Control_Level 0 */ void handle_configure(rtsp_conn_info *conn __attribute__((unused)), rtsp_message *req __attribute__((unused)), rtsp_message *resp) { debug_log_rtsp_message_conn(conn, 1, "POST /configure req:", req); int existingEnable_HK_Access_Control = config.enable_HK_Access_Control; plist_t response_plist = plist_new_dict(); // look for a configuration dictionary plist_t messagePlist = plist_from_rtsp_content(req); if (messagePlist != NULL) { // look for the keyed dict "ConfigurationDictionary" plist_t configurationDict = plist_dict_get_item(messagePlist, "ConfigurationDictionary"); if (configurationDict != NULL) { uint8_t enable_HK_Access_Control = 0; plist_t enableItem = plist_dict_get_item(configurationDict, "Enable_HK_Access_Control"); if (enableItem != NULL) { plist_get_bool_val(enableItem, &enable_HK_Access_Control); if (enable_HK_Access_Control != 0) { config.enable_HK_Access_Control = 1; plist_dict_set_item(response_plist, "Identifier", plist_new_string(config.airplay_pi)); plist_dict_set_item(response_plist, "Enable_HK_Access_Control", plist_new_bool(1)); plist_dict_set_item( response_plist, "PublicKey", plist_new_data((const char *)config.airplay_pk, sizeof(config.airplay_pk))); plist_dict_set_item(response_plist, "Device_Name", plist_new_string(config.service_name)); plist_dict_set_item(response_plist, "Access_Control_Level", plist_new_uint(0)); } else { config.enable_HK_Access_Control = 0; // leave the response dict empty } } else { debug(1, "no Enable_HK_Access_Control item in POST /configure ConfigurationDictionary"); } debug(1, "enable_HK_Access_Control is %u.", enable_HK_Access_Control); } else { debug(1, "no ConfigurationDictionary in POST /configure plist"); } plist_free(messagePlist); } else { debug(1, "no plist in POST /configure request"); } if (config.enable_HK_Access_Control != 0) { config.airplay_statusflags |= (1 << 10); // DeviceWasSetupForHKAccessControl } else { config.airplay_statusflags &= (0xffffffff - (1 << 10)); } if (config.enable_HK_Access_Control != existingEnable_HK_Access_Control) { build_bonjour_strings(principal_conn); if (config.service_type == APST_airplay2) { mdns_update(NULL, secondary_txt_records); } } plist_to_bin(response_plist, &resp->content, &resp->contentlength); plist_free(response_plist); msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); debug_log_rtsp_message_conn(conn, 1, "POST /configure response:", resp); } void handle_feedback(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { debug(4, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(4, NULL, req); int is_playing = 0; int connection_number = 0; int type = 0; double rate = 0.0; // get information from the current player, if any. pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); if ((principal_conn != NULL) && (principal_conn->is_playing != 0)) { is_playing = 1; connection_number = principal_conn->connection_number; type = principal_conn->type; rate = 1.0 * principal_conn->input_rate; } pthread_cleanup_pop(1); // release the principal_conn lock // debug(1, "Player is%s playing.", is_playing != 0 ? "" : " not"); if (is_playing != 0) { if ((type != 96) && (type != 103)) debug(1, "Connection %d, feedback unexpected type: %u.", connection_number, type); if ((rate != 44100.0) && (rate != 48000.0)) debug(2, "Connection %d, feedback unexpected rate: %f.", connection_number, rate); plist_t payload_plist = plist_new_dict(); plist_dict_set_item(payload_plist, "type", plist_new_uint(type)); plist_dict_set_item(payload_plist, "sr", plist_new_real(rate)); plist_t array_plist = plist_new_array(); plist_array_append_item(array_plist, payload_plist); plist_t response_plist = plist_new_dict(); plist_dict_set_item(response_plist, "streams", array_plist); plist_to_bin(response_plist, &resp->content, &resp->contentlength); plist_free(response_plist); // plist_free(array_plist); // plist_free(payload_plist); msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); debug_log_rtsp_message(4, "FEEDBACK response:", resp); } } void handle_command(rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { debug(3, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, NULL, req); if (rtsp_message_contains_plist(req)) { // we are not going to load the plist here because we don't wamt // to incur the memory and processing cost. So we'll just send it to the // metadata handling code and it can be dealt with there. #ifdef CONFIG_METADATA send_metadata('ssnc', 'copl', req->content, req->contentlength, req, 1); // COmmand PList (and release 'req' afterwards) #endif /* plist_t command_dict = NULL; plist_from_memory(req->content, req->contentlength, &command_dict); if (command_dict != NULL) { // we have a plist -- try to get the dict item keyed to "updateMRSupportedCommands" plist_t item = plist_dict_get_item(command_dict, "type"); if (item != NULL) { char *typeValue = NULL; plist_get_string_val(item, &typeValue); debug(1, "Connection %d: POST /command plist type \"%s\" received.", conn->connection_number, typeValue); debug_log_rtsp_message(1, NULL, req); if ((typeValue != NULL) && (strcmp(typeValue, "updateMRSupportedCommands") == 0)) { item = plist_dict_get_item(command_dict, "params"); if (item != NULL) { // the item should be a dict plist_t item_array = plist_dict_get_item(item, "mrSupportedCommandsFromSender"); if (item_array != NULL) { // here we have an array of data items uint32_t items = plist_array_get_size(item_array); if (items) { uint32_t item_number; for (item_number = 0; item_number < items; item_number++) { plist_t the_item = plist_array_get_item(item_array, item_number); char *buff = NULL; uint64_t length = 0; plist_get_data_val(the_item, &buff, &length); // debug(1,"Item %d, length: %" PRId64 " bytes", item_number, length); if ((buff != NULL) && (length >= strlen("bplist00")) && (strstr(buff, "bplist00") == buff)) { // debug(1,"Contains a plist."); plist_t subsidiary_plist = NULL; plist_from_memory(buff, length, &subsidiary_plist); if (subsidiary_plist) { char *printable_plist = plist_as_xml_text(subsidiary_plist); if (printable_plist) { debug(4, "Connection %d:\n==\n%s\n==", conn->connection_number, printable_plist); free(printable_plist); } else { debug(1, "Can't print the plist!"); } plist_free(subsidiary_plist); } else { debug(1, "Can't access the plist!"); } } if (buff != NULL) free(buff); } } } else { debug(1, "Connection %d: POST /command no mrSupportedCommandsFromSender item.", conn->connection_number); } } else { debug(1, "Connection %d: POST /command no params dict.", conn->connection_number); } resp->respcode = 200; } if (typeValue != NULL) free(typeValue); } else { debug(2, "Connection %d: Could not find a \"type\" item.", conn->connection_number); } plist_free(command_dict); } else { debug(1, "Connection %d: POST /command plist cannot be inputted.", conn->connection_number); } */ } else { debug(1, "Connection %d: POST /command contains no plist", conn->connection_number); } } void handle_audio_mode(rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { debug(2, "Connection %d: POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, NULL, req); } 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, "/pair-add") == 0) { handle_pair_add(conn, req, resp); } else if (strcmp(req->path, "/pair-remove") == 0) { handle_pair_remove(conn, req, resp); } else if (strcmp(req->path, "/pair-list") == 0) { handle_pair_list(conn, req, resp); } else if (strcmp(req->path, "/pair-pin-start") == 0) { handle_pair_pin_start(conn, req, resp); } else if (strcmp(req->path, "/fp-setup") == 0) { handle_fp_setup(conn, req, resp); } else if (strcmp(req->path, "/configure") == 0) { handle_configure(conn, req, resp); } else if (strcmp(req->path, "/feedback") == 0) { handle_feedback(conn, req, resp); } else if (strcmp(req->path, "/command") == 0) { handle_command(conn, req, resp); } else if (strcmp(req->path, "/audioMode") == 0) { handle_audio_mode(conn, req, resp); } else { debug(1, "Connection %d: Unhandled POST %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(2, "POST request", req); } } void handle_setpeers(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: SETPEERS %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(3, "SETPEERS request", req); /* char timing_list_message[4096]; timing_list_message[0] = 'T'; timing_list_message[1] = 0; // ensure the client itself is first -- it's okay if it's duplicated later strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); strncat(timing_list_message, (const char *)&conn->client_ip_string, sizeof(timing_list_message) - 1 - strlen(timing_list_message)); plist_t addresses_array = NULL; plist_from_memory(req->content, req->contentlength, &addresses_array); uint32_t items = plist_array_get_size(addresses_array); if (items) { uint32_t item; for (item = 0; item < items; item++) { plist_t n = plist_array_get_item(addresses_array, item); char *ip_address = NULL; plist_get_string_val(n, &ip_address); // debug(1,ip_address); strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); strncat(timing_list_message, ip_address, sizeof(timing_list_message) - 1 - strlen(timing_list_message)); if (ip_address != NULL) free(ip_address); } ptp_send_control_message_string(timing_list_message); } plist_free(addresses_array); */ // set_client_as_ptp_clock(conn); resp->respcode = 200; } void handle_setpeersx(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: SETPEERSX %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message(2, "SETPEERS Xrequest", req); resp->respcode = 200; } #endif void handle_options(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message(2, "OPTIONS request", req); debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " "PAUSE, FLUSH, TEARDOWN, " "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } void handle_teardown(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(4, "Connection %d: TEARDOWN (Classic) %s Content-Length %d", conn->connection_number, req->path, req->contentlength); debug_log_rtsp_message_conn(conn, 4, "TEARDOWN (Classic)", req); // most of the cleanup here is done by the exiting player_thread, if any, and by the event // receiver if and when it exits. if (conn->player_thread) { debug(2, "TEARDOWN (Classic) is stopping a player thread before exiting..."); player_stop(conn); // this nulls the player_thread and cancels the threads... activity_monitor_signify_activity(0); // inactive, and should be after command_stop() } resp->respcode = 200; msg_add_header(resp, "Connection", "close"); conn->stop = 1; //an anomaly needs to be fixed } #ifdef CONFIG_AIRPLAY_2 void handle_options_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message_conn(conn, 3, "OPTIONS request", req); debug(3, "Connection %d: OPTIONS", conn->connection_number); resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " "PAUSE, FLUSH, FLUSHBUFFERED, TEARDOWN, " "OPTIONS, POST, GET, PUT"); } void handle_teardown_2(rtsp_conn_info *conn, __attribute__((unused)) rtsp_message *req, rtsp_message *resp) { debug(4, "Connection %d from \"%s\": TEARDOWN (AP2 %s) %s Content-Length %d", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category), req->path, req->contentlength); debug_log_rtsp_message_conn(conn, 4, "TEARDOWN (AP2)", req); // look for a configuration dictionary plist_t messagePlist = plist_from_rtsp_content(req); if (messagePlist != NULL) { plist_t streams = plist_dict_get_item(messagePlist, "streams"); if (streams != NULL) { // just drop the player, leave the connection open if (conn->player_thread) { debug(4, "Connection %d from \"%s\": TEARDOWN (AP2 %s) %s Content-Length %d is stopping a player thread", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category), req->path, req->contentlength); player_stop(conn); // this nulls the player_thread and cancels the threads... activity_monitor_signify_activity(0); // inactive, and should be after command_stop() } } else { if (plist_dict_get_size(messagePlist) != 0) { debug(1, "Connection %d from \"%s\": TEARDOWN (AP2 %s) %s Content-Length %d plist is non-empty but contains no \"streams\" item.", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category), req->path, req->contentlength); debug_log_rtsp_message_conn(conn, 4, "Contents follow:", req); } msg_add_header(resp, "Connection", "close"); debug(4, "Connection %d from \"%s\": TEARDOWN (AP2 %s) %s Content-Length %d is asking to terminate the connection.", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category), req->path, req->contentlength); conn->stop = 1; } plist_free(messagePlist); } else { debug(1, "Connection %d from \"%s\": TEARDOWN (AP2 %s) %s Content-Length %d has no plist -- nothing done.", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category), req->path, req->contentlength); } resp->respcode = 200; } #endif void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug_log_rtsp_message(2, "FLUSH request", req); debug(3, "Connection %d: FLUSH", conn->connection_number); char *p = NULL; uint32_t rtptime = 0; char *hdr = msg_get_header(req, "RTP-Info"); if (hdr) { // debug(1,"FLUSH message received: \"%s\".",hdr); // get the rtp timestamp p = strstr(hdr, "rtptime="); if (p) { p = strchr(p, '='); if (p) rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1 } } debug(2, "RTSP Flush Requested: %u.", rtptime); if ((conn != NULL) && (conn == principal_conn)) { #ifdef CONFIG_METADATA if (p) send_metadata('ssnc', 'flsr', p + 1, strlen(p + 1), req, 1); else send_metadata('ssnc', 'flsr', NULL, 0, NULL, 0); #endif player_flush(rtptime, conn); // will not crash even it there is no player thread. resp->respcode = 200; } else { warn("Connection %d FLUSH %u received without having the player", conn->connection_number, rtptime); resp->respcode = 451; } } #ifdef CONFIG_AIRPLAY_2 #ifdef CONFIG_METADATA static void check_and_send_plist_metadata(plist_t messagePlist, const char *plist_key, uint32_t metadata_code) { plist_t item = plist_dict_get_item(messagePlist, plist_key); if (item) { char *value; plist_get_string_val(item, &value); if (value != NULL) { send_metadata('ssnc', metadata_code, value, strlen(value), NULL, 0); free(value); } } } #endif void handle_setup_2(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int err; debug(4, "Connection %d from \"%s\": SETUP (AP2) %s Content-Length %d", conn->connection_number, conn->ap2_client_name, req->path, req->contentlength); debug_log_rtsp_message_conn(conn, 4, "SETUP (AP2)", req); plist_t messagePlist = plist_from_rtsp_content(req); if (messagePlist != NULL) { // if (conn->sessionPlist) // plist_free(conn->sessionPlist); conn->sessionPlist = messagePlist; } plist_t setupResponsePlist = plist_new_dict(); resp->respcode = 501; // see if we can get a name for the client plist_t nameItem = plist_dict_get_item(messagePlist, "name"); if (nameItem != NULL) { if (conn->ap2_client_name) free(conn->ap2_client_name); plist_get_string_val(nameItem, &conn->ap2_client_name); // we need to free it! } // see if the incoming plist contains a "streams" array plist_t streams = plist_dict_get_item(messagePlist, "streams"); if (streams == NULL) { // no "streams" plist, so it must (?) be an initial setup debug(2, "Connection %d SETUP: No \"streams\" array has been found -- create an event thread " "and open a TCP port.", conn->connection_number); conn->airplay_stream_category = unspecified_stream_category; // figure out what category of stream it is, by looking at the plist plist_t timingProtocol = plist_dict_get_item(messagePlist, "timingProtocol"); if (timingProtocol != NULL) { char *timingProtocolString = NULL; plist_get_string_val(timingProtocol, &timingProtocolString); if (timingProtocolString) { if (strcmp(timingProtocolString, "PTP") == 0) { debug(2, "Connection %d: AP2 PTP connection from %s:%u (\"%s\") to self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = ptp_stream; conn->timing_type = ts_ptp; do_pthread_setname(&conn->thread, "ap2_ptp_%d", conn->connection_number); } else if (strcmp(timingProtocolString, "NTP") == 0) { debug(1, "Connection %d: SETUP: NTP setup from %s:%u (\"%s\") to self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = ntp_stream; conn->timing_type = ts_ntp; do_pthread_setname(&conn->thread, "ap2_ntp_%d", conn->connection_number); } else if (strcmp(timingProtocolString, "None") == 0) { debug(3, "Connection %d: SETUP: a \"None\" setup detected from %s:%u (\"%s\") to self at " "%s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); // now check to see if it's got the "isRemoteControlOnly" item and check it's true plist_t isRemoteControlOnly = plist_dict_get_item(messagePlist, "isRemoteControlOnly"); if (isRemoteControlOnly != NULL) { uint8_t isRemoteControlOnlyBoolean = 0; plist_get_bool_val(isRemoteControlOnly, &isRemoteControlOnlyBoolean); if (isRemoteControlOnlyBoolean != 0) { debug(2, "Connection %d: SETUP: Remote Control Only connection from %s:%u (\"%s\") to " "self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, conn->ap2_client_name, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_stream_category = remote_control_stream; do_pthread_setname(&conn->thread, "ap2_rc_%d", conn->connection_number); } else { debug(1, "Connection %d: SETUP: a \"None\" setup detected, with " "\"isRemoteControlOnly\" item set to \"false\".", conn->connection_number); } } else { debug(1, "Connection %d: SETUP: a \"None\" setup detected, but no " "\"isRemoteControlOnly\" item detected.", conn->connection_number); } } debug(2, "Connection %d from \"%s\": Initial (i.e. no streams array) SETUP (AirPlay 2) on %s", conn->connection_number, conn->ap2_client_name, get_category_string(conn->airplay_stream_category)); debug_log_rtsp_message_conn( conn, 2, "Initial (i.e. no streams array) SETUP (AirPlay 2) incoming message", req); // here, we know it's an initial setup and we know the kind of setup being requested // if it's a full service PTP stream, we get groupUUID, groupContainsGroupLeader and // timingPeerList if (conn->airplay_stream_category == ptp_stream) { // airplay 2 always allows interruption, so should never return // play_lock_aquisition_failed if (get_play_lock(conn, 1) != play_lock_aquisition_failed) { debug(2, "Connection %d: %s AP2 setup -- play lock acquired.", conn->connection_number, get_category_string(conn->airplay_stream_category)); #ifdef CONFIG_METADATA send_ssnc_metadata('conn', conn->client_ip_string, strlen(conn->client_ip_string), 1); // before disconnecting an existing play send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1); send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1); #endif if (ptp_shm_interface_open() != 0) // it should be open already, but just in case it isn't... die("Can not access the NQPTP service. Has it stopped running?"); debug_log_rtsp_message(3, "SETUP \"PTP\" message", req); plist_t groupUUID = plist_dict_get_item(messagePlist, "groupUUID"); if (groupUUID) { char *gid = NULL; plist_get_string_val(groupUUID, &gid); if (gid) { if (conn->airplay_gid) free(conn->airplay_gid); conn->airplay_gid = gid; // it'll be free'd later on... } else { debug(1, "Invalid groupUUID"); } } else { debug(1, "No groupUUID in SETUP"); } // now see if the group contains a group leader plist_t groupContainsGroupLeader = plist_dict_get_item(messagePlist, "groupContainsGroupLeader"); if (groupContainsGroupLeader) { uint8_t value = 0; plist_get_bool_val(groupContainsGroupLeader, &value); conn->groupContainsGroupLeader = value; debug(3, "Updated groupContainsGroupLeader to %u", conn->groupContainsGroupLeader); } else { debug(1, "No groupContainsGroupLeader in SETUP"); } char timing_list_message[4096]; timing_list_message[0] = 'T'; timing_list_message[1] = 0; // ensure the client itself is first -- it's okay if it's duplicated later strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); strncat(timing_list_message, (const char *)&conn->client_ip_string, sizeof(timing_list_message) - 1 - strlen(timing_list_message)); plist_t timing_peer_info = plist_dict_get_item(messagePlist, "timingPeerInfo"); if (timing_peer_info) { // first, get the incoming plist. plist_t addresses_array = plist_dict_get_item(timing_peer_info, "Addresses"); if (addresses_array) { // iterate through the array of items uint32_t items = plist_array_get_size(addresses_array); if (items) { uint32_t item; for (item = 0; item < items; item++) { plist_t n = plist_array_get_item(addresses_array, item); char *ip_address = NULL; plist_get_string_val(n, &ip_address); // debug(1, "Timing peer: %s", ip_address); strncat(timing_list_message, " ", sizeof(timing_list_message) - 1 - strlen(timing_list_message)); strncat(timing_list_message, ip_address, sizeof(timing_list_message) - 1 - strlen(timing_list_message)); free(ip_address); } } else { debug(1, "SETUP on Connection %d: No timingPeerInfo addresses in the array.", conn->connection_number); } } else { debug(1, "SETUP on Connection %d: Can't find timingPeerInfo addresses", conn->connection_number); } // make up the timing peer info list part of the response... // debug(1,"Create timingPeerInfoPlist"); plist_t timingPeerInfoPlist = plist_new_dict(); plist_t addresses = plist_new_array(); // to hold the device's interfaces plist_array_append_item(addresses, plist_new_string(conn->self_ip_string)); // debug(1,"self ip: \"%s\"", conn->self_ip_string); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable struct ifaddrs *addrs, *iap; getifaddrs(&addrs); for (iap = addrs; iap != NULL; iap = iap->ifa_next) { // debug(1, "Interface index %d, name: \"%s\"",if_nametoindex(iap->ifa_name), // iap->ifa_name); if ((iap->ifa_addr) && (iap->ifa_netmask) && (iap->ifa_flags & IFF_UP) && ((iap->ifa_flags & IFF_LOOPBACK) == 0)) { char buf[INET6_ADDRSTRLEN + 1]; // +1 for a NUL memset(buf, 0, sizeof(buf)); if (iap->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(iap->ifa_addr); inet_ntop(AF_INET6, (void *)&addr6->sin6_addr, buf, sizeof(buf)); plist_array_append_item(addresses, plist_new_string(buf)); // debug(1, "Own address IPv6: %s", buf); // strncat(timing_list_message, " ", // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); // strncat(timing_list_message, buf, // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); } else { struct sockaddr_in *addr = (struct sockaddr_in *)(iap->ifa_addr); inet_ntop(AF_INET, (void *)&addr->sin_addr, buf, sizeof(buf)); plist_array_append_item(addresses, plist_new_string(buf)); // debug(1, "Own address IPv4: %s", buf); // strncat(timing_list_message, " ", // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); // strncat(timing_list_message, buf, // sizeof(timing_list_message) - 1 - strlen(timing_list_message)); } } } freeifaddrs(addrs); pthread_setcancelstate(oldState, NULL); // debug(1,"initial timing peer command: \"%s\".", timing_list_message); // ptp_send_control_message_string(timing_list_message); // deferring this until play is about to start // set_client_as_ptp_clock(conn); // ptp_send_control_message_string("B"); // signify clock dependability period is // "B"eginning (or continuing) plist_dict_set_item(timingPeerInfoPlist, "Addresses", addresses); plist_dict_set_item(timingPeerInfoPlist, "ID", plist_new_string(conn->self_ip_string)); plist_dict_set_item(setupResponsePlist, "timingPeerInfo", timingPeerInfoPlist); // get a port to use as an event port // bind a new TCP port and get a socket conn->local_event_port = 0; // any port int lerr = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_event_port, &conn->event_socket); if (lerr) { die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an " "event " "port", conn->connection_number, lerr); } listen(conn->event_socket, 128); // ensure socket is open before telling client debug(2, "Connection %d: TCP PTP event port opened: %u.", conn->connection_number, conn->local_event_port); if (conn->rtp_event_thread != NULL) debug(1, "previous rtp_event_thread allocation not freed, it seems."); conn->ap2_event_receiver_exited = 0; conn->rtp_event_thread = malloc(sizeof(pthread_t)); if (conn->rtp_event_thread == NULL) die("Couldn't allocate space for pthread_t"); named_pthread_create(conn->rtp_event_thread, NULL, &ap2_event_receiver, (void *)conn, "ap2_ptp_evt_%d", conn->connection_number); plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port)); plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // dummy // cancel_all_RTSP_threads(ptp_stream, // conn->connection_number); // kill all the other listeners resp->respcode = 200; } else { debug(1, "SETUP on Connection %d: PTP setup -- no timingPeerInfo plist.", conn->connection_number); } // since the GID from the client has been acquired, update the airplay bonjour strings. build_bonjour_strings(conn); debug(2, "Connection %d: SETUP mdns_update on %s.", conn->connection_number, get_category_string(conn->airplay_stream_category)); if (config.service_type == APST_airplay2) { mdns_update(NULL, secondary_txt_records); } #ifdef CONFIG_METADATA check_and_send_plist_metadata(messagePlist, "name", 'snam'); check_and_send_plist_metadata(messagePlist, "deviceID", 'cdid'); check_and_send_plist_metadata(messagePlist, "model", 'cmod'); check_and_send_plist_metadata(messagePlist, "macAddress", 'cmac'); #endif } else { // this should never happen! debug(1, "SETUP on Connection %d: could not become principal conn.", conn->connection_number); resp->respcode = 453; } } else if (conn->airplay_stream_category == ntp_stream) { debug(1, "SETUP on Connection %d: ntp stream handling is not implemented!", conn->connection_number); warn("Shairport Sync can not handle NTP streams."); } else if (conn->airplay_stream_category == remote_control_stream) { debug_log_rtsp_message(3, "SETUP (no stream) \"isRemoteControlOnly\" message", req); // get a port to use as an event port // bind a new TCP port and get a socket conn->local_event_port = 0; // any port int lerr = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_event_port, &conn->event_socket); if (lerr) { die("SETUP on Connection %d: Error %d: could not find a TCP port to use as an event " "port", conn->connection_number, lerr); } listen(conn->event_socket, 128); // ensure socket is open before telling client debug(2, "Connection %d SETUP (RC): TCP Remote Control event port opened: %u.", conn->connection_number, conn->local_event_port); if (conn->rtp_event_thread != NULL) debug(1, "Connection %d SETUP (RC): previous rtp_event_thread allocation not freed, it " "seems.", conn->connection_number); conn->ap2_event_receiver_exited = 0; conn->rtp_event_thread = malloc(sizeof(pthread_t)); if (conn->rtp_event_thread == NULL) die("Couldn't allocate space for pthread_t"); named_pthread_create(conn->rtp_event_thread, NULL, &ap2_event_receiver, (void *)conn, "ap2_rc_evt_%d", conn->connection_number); plist_dict_set_item(setupResponsePlist, "eventPort", plist_new_uint(conn->local_event_port)); debug(2, "SETUP on Connection %d: RemoteControl Only eventPort %u.", conn->connection_number, conn->local_event_port); // plist_dict_set_item(setupResponsePlist, "timingPort", plist_new_uint(0)); // cancel_all_RTSP_threads( // remote_control_stream, // conn->connection_number); // kill all the other remote control listeners resp->respcode = 200; } else { debug(1, "SETUP on Connection %d: an unrecognised \"%s\" setup detected.", conn->connection_number, timingProtocolString); warn("Shairport Sync can not handle streams of this type: \"%s\".", timingProtocolString); } free(timingProtocolString); } else { debug(1, "SETUP on Connection %d: Can't retrieve timingProtocol string in initial SETUP.", conn->connection_number); } } else { debug(1, "SETUP on Connection %d: Unrecognised SETUP incoming message from \"%s\": no " "timingProtocol or streams plist found.", conn->connection_number, (const char *)conn->client_ip_string); debug_log_rtsp_message(1, "Unrecognised SETUP incoming message.", req); warn("Unrecognised SETUP incoming message -- ignored."); } } else { if (conn->airplay_stream_category == ptp_stream) { if (conn->player_thread) { debug(1, "stopping a running player during setup phase 2"); player_stop(conn); // this nulls the player_thread and cancels the threads... activity_monitor_signify_activity(0); // inactive, and should be after command_stop() } set_client_as_ptp_clock(conn); ptp_send_control_message_string( "B"); // signify clock dependability period is "B"eginning (or continuing) plist_t stream0 = plist_array_get_item(streams, 0); plist_t streams_array = plist_new_array(); // to hold the ports and stuff plist_t stream0dict = plist_new_dict(); // get the session key -- it must have one plist_t item = plist_dict_get_item(stream0, "shk"); // session key uint64_t item_value = 0; if (item != NULL) { plist_get_data_val(item, (char **)&conn->session_key, &item_value); // item_value is the session key length (?) } else { warn("No session key (shk) property in setup! This is fatal!"); } // get the compression type // this seems to be static -- a stream's encoding can change dynamically, it seems item = plist_dict_get_item(stream0, "ct"); // compression type if (item != NULL) { plist_get_uint_val(item, &item_value); conn->compressionType = item_value; // see https://emanuelecozzi.net/docs/airplay2/audio/ for values } else { debug(1, "No compression type (ct) property found in setup."); } // get the max frames per packet item = plist_dict_get_item(stream0, "spf"); // samples per frame (?) if (item != NULL) { plist_get_uint_val(item, &item_value); // see https://emanuelecozzi.net/docs/airplay2/audio/ for values debug(3, "Frames per packet (aka spf (\"samples per frame\"?): %" PRId64 ".", item_value); conn->frames_per_packet = item_value; } else { warn("No frames per packet (spf) property found in setup!"); } // bind a new UDP port and get a socket conn->local_ap2_control_port = 0; // any port err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_ap2_control_port, &conn->ap2_control_socket); if (err) { die("Error %d: could not find a UDP port to use as an ap2_control port", err); } debug(2, "Connection %d: UDP control port opened: %u.", conn->connection_number, conn->local_ap2_control_port); named_pthread_create(&conn->rtp_ap2_control_thread, NULL, &rtp_ap2_control_receiver, (void *)conn, "ap2_cn_%d", conn->connection_number); // get the DACP-ID and Active Remote for remote control stuff char *ar = msg_get_header(req, "Active-Remote"); if (ar) { debug(3, "Connection %d: SETUP AP2 -- Active-Remote string seen: \"%s\".", conn->connection_number, ar); // get the active remote if (conn->dacp_active_remote) // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); #endif } else { debug(2, "Connection %d: SETUP AP2 no Active-Remote information in the the SETUP Record.", conn->connection_number); if (conn->dacp_active_remote) { // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } } ar = msg_get_header(req, "DACP-ID"); if (ar) { debug(3, "Connection %d: SETUP AP2 -- DACP-ID string seen: \"%s\".", conn->connection_number, ar); if (conn->dacp_id) // this is in case SETUP was previously called free(conn->dacp_id); conn->dacp_id = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); #endif } else { debug(2, "Connection %d: SETUP AP2 doesn't include DACP-ID string information.", conn->connection_number); if (conn->dacp_id) { // this is in case SETUP was previously called free(conn->dacp_id); conn->dacp_id = NULL; } } // now, get the type of the stream. item = plist_dict_get_item(stream0, "type"); item_value = 0; plist_get_uint_val(item, &item_value); conn->type = item_value; switch (item_value) { case 96: { debug(4, "Connection %d. AP2 Realtime Audio Stream SETUP.", conn->connection_number); debug_log_rtsp_message(4, "AP2 Realtime Audio Stream SETUP incoming message:", req); conn->stream.type = ast_apple_lossless; conn->airplay_stream_type = realtime_stream; // get the sample rate item = plist_dict_get_item(stream0, "sr"); // sample rate if (item != NULL) { plist_get_uint_val(item, &item_value); // see https://emanuelecozzi.net/docs/airplay2/audio/ for values conn->input_rate = item_value; debug(4, "Set conn->input_rate: %u.", conn->input_rate); } else { debug(1, "Connection %d. No sample rate (sr) property found in setup.", conn->connection_number); } item = plist_dict_get_item(stream0, "spf"); // samples per frame if (item != NULL) { plist_get_uint_val(item, &item_value); conn->frames_per_packet = item_value; debug(4, "Set conn->frames_per_packet: %u.", conn->frames_per_packet); } else { debug(1, "Connection %d. No samples per frame (spf) property found in setup.", conn->connection_number); } // bind a new UDP port and get a socket conn->local_realtime_audio_port = 0; // any port err = bind_socket_and_port(SOCK_DGRAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_realtime_audio_port, &conn->realtime_audio_socket); if (err) { die("Error %d: could not find a UDP port to use as a realtime_audio port", err); } debug(2, "Connection %d: UDP realtime audio port opened: %u.", conn->connection_number, conn->local_realtime_audio_port); named_pthread_create(&conn->rtp_realtime_audio_thread, NULL, &rtp_realtime_audio_receiver, (void *)conn, "ap2_ra_%d", conn->connection_number); plist_dict_set_item(stream0dict, "type", plist_new_uint(96)); plist_dict_set_item(stream0dict, "dataPort", plist_new_uint(conn->local_realtime_audio_port)); debug(2, "Realtime Stream Play"); activity_monitor_signify_activity(1); player_play(conn); conn->rtp_running = 1; // hack! } break; case 103: { debug_log_rtsp_message(3, "Buffered Audio Stream SETUP incoming message", req); conn->airplay_stream_type = buffered_stream; // get the audio format code item = plist_dict_get_item(stream0, "audioFormat"); // audio format if (item != NULL) { plist_get_uint_val(item, &conn->audio_format); // see https://emanuelecozzi.net/docs/airplay2/audio/ for values // seems to be only the initial format -- it seems as if it can change dynamically } else { debug(1, "No audio format (audioFormat) property found in setup."); } // bind a new TCP port and get a socket conn->local_buffered_audio_port = 0; // any port err = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_buffered_audio_port, &conn->buffered_audio_socket); if (err) { die("SETUP on Connection %d: Error %d: could not find a TCP port to use as a " "buffered_audio port", conn->connection_number, err); } listen(conn->buffered_audio_socket, 128); // ensure it's open before telling the client debug(2, "Connection %d: TCP Buffered Audio port opened: %u.", conn->connection_number, conn->local_buffered_audio_port); activity_monitor_signify_activity(1); // debug(1, "Connection %d: create rtp_buffered_audio_thread", conn->connection_number); named_pthread_create_with_priority(&conn->rtp_buffered_audio_thread, 2, &rtp_buffered_audio_processor, (void *)conn, "ap2_ba_%d", conn->connection_number); plist_dict_set_item(stream0dict, "type", plist_new_uint(103)); plist_dict_set_item(stream0dict, "dataPort", plist_new_uint(conn->local_buffered_audio_port)); plist_dict_set_item(stream0dict, "audioBufferSize", plist_new_uint(conn->ap2_audio_buffer_size)); // this should be cancelled by an activity_monitor_signify_activity(1) // call in the SETRATEANCHORI handler, which should come up right away activity_monitor_signify_activity(0); player_play(conn); conn->rtp_running = 1; // hack! } break; case 130: { debug(1, "Remote Control Setup Received on a PTP connection."); debug_log_rtsp_message(2, "Incoming message", req); } break; default: debug(1, "SETUP on Connection %d: Unhandled stream type %" PRIu64 ".", conn->connection_number, item_value); debug_log_rtsp_message(1, "Unhandled stream type incoming message", req); } plist_dict_set_item(stream0dict, "controlPort", plist_new_uint(conn->local_ap2_control_port)); plist_array_append_item(streams_array, stream0dict); plist_dict_set_item(setupResponsePlist, "streams", streams_array); resp->respcode = 200; } else if (conn->airplay_stream_category == remote_control_stream) { debug(3, "Connection %d (RC): SETUP: Remote Control Only with stream received from %s.", conn->connection_number, conn->client_ip_string); debug_log_rtsp_message(3, "Remote Control Stream SETUP incoming message", req); plist_t seed_item = NULL; // the data port and listener thread may already have been set up // if so, the local_data_port will be non-zero if (conn->local_data_port == 0) { // set up data channel ciphering plist_t dict = plist_array_get_item(streams, 0); if (dict != NULL) { // get the seed that becomes the suffix for the salt seed_item = plist_dict_get_item(dict, "seed"); // session key uint64_t seed = 0; if (seed_item != NULL) { plist_get_uint_val(seed_item, &seed); char salt_suffix[256] = ""; snprintf(salt_suffix, sizeof(salt_suffix), "%" PRIu64 "", seed); conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 5, conn->pair_setup_result->shared_secret, conn->pair_setup_result->shared_secret_len, salt_suffix); // last argument is the (possible) dynamic salt suffix if (conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx != NULL) { conn->ap2_pairing_context.data_cipher_bundle.description = strdup("DataStream"); // get a port to use as an data port // bind a new TCP port and get a socket conn->local_data_port = 0; // any port int lerr = bind_socket_and_port(SOCK_STREAM, conn->connection_ip_family, conn->self_ip_string, conn->self_scope_id, &conn->local_data_port, &conn->data_socket); if (lerr) { die("SETUP on Connection %d (RC): Error %d: could not find a TCP port to use as a " "data " "port", conn->connection_number, lerr); } listen(conn->data_socket, 128); // open port for listening before telling the client about it! debug(2, "Connection %d SETUP (RC): TCP Remote Control data port opened: %u.", conn->connection_number, conn->local_data_port); } else { debug(1, "Connection %d: SETUP: Error setting up rtsp data channel ciphering.", conn->connection_number); } } else { debug(2, "Connection %d: SETUP: No data channel encryption salt seed found.", conn->connection_number); } } else { debug(1, "Connection %d: SETUP: Could not find the streams array", conn->connection_number); } } else { debug(1, "Connection %d SETUP (RC): data port already allocated.", conn->connection_number); } plist_t coreResponseDict = plist_new_dict(); plist_dict_set_item(coreResponseDict, "streamID", plist_new_uint(1)); plist_dict_set_item(coreResponseDict, "type", plist_new_uint(130)); if (seed_item != NULL) plist_dict_set_item(coreResponseDict, "dataPort", plist_new_uint(conn->local_data_port)); plist_t coreResponseArray = plist_new_array(); plist_array_append_item(coreResponseArray, coreResponseDict); plist_dict_set_item(setupResponsePlist, "streams", coreResponseArray); resp->respcode = 200; } else { debug(1, "Connection %d: SETUP: Stream received but no airplay category set. Nothing done.", conn->connection_number); } } if (resp->respcode == 200) { plist_to_bin(setupResponsePlist, &resp->content, &resp->contentlength); plist_free(setupResponsePlist); msg_add_header(resp, "Content-Type", "application/x-apple-binary-plist"); } plist_free(messagePlist); } #endif void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(3, "Connection %d: SETUP", conn->connection_number); resp->respcode = 451; // invalid arguments -- expect them // check this connection has the principal_conn, obtained during a prior ANNOUNCE if ((conn != NULL) && (principal_conn == conn)) { uint16_t cport, tport; char *ar = msg_get_header(req, "Active-Remote"); if (ar) { debug(2, "Connection %d: SETUP: Active-Remote string seen: \"%s\".", conn->connection_number, ar); // get the active remote if (conn->dacp_active_remote) // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1); #endif } else { debug(2, "Connection %d: SETUP: Note: no Active-Remote information seen.", conn->connection_number); if (conn->dacp_active_remote) { // this is in case SETUP was previously called free(conn->dacp_active_remote); conn->dacp_active_remote = NULL; } } ar = msg_get_header(req, "DACP-ID"); if (ar) { debug(2, "Connection %d: SETUP: DACP-ID string seen: \"%s\".", conn->connection_number, ar); if (conn->dacp_id) // this is in case SETUP was previously called free(conn->dacp_id); conn->dacp_id = strdup(ar); #ifdef CONFIG_METADATA send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1); #endif } else { debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.", conn->connection_number); if (conn->dacp_id) { // this is in case SETUP was previously called free(conn->dacp_id); conn->dacp_id = NULL; } } char *hdr = msg_get_header(req, "Transport"); if (hdr) { char *p; p = strstr(hdr, "control_port="); if (p) { p = strchr(p, '=') + 1; cport = atoi(p); p = strstr(hdr, "timing_port="); if (p) { p = strchr(p, '=') + 1; tport = atoi(p); if (conn->rtp_running) { if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) { warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) " "or " "timing (old %u, new " "%u) ports! This is probably fatal!", conn->connection_number, conn->remote_control_port, cport, conn->remote_timing_port, tport); } else { warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing " "(%u) " "ports. This is " "probably not fatal.", conn->connection_number, conn->remote_control_port, conn->remote_timing_port); } } else { rtp_setup(&conn->local, &conn->remote, cport, tport, conn); } if (conn->local_audio_port != 0) { char resphdr[256] = ""; snprintf(resphdr, sizeof(resphdr), "RTP/AVP/" "UDP;unicast;interleaved=0-1;mode=record;control_port=%d;" "timing_port=%d;server_" "port=%d", conn->local_control_port, conn->local_timing_port, conn->local_audio_port); msg_add_header(resp, "Transport", resphdr); msg_add_header(resp, "Session", "1"); resp->respcode = 200; // it all worked out okay debug(2, "Connection %d: SETUP DACP-ID \"%s\" from %s to %s with UDP ports Control: " "%d, Timing: %d and Audio: %d.", conn->connection_number, conn->dacp_id, (char *)&conn->client_ip_string, (char *)&conn->self_ip_string, conn->local_control_port, conn->local_timing_port, conn->local_audio_port); } else { debug(1, "Connection %d: SETUP seems to specify a null audio port.", conn->connection_number); } } else { debug(1, "Connection %d: SETUP doesn't specify a timing_port.", conn->connection_number); } } else { debug(1, "Connection %d: SETUP doesn't specify a control_port.", conn->connection_number); } } else { debug(1, "Connection %d: SETUP doesn't contain a Transport header.", conn->connection_number); } } else { warn("Connection %d SETUP received without having the player (no ANNOUNCE?)", conn->connection_number); } if (resp->respcode == 200) { do_pthread_setname(&conn->thread, "rtsp_1_%d", conn->connection_number); #ifdef CONFIG_METADATA send_ssnc_metadata('clip', conn->client_ip_string, strlen(conn->client_ip_string), 1); send_ssnc_metadata('svip', conn->self_ip_string, strlen(conn->self_ip_string), 1); #endif } else { debug(1, "Connection %d: SETUP error -- releasing the player lock.", conn->connection_number); release_play_lock(conn); } } /* static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(1, "Connection thread %d: IGNORE", conn->connection_number); resp->respcode = 200; } */ void handle_set_parameter_parameter(rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { char *cp = req->content; int cp_left = req->contentlength; /* int k = cp_left; if (k>max_bytes) k = max_bytes; for (i = 0; i < k; i++) snprintf((char *)buf + 2 * i, 3, "%02x", cp[i]); debug(1, "handle_set_parameter_parameter: \"%s\".",buf); */ char *next; while (cp_left && cp) { next = nextline(cp, cp_left); // note: "next" will return NULL if there is no \r or \n or \r\n at the end of this // but we are always guaranteed that if cp is not null, it will be pointing to something // NUL-terminated if (next) cp_left -= (next - cp); else cp_left = 0; if (!strncmp(cp, "volume: ", strlen("volume: "))) { float volume = atof(cp + strlen("volume: ")); debug(3, "Connection %d: request to set AirPlay Volume to: %f.", conn->connection_number, volume); // if we are playing, go ahead and change the volume // #ifdef CONFIG_DBUS_INTERFACE // if (dbus_service_is_running()) { // shairport_sync_set_volume(shairportSyncSkeleton, volume); // } else { // #endif pthread_rwlock_rdlock(&principal_conn_lock); // don't let the principal_conn be changed pthread_cleanup_push(rwlock_unlock, (void *)&principal_conn_lock); if (principal_conn == conn) { debug(3, "Connection %d: set player volume to %.3f.", conn->connection_number, volume); player_volume(volume, conn); debug(3, "Connection %d: set player volume to %.3f success.", conn->connection_number, volume); } else { if (principal_conn != NULL) debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is %d.", conn->connection_number, volume, principal_conn->connection_number); else debug(1, "Connection %d: fail to set player volume to %.3f. Principal conn is NULL.", conn->connection_number, volume); } if (conn != NULL) { conn->own_airplay_volume = volume; conn->own_airplay_volume_set = 1; } pthread_cleanup_pop(1); // release the principal_conn lock // #ifdef CONFIG_DBUS_INTERFACE // } // #endif } else if (strncmp(cp, "progress: ", strlen("progress: ")) == 0) { // this can be sent even when metadata is not solicited #ifdef CONFIG_METADATA char *progress = cp + strlen("progress: "); // debug(2, "progress: \"%s\"",progress); // rtpstampstart/rtpstampnow/rtpstampend 44100 // (always?) per second send_ssnc_metadata('prgr', progress, strlen(progress), 1); #endif } else { debug(1, "Connection %d, unrecognised parameter: \"%s\"\n", conn->connection_number, cp); } cp = next; } } #ifdef CONFIG_METADATA static void handle_set_parameter_metadata(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, __attribute__((unused)) rtsp_message *resp) { char *cp = req->content; unsigned int cl = req->contentlength; unsigned int off = 8; uint32_t itag, vl; while (off + 8 <= cl) { // pick up the metadata tag as an unsigned longint memcpy(&itag, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ itag = ntohl(itag); off += sizeof(uint32_t); // pick up the length of the data memcpy(&vl, (uint32_t *)(cp + off), sizeof(uint32_t)); /* can be misaligned, thus memcpy */ vl = ntohl(vl); off += sizeof(uint32_t); if (vl > cl - off) break; // pass the data over if (vl == 0) send_metadata('core', itag, NULL, 0, NULL, 1); else send_metadata('core', itag, (char *)(cp + off), vl, req, 1); // move on to the next item off += vl; } } #endif static void handle_get_parameter(__attribute__((unused)) rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { // debug(1, "Connection %d: GET_PARAMETER", conn->connection_number); // debug_print_msg_headers(1,req); // debug_print_msg_content(1,req); if ((req->content) && (req->contentlength == strlen("volume\r\n")) && strstr(req->content, "volume") == req->content) { debug(2, "Connection %d: current volume (%.6f) requested", conn->connection_number, suggested_volume(conn)); char *p = malloc(128); // will be automatically deallocated with the response is deleted if (p) { resp->content = p; resp->contentlength = snprintf(p, 128, "\r\nvolume: %.6f\r\n", suggested_volume(conn)); } else { debug(1, "Couldn't allocate space for a response."); } } resp->respcode = 200; } static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(4, "Connection %d: SET_PARAMETER", conn->connection_number); // if (!req->contentlength) // debug(1, "received empty SET_PARAMETER request."); // debug_print_msg_headers(1,req); char *ct = msg_get_header(req, "Content-Type"); if (ct) { // debug(2, "SET_PARAMETER Content-Type:\"%s\".", ct); #ifdef CONFIG_METADATA // It seems that the rtptime of the message is used as a kind of an ID that // can be used // to link items of metadata, including pictures, that refer to the same // entity. // If they refer to the same item, they have the same rtptime. // So we send the rtptime before and after both the metadata items and the // picture item // get the rtptime char *p = NULL; char *hdr = msg_get_header(req, "RTP-Info"); if (hdr) { p = strstr(hdr, "rtptime="); if (p) { p = strchr(p, '='); } } // not all items have RTP-time stuff in them, which is okay if (!strncmp(ct, "application/x-dmap-tagged", 25)) { debug(4, "received metadata tags in SET_PARAMETER request."); if (p == NULL) debug(1, "Missing RTP-Time info for metadata"); if (p) send_metadata('ssnc', 'mdst', p + 1, strlen(p + 1), req, 1); // metadata starting else send_metadata('ssnc', 'mdst', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available handle_set_parameter_metadata(conn, req, resp); if (p) send_metadata('ssnc', 'mden', p + 1, strlen(p + 1), req, 1); // metadata ending else send_metadata('ssnc', 'mden', NULL, 0, NULL, 0); // metadata starting, if rtptime is not available } else if (!strncmp(ct, "image", 5)) { // Some server simply ignore the md field from the TXT record. If The // config says 'please, do not include any cover art', we are polite and // do not write them to the pipe. if (config.get_coverart) { // debug(1, "received image in SET_PARAMETER request."); // note: the image/type tag isn't reliable, so it's not being sent // -- best look at the first few bytes of the image if (p == NULL) debug(1, "Missing RTP-Time info for picture item"); if (p) send_metadata('ssnc', 'pcst', p + 1, strlen(p + 1), req, 1); // picture starting else send_metadata('ssnc', 'pcst', NULL, 0, NULL, 0); // picture starting, if rtptime is not available send_metadata('ssnc', 'PICT', req->content, req->contentlength, req, 1); if (p) send_metadata('ssnc', 'pcen', p + 1, strlen(p + 1), req, 1); // picture ending else send_metadata('ssnc', 'pcen', NULL, 0, NULL, 0); // picture ending, if rtptime is not available } else { debug(1, "Ignore received picture item (include_cover_art = no)."); } } else #endif if (!strncmp(ct, "text/parameters", 15)) { debug(3, "received parameters in SET_PARAMETER request."); handle_set_parameter_parameter(conn, req, resp); // this could be volume or progress } else { debug(1, "Connection %d: received unknown Content-Type \"%s\" in SET_PARAMETER request.", conn->connection_number, ct); debug_print_msg_headers(1, req); } } else { debug(1, "Connection %d: missing Content-Type header in SET_PARAMETER request.", conn->connection_number); } resp->respcode = 200; } static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { debug(2, "Connection %d: ANNOUNCE", conn->connection_number); #ifdef CONFIG_AIRPLAY_2 conn->airplay_stream_category = classic_airplay_stream; // already set in Classic AirPlay build play_lock_r get_play_status = get_play_lock( conn, 1); // always allow interruption in the Classic-AirPlay-in-AirPlay-2 mode (?) #else play_lock_r get_play_status = get_play_lock(conn, config.allow_session_interruption); #endif if (get_play_status != play_lock_aquisition_failed) { // this has already been checked for in Classic Airplay and would be play_lock_already_acquired // here. if this new session did not break in, then it's okay to reset the next UDP ports to the // start of the range if (get_play_status == play_lock_acquired_without_breaking_in) { // if it's safe to re-use original UDP ports resetFreeUDPPort(); } /* { char *cp = req->content; int cp_left = req->contentlength; while (cp_left > 1) { if (strlen(cp) != 0) debug(1,">>>>>> %s", cp); cp += strlen(cp) + 1; cp_left -= strlen(cp) + 1; } } */ #ifdef CONFIG_AIRPLAY_2 // In AirPlay 2, an ANNOUNCE signifies the start of an AirPlay 1 session. debug(1, "Connection %d: %s connection from %s:%u to self at %s:%u.", conn->connection_number, get_category_string(conn->airplay_stream_category), conn->client_ip_string, conn->client_rtsp_port, conn->self_ip_string, conn->self_rtsp_port); conn->airplay_type = ap_1; conn->timing_type = ts_ntp; conn->type = 96; // this is the AirPlay 2 code for Realtime Audio -- not sure it's right #endif conn->stream.type = ast_unknown; resp->respcode = 200; // presumed OK char *pssid = NULL; char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; char *pminlatency = NULL; char *pmaxlatency = NULL; // char *pAudioMediaInfo = NULL; char *pUncompressedCDAudio = NULL; char *cp = req->content; int cp_left = req->contentlength; char *next; while (cp_left && cp) { next = nextline(cp, cp_left); cp_left -= next - cp; if (!strncmp(cp, "a=rtpmap:96 L16/44100/2", strlen("a=rtpmap:96 L16/44100/2"))) pUncompressedCDAudio = cp + strlen("a=rtpmap:96 L16/44100/2"); // if (!strncmp(cp, "m=audio", strlen("m=audio"))) // pAudioMediaInfo = cp + strlen("m=audio"); if (!strncmp(cp, "o=iTunes", strlen("o=iTunes"))) pssid = cp + strlen("o=iTunes"); if (!strncmp(cp, "a=fmtp:", strlen("a=fmtp:"))) pfmtp = cp + strlen("a=fmtp:"); if (!strncmp(cp, "a=aesiv:", strlen("a=aesiv:"))) paesiv = cp + strlen("a=aesiv:"); if (!strncmp(cp, "a=rsaaeskey:", strlen("a=rsaaeskey:"))) prsaaeskey = cp + strlen("a=rsaaeskey:"); if (!strncmp(cp, "a=min-latency:", strlen("a=min-latency:"))) pminlatency = cp + strlen("a=min-latency:"); if (!strncmp(cp, "a=max-latency:", strlen("a=max-latency:"))) pmaxlatency = cp + strlen("a=max-latency:"); cp = next; } if (pUncompressedCDAudio) { debug(2, "An uncompressed PCM stream has been detected."); conn->stream.type = ast_uncompressed; conn->frames_per_packet = 352; // number of audio frames per packet. debug(2, "Set conn->input_rate: 44100"); conn->input_rate = 44100; conn->input_num_channels = 2; conn->input_bit_depth = 16; conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); /* int y = strlen(pAudioMediaInfo); if (y > 0) { char obf[4096]; if (y > 4096) y = 4096; char *p = pAudioMediaInfo; char *obfp = obf; int obfc; for (obfc = 0; obfc < y; obfc++) { snprintf(obfp, 3, "%02X", (unsigned int)*p); p++; obfp += 2; }; *obfp = 0; debug(1, "AudioMediaInfo: \"%s\".", obf); } */ } if (pssid) { uint32_t ssid = uatoi(pssid); debug(3, "Synchronisation Source Identifier: %08X,%u", ssid, ssid); } if (pminlatency) { conn->minimum_latency = atoi(pminlatency); debug(3, "Minimum latency %d specified", conn->minimum_latency); } if (pmaxlatency) { conn->maximum_latency = atoi(pmaxlatency); debug(3, "Maximum latency %d specified", conn->maximum_latency); } if ((paesiv == NULL) && (prsaaeskey == NULL)) { // debug(1,"Unencrypted session requested?"); conn->stream.encrypted = 0; } else if ((paesiv != NULL) && (prsaaeskey != NULL)) { conn->stream.encrypted = 1; // debug(1,"Encrypted session requested"); } else { warn("Invalid Announce message -- missing paesiv or prsaaeskey."); resp->respcode = 456; // 456 - Header Field Not Valid for Resource // goto out; } if (conn->stream.encrypted) { int len, keylen; uint8_t *aesiv = base64_dec(paesiv, &len); if (len == 16) { memcpy(conn->stream.aesiv, aesiv, 16); } else { resp->respcode = 456; // 456 - Header Field Not Valid for Resource warn("client announced aeskey of %d bytes, wanted 16", len); } free(aesiv); uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); free(rsaaeskey); if (keylen == 16) { memcpy(conn->stream.aeskey, aeskey, 16); } else { resp->respcode = 456; // 456 - Header Field Not Valid for Resource warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); } free(aeskey); } if (pfmtp) { conn->stream.type = ast_apple_lossless; debug(3, "An ALAC stream has been detected."); // Set reasonable connection defaults conn->stream.fmtp[0] = 96; conn->stream.fmtp[1] = 352; conn->stream.fmtp[2] = 0; conn->stream.fmtp[3] = 16; conn->stream.fmtp[4] = 40; conn->stream.fmtp[5] = 10; conn->stream.fmtp[6] = 14; conn->stream.fmtp[7] = 2; conn->stream.fmtp[8] = 255; conn->stream.fmtp[9] = 0; conn->stream.fmtp[10] = 0; conn->stream.fmtp[11] = 44100; unsigned int i = 0; unsigned int max_param = sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); char *found; while ((found = strsep(&pfmtp, " \t")) != NULL && i < max_param) { conn->stream.fmtp[i++] = atoi(found); } // here we should check the sanity of the fmtp values // for (i = 0; i < sizeof(conn->stream.fmtp) / sizeof(conn->stream.fmtp[0]); i++) // debug(1," fmtp[%2d] is: %10d",i,conn->stream.fmtp[i]); // set the parameters of the player (as distinct from the parameters of the decoder -- that's // done later). conn->frames_per_packet = conn->stream.fmtp[1]; // number of audio frames per packet. conn->input_rate = conn->stream.fmtp[11]; debug(2, "Set conn->input_rate: %u.", conn->input_rate); conn->input_num_channels = conn->stream.fmtp[7]; conn->input_bit_depth = conn->stream.fmtp[3]; conn->input_bytes_per_frame = conn->input_num_channels * ((conn->input_bit_depth + 7) / 8); } if ((resp->respcode == 200) && (conn->stream.type != ast_unknown)) { char *hdr = msg_get_header(req, "X-Apple-Client-Name"); if (hdr) { debug(1, "Play connection from device named \"%s\" on RTSP conversation thread %d.", hdr, conn->connection_number); #ifdef CONFIG_METADATA send_metadata('ssnc', 'snam', hdr, strlen(hdr), req, 1); #endif } hdr = msg_get_header(req, "User-Agent"); if (hdr) { conn->UserAgent = strdup(hdr); debug(2, "Play connection from user agent \"%s\" on RTSP conversation thread %d.", hdr, conn->connection_number); // if the user agent is AirPlay and has a version number of 353 or less (from iOS 11.1,2) // use the older way of calculating the latency char *p = strstr(hdr, "AirPlay"); if (p) { p = strchr(p, '/'); if (p) { conn->AirPlayVersion = atoi(p + 1); debug(2, "AirPlay version %d detected.", conn->AirPlayVersion); } } conn->input_format_is_valid = 1; #ifdef CONFIG_METADATA send_metadata('ssnc', 'snua', hdr, strlen(hdr), req, 1); #endif } } else { warn("Can not process the following ANNOUNCE message:"); // print each line of the request content // the problem is that nextline has replace all returns, newlines, etc. by // NULLs char *lcp = req->content; int lcp_left = req->contentlength; while (lcp_left > 1) { if (strlen(lcp) != 0) warn(" %s", lcp); lcp += strlen(lcp) + 1; lcp_left -= strlen(lcp) + 1; } } debug(2, "Connection %d: ANNOUNCE has completed.", conn->connection_number); } else { // can't get the principal_conn resp->respcode = 453; } } #ifdef CONFIG_AIRPLAY_2 static struct method_handler { char *method; void (*ap1_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 1 void (*ap2_handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 2 } method_handlers[] = {{"OPTIONS", handle_options, handle_options_2}, {"ANNOUNCE", handle_announce, handle_announce}, {"FLUSH", handle_flush, handle_flush}, {"TEARDOWN", handle_teardown, handle_teardown_2}, {"SETUP", handle_setup, handle_setup_2}, {"GET_PARAMETER", handle_get_parameter, handle_get_parameter}, {"SET_PARAMETER", handle_set_parameter, handle_set_parameter}, {"RECORD", handle_record, handle_record_2}, {"GET", handle_get, handle_get}, {"POST", handle_post, handle_post}, {"SETPEERS", handle_unimplemented_ap1, handle_setpeers}, {"SETPEERSX", handle_unimplemented_ap1, handle_setpeersx}, {"SETRATEANCHORTI", handle_unimplemented_ap1, handle_setrateanchori}, {"FLUSHBUFFERED", handle_unimplemented_ap1, handle_flushbuffered}, {"SETRATE", handle_unimplemented_ap1, handle_setrate}, {NULL, NULL, NULL}}; #else static struct method_handler { char *method; void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); // for AirPlay 1 only } method_handlers[] = {{"OPTIONS", handle_options}, {"GET", handle_get}, {"POST", handle_post}, {"ANNOUNCE", handle_announce}, {"FLUSH", handle_flush}, {"TEARDOWN", handle_teardown}, {"SETUP", handle_setup}, {"GET_PARAMETER", handle_get_parameter}, {"SET_PARAMETER", handle_set_parameter}, {"RECORD", handle_record}, {NULL, NULL}}; #endif static void apple_challenge(int lfd, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "Apple-Challenge"); if (!hdr) return; SOCKADDR fdsa; socklen_t sa_len = sizeof(fdsa); getsockname(lfd, (struct sockaddr *)&fdsa, &sa_len); int chall_len; uint8_t *chall = base64_dec(hdr, &chall_len); if (chall == NULL) die("null chall in apple_challenge"); uint8_t buf[48], *bp = buf; int i; memset(buf, 0, sizeof(buf)); if (chall_len > 16) { warn("oversized Apple-Challenge!"); free(chall); return; } memcpy(bp, chall, chall_len); free(chall); bp += chall_len; #ifdef AF_INET6 if (fdsa.SAFAMILY == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(&fdsa); memcpy(bp, sa6->sin6_addr.s6_addr, 16); bp += 16; } else #endif { struct sockaddr_in *sa = (struct sockaddr_in *)(&fdsa); memcpy(bp, &sa->sin_addr.s_addr, 4); bp += 4; } for (i = 0; i < 6; i++) *bp++ = config.ap1_prefix[i]; int buflen, resplen; buflen = bp - buf; if (buflen < 0x20) buflen = 0x20; uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); char *encoded = base64_enc(challresp, resplen); if (encoded == NULL) die("could not allocate memory for \"encoded\""); // strip the padding. char *padding = strchr(encoded, '='); if (padding) *padding = 0; msg_add_header(resp, "Apple-Response", encoded); // will be freed when the response is freed. free(challresp); free(encoded); } static char *make_nonce(void) { uint8_t random[8]; int lfd = open("/dev/urandom", O_RDONLY); if (lfd < 0) die("could not open /dev/urandom!"); // int ignore = if (read(lfd, random, sizeof(random)) != sizeof(random)) debug(1, "Error reading /dev/urandom"); safe_socket_close(&lfd); return base64_enc(random, 8); // returns a pointer to malloc'ed memory } static int rtsp_classic_airplay_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { if (!config.password) return 0; if (!*nonce) { *nonce = make_nonce(); goto authenticate; } char *hdr = msg_get_header(req, "Authorization"); if (!hdr || strncmp(hdr, "Digest ", 7)) goto authenticate; char *realm = strstr(hdr, "realm=\""); char *username = strstr(hdr, "username=\""); char *response = strstr(hdr, "response=\""); char *uri = strstr(hdr, "uri=\""); if (!realm || !username || !response || !uri) goto authenticate; char *quote; realm = strchr(realm, '"') + 1; if (!(quote = strchr(realm, '"'))) goto authenticate; *quote = 0; username = strchr(username, '"') + 1; if (!(quote = strchr(username, '"'))) goto authenticate; *quote = 0; response = strchr(response, '"') + 1; if (!(quote = strchr(response, '"'))) goto authenticate; *quote = 0; uri = strchr(uri, '"') + 1; if (!(quote = strchr(uri, '"'))) goto authenticate; *quote = 0; uint8_t digest_urp[16], digest_mu[16], digest_total[16]; #ifdef CONFIG_OPENSSL EVP_MD_CTX *ctx; unsigned int digest_urp_len = EVP_MD_size(EVP_md5()); unsigned int digest_mu_len = EVP_MD_size(EVP_md5()); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_md5(), NULL); EVP_DigestUpdate(ctx, username, strlen(username)); EVP_DigestUpdate(ctx, ":", 1); EVP_DigestUpdate(ctx, realm, strlen(realm)); EVP_DigestUpdate(ctx, ":", 1); EVP_DigestUpdate(ctx, config.password, strlen(config.password)); EVP_DigestFinal_ex(ctx, digest_urp, &digest_urp_len); EVP_MD_CTX_free(ctx); ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_md5(), NULL); EVP_DigestUpdate(ctx, req->method, strlen(req->method)); EVP_DigestUpdate(ctx, ":", 1); EVP_DigestUpdate(ctx, uri, strlen(uri)); EVP_DigestFinal_ex(ctx, digest_mu, &digest_mu_len); EVP_MD_CTX_free(ctx); pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_context tctx; mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, (const unsigned char *)username, strlen(username)); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)realm, strlen(realm)); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)config.password, strlen(config.password)); mbedtls_md5_finish_ret(&tctx, digest_urp); mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, (const unsigned char *)req->method, strlen(req->method)); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish_ret(&tctx, digest_mu); #else mbedtls_md5_context tctx; mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)username, strlen(username)); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); mbedtls_md5_finish(&tctx, digest_urp); mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); mbedtls_md5_finish(&tctx, digest_mu); #endif #endif #ifdef CONFIG_POLARSSL md5_context tctx; md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)username, strlen(username)); md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)realm, strlen(realm)); md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)config.password, strlen(config.password)); md5_finish(&tctx, digest_urp); md5_starts(&tctx); md5_update(&tctx, (const unsigned char *)req->method, strlen(req->method)); md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)uri, strlen(uri)); md5_finish(&tctx, digest_mu); #endif int i; unsigned char buf[33]; for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_urp[i]); #ifdef CONFIG_OPENSSL pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); unsigned int digest_total_len = EVP_MD_size(EVP_md5()); ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(ctx, EVP_md5(), NULL); EVP_DigestUpdate(ctx, buf, 32); EVP_DigestUpdate(ctx, ":", 1); EVP_DigestUpdate(ctx, *nonce, strlen(*nonce)); EVP_DigestUpdate(ctx, ":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); EVP_DigestUpdate(ctx, buf, 32); EVP_DigestFinal_ex(ctx, digest_total, &digest_total_len); EVP_MD_CTX_free(ctx); pthread_setcancelstate(oldState, NULL); #endif #ifdef CONFIG_MBEDTLS #if MBEDTLS_VERSION_MINOR >= 7 mbedtls_md5_starts_ret(&tctx); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); mbedtls_md5_update_ret(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); mbedtls_md5_update_ret(&tctx, (unsigned char *)":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update_ret(&tctx, buf, 32); mbedtls_md5_finish_ret(&tctx, digest_total); #else mbedtls_md5_starts(&tctx); mbedtls_md5_update(&tctx, buf, 32); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); mbedtls_md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); mbedtls_md5_update(&tctx, (unsigned char *)":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); mbedtls_md5_update(&tctx, buf, 32); mbedtls_md5_finish(&tctx, digest_total); #endif #endif #ifdef CONFIG_POLARSSL md5_starts(&tctx); md5_update(&tctx, buf, 32); md5_update(&tctx, (unsigned char *)":", 1); md5_update(&tctx, (const unsigned char *)*nonce, strlen(*nonce)); md5_update(&tctx, (unsigned char *)":", 1); for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_mu[i]); md5_update(&tctx, buf, 32); md5_finish(&tctx, digest_total); #endif for (i = 0; i < 16; i++) snprintf((char *)buf + 2 * i, 3, "%02x", digest_total[i]); if (!strcmp(response, (const char *)buf)) return 0; warn("Password authorization failed."); authenticate: resp->respcode = 401; int hdrlen = strlen(*nonce) + 40; char *authhdr = malloc(hdrlen); snprintf(authhdr, hdrlen, "Digest realm=\"raop\", nonce=\"%s\"", *nonce); msg_add_header(resp, "WWW-Authenticate", authhdr); free(authhdr); return 1; } void rtsp_conversation_thread_cleanup_function(void *arg) { rtsp_conn_info *conn = (rtsp_conn_info *)arg; if (conn != NULL) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(3, "Connection %d: %s rtsp_conversation_thread_func_cleanup_function called.", conn->connection_number, get_category_string(conn->airplay_stream_category)); if (conn->player_thread) { player_stop(conn); // this nulls the player_thread and cancels the threads... activity_monitor_signify_activity(0); // inactive, and should be after command_stop() } if (conn->fd > 0) { debug( 2, "Connection %d: terminating -- closing RTSP connection socket %d: from %s:%u to self at " "%s:%u.", conn->connection_number, conn->fd, conn->client_ip_string, conn->client_rtsp_port, conn->self_ip_string, conn->self_rtsp_port); safe_socket_close(&conn->fd); } #ifdef CONFIG_AIRPLAY_2 if (conn->session_key) { free(conn->session_key); conn->session_key = NULL; } // give the event receiver a chance to exit normally, if it exists if (conn->rtp_event_thread != NULL) { pthread_cancel(*conn->rtp_event_thread); pthread_join(*conn->rtp_event_thread, NULL); free(conn->rtp_event_thread); conn->rtp_event_thread = NULL; } conn->ap2_event_receiver_exited = 0; debug(3, "Connection %d: %s event thread deleted.", conn->connection_number, get_category_string(conn->airplay_stream_category)); #endif debug(3, "Connection %d: terminating -- closing timing, control and audio sockets...", conn->connection_number); if (conn->control_socket) { debug(3, "Connection %d: terminating -- closing control_socket %d.", conn->connection_number, conn->control_socket); safe_socket_close(&conn->control_socket); } if (conn->timing_socket) { debug(3, "Connection %d: terminating -- closing timing_socket %d.", conn->connection_number, conn->timing_socket); safe_socket_close(&conn->timing_socket); } if (conn->audio_socket) { debug(3, "Connection %d: terminating -- closing audio_socket %d.", conn->connection_number, conn->audio_socket); safe_socket_close(&conn->audio_socket); } if (conn->auth_nonce) { free(conn->auth_nonce); conn->auth_nonce = NULL; } #ifdef CONFIG_AIRPLAY_2 buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.plaintext_read_buffer, -1); buf_drain(&conn->ap2_pairing_context.control_cipher_bundle.encrypted_read_buffer, -1); if (conn->ap2_pairing_context.control_cipher_bundle.description != NULL) free(conn->ap2_pairing_context.control_cipher_bundle.description); pair_cipher_free(conn->ap2_pairing_context.control_cipher_bundle.cipher_ctx); buf_drain(&conn->ap2_pairing_context.event_cipher_bundle.plaintext_read_buffer, -1); buf_drain(&conn->ap2_pairing_context.event_cipher_bundle.encrypted_read_buffer, -1); if (conn->ap2_pairing_context.event_cipher_bundle.description != NULL) free(conn->ap2_pairing_context.event_cipher_bundle.description); pair_cipher_free(conn->ap2_pairing_context.event_cipher_bundle.cipher_ctx); buf_drain(&conn->ap2_pairing_context.data_cipher_bundle.plaintext_read_buffer, -1); buf_drain(&conn->ap2_pairing_context.data_cipher_bundle.encrypted_read_buffer, -1); if (conn->ap2_pairing_context.data_cipher_bundle.description != NULL) free(conn->ap2_pairing_context.data_cipher_bundle.description); pair_cipher_free(conn->ap2_pairing_context.data_cipher_bundle.cipher_ctx); pair_setup_free(conn->ap2_pairing_context.setup_ctx); pair_verify_free(conn->ap2_pairing_context.verify_ctx); if (conn->airplay_gid) { free(conn->airplay_gid); conn->airplay_gid = NULL; } #endif rtp_terminate(conn); if (conn->dacp_id) { free(conn->dacp_id); conn->dacp_id = NULL; } if (conn->UserAgent) { free(conn->UserAgent); conn->UserAgent = NULL; } #ifdef CONFIG_AIRPLAY_2 if (conn->ap2_client_name) { free(conn->ap2_client_name); conn->ap2_client_name = NULL; } #endif // remove flow control and mutexes int rc = pthread_mutex_destroy(&conn->volume_control_mutex); if (rc) debug(1, "Connection %d: error %d destroying volume_control_mutex.", conn->connection_number, rc); rc = pthread_cond_destroy(&conn->flowcontrol); if (rc) debug(1, "Connection %d: error %d destroying flow control condition variable.", conn->connection_number, rc); rc = pthread_mutex_destroy(&conn->ab_mutex); if (rc) debug(1, "Connection %d: error %d destroying ab_mutex.", conn->connection_number, rc); rc = pthread_mutex_destroy(&conn->flush_mutex); if (rc) debug(1, "Connection %d: error %d destroying flush_mutex.", conn->connection_number, rc); #ifdef CONFIG_AIRPLAY_2 rc = pthread_mutex_destroy(&conn->event_sender_mutex); if (rc) debug(1, "Connection %d: error %d destroying event_sender_mutex.", conn->connection_number, rc); #endif debug(3, "Connection %d: Closed.", conn->connection_number); conn->running = 0; // for the garbage collector // release_play_lock(conn); pthread_setcancelstate(oldState, NULL); } } void msg_cleanup_function(void *arg) { debug(4, "msg_cleanup_function called 0x%" PRIxPTR ".", (uintptr_t)arg); msg_free((rtsp_message **)arg); } static void *rtsp_conversation_thread_func(void *pconn) { // #include // debug(1, "rtsp_conversation_thread_func PID %d", syscall(SYS_gettid)); rtsp_conn_info *conn = pconn; int rc = pthread_mutex_init(&conn->flush_mutex, NULL); if (rc) die("Connection %d: error %d initialising flush_mutex.", conn->connection_number, rc); rc = pthread_mutex_init(&conn->ab_mutex, NULL); if (rc) die("Connection %d: error %d initialising ab_mutex.", conn->connection_number, rc); rc = pthread_cond_init(&conn->flowcontrol, NULL); if (rc) die("Connection %d: error %d initialising flow control condition variable.", conn->connection_number, rc); rc = pthread_mutex_init(&conn->volume_control_mutex, NULL); if (rc) die("Connection %d: error %d initialising volume_control_mutex.", conn->connection_number, rc); rc = pthread_mutex_init(&conn->player_create_delete_mutex, NULL); if (rc) die("Connection %d: error %d initialising player_create_delete_mutex.", conn->connection_number, rc); #ifdef CONFIG_AIRPLAY_2 rc = pthread_mutex_init(&conn->event_sender_mutex, NULL); if (rc) die("Connection %d: error %d initialising event_sender_mutex.", conn->connection_number, rc); #endif // nothing before this is cancellable pthread_cleanup_push(rtsp_conversation_thread_cleanup_function, (void *)conn); rtp_initialise(conn); char *hdr = NULL; enum rtsp_read_request_response reply; // int rtsp_read_request_attempt_count = 1; // 1 means exit immediately rtsp_message *req = NULL, *resp = NULL; #ifdef CONFIG_AIRPLAY_2 conn->ap2_audio_buffer_size = 1024 * 1024 * 8; #endif while (conn->stop == 0) { pthread_testcancel(); int debug_level = 4; // for printing the request and response // check to see if a conn has been zeroed pthread_mutex_lock(&conns_lock); int i; for (i = 0; i < nconns; i++) { if ((conns[i] != NULL) && (conns[i]->connection_number == 0)) { debug(1, "conns[%d] has a Connection Number of 0!", i); } } pthread_mutex_unlock(&conns_lock); reply = rtsp_read_request(conn, &req); if (reply == rtsp_read_request_response_ok) { pthread_cleanup_push(msg_cleanup_function, (void *)&req); resp = msg_init(); pthread_cleanup_push(msg_cleanup_function, (void *)&resp); resp->respcode = 501; // Not Implemented int dl = debug_level; // if ((strcmp(req->method, "OPTIONS") == 0) || // (strcmp(req->method, "POST") == // 0)) // the options message is very common, so don't log it until level 3 // dl = 3; debug(dl, "Connection %d: (%s) received an RTSP Packet of type \"%s\":", conn->connection_number, get_category_string(conn->airplay_stream_category), req->method); debug_log_rtsp_message(dl, NULL, req); apple_challenge(conn->fd, req, resp); hdr = msg_get_header(req, "CSeq"); if (hdr) msg_add_header(resp, "CSeq", hdr); // msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); #ifdef CONFIG_AIRPLAY_2 char server_string[128]; snprintf(server_string, sizeof(server_string), "AirTunes/%s", config.srcvers); msg_add_header(resp, "Server", server_string); #else msg_add_header(resp, "Server", "AirTunes/105.1"); #endif // we are only concerned with classic_airplay_authorized if we are running in classic AirPlay // mode either because the build is for classic AirPlay only or, if the build is for AirPlay // 2, we happen to be running in classic AirPlay compatibility mode if ((conn->authorized == 1) || #ifdef CONFIG_AIRPLAY_2 (conn->airplay_type != ap_1) || #endif (rtsp_classic_airplay_auth(&conn->auth_nonce, req, resp) == 0)) { conn->authorized = 1; // it must have been classic airplay and authorized or didn't need a // password, or not classic AirPlay at all struct method_handler *mh; int method_selected = 0; for (mh = method_handlers; mh->method; mh++) { if (!strcmp(mh->method, req->method)) { method_selected = 1; #ifdef CONFIG_AIRPLAY_2 if (conn->airplay_type == ap_1) mh->ap1_handler(conn, req, resp); else mh->ap2_handler(conn, req, resp); #else mh->handler(conn, req, resp); #endif break; } } if (method_selected == 0) { debug(1, "Connection %d: (%s) unrecognised and unhandled rtsp request \"%s\". HTTP Response " "Code " "%d returned.", conn->connection_number, get_category_string(conn->airplay_stream_category), req->method, resp->respcode); debug_log_rtsp_message(dl, NULL, req); int y = req->contentlength; if (y > 0) { char obf[4096]; if (y > 4096) y = 4096; char *p = req->content; char *obfp = obf; int obfc; for (obfc = 0; obfc < y; obfc++) { snprintf(obfp, 3, "%02X", (unsigned int)*p); p++; obfp += 2; }; *obfp = 0; debug(dl, "Content: \"%s\".", obf); } } resp->respcode = 200; // OK } debug(dl, "Connection %d: (%s) RTSP response:", conn->connection_number, get_category_string(conn->airplay_stream_category)); debug_log_rtsp_message(dl, NULL, resp); // if (conn->stop == 0) { int err = msg_write_response(conn, resp); if (err) { debug(1, "Connection %d: Unable to write an RTSP message response. Terminating the " "connection.", conn->connection_number); struct linger so_linger; so_linger.l_onoff = 1; // "true" so_linger.l_linger = 0; err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); if (err) debug(1, "Connection %d: Could not set the RTSP socket to abort due to a write error on " "closing.", conn->connection_number); conn->stop = 1; // if (debuglev >= 1) // debuglev = 3; // see what happens next } // } pthread_cleanup_pop(1); pthread_cleanup_pop(1); } else { // if the response is not rtsp_read_request_response_ok conn->stop = 1; if (reply == rtsp_read_request_response_read_error) { debug(1, "bad packet received."); struct linger so_linger; so_linger.l_onoff = 1; // "true" so_linger.l_linger = 0; int err = setsockopt(conn->fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger); if (err) debug(1, "Could not set the RTSP socket to abort due to a read error on closing."); } else if (reply == rtsp_read_request_response_bad_packet) { conn->stop = 0; // don't stop for a bad packet char *response_text = "RTSP/1.0 400 Bad Request\r\nServer: AirTunes/105.1\r\n\r\n"; ssize_t lreply = write(conn->fd, response_text, strlen(response_text)); if (lreply == -1) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "rtsp_read_request_response_bad_packet write response error %d: \"%s\".", errno, (char *)errorstring); } else if (lreply != (ssize_t)strlen(response_text)) { debug(1, "rtsp_read_request_response_bad_packet write %zd bytes requested but %d written.", strlen(response_text), reply); } } } } release_play_lock(conn); pthread_cleanup_pop(1); debug(2, "Connection %d: exit.", conn->connection_number); pthread_exit(NULL); } /* // this function is not thread safe. static const char *format_address(struct sockaddr *fsa) { static char string[INETx_ADDRSTRLEN]; void *addr; #ifdef AF_INET6 if (fsa->sa_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)(fsa); addr = &(sa6->sin6_addr); } else #endif { struct sockaddr_in *sa = (struct sockaddr_in *)(fsa); addr = &(sa->sin_addr); } return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); } */ void rtsp_listen_loop_cleanup_handler(__attribute__((unused)) void *arg) { int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); debug(2, "rtsp_listen_loop_cleanup_handler called."); if (conns) { cancel_all_RTSP_threads(unspecified_stream_category, 0); // kill all RTSP listeners free(conns); conns = NULL; } int *sockfd = (int *)arg; if (sockfd) { int i; for (i = 1; i <= sockfd[0]; i++) { safe_socket_close(&sockfd[i]); } free(sockfd); } pthread_setcancelstate(oldState, NULL); } void *rtsp_listen_loop(__attribute((unused)) void *arg) { // #include // debug(1, "rtsp_listen_loop PID %d", syscall(SYS_gettid)); int oldState; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); struct addrinfo hints, *info, *p; char portstr[6]; int *sockfd = NULL; int nsock = 0; int i, ret; principal_conn = NULL; // the data structure representing the connection that has the player. memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; snprintf(portstr, 6, "%d", config.port); // debug(1,"listen socket port request is \"%s\".",portstr); ret = getaddrinfo(NULL, portstr, &hints, &info); if (ret) { die("getaddrinfo failed: %s", gai_strerror(ret)); } for (p = info; p; p = p->ai_next) { ret = 0; int lfd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); int yes = 1; // Handle socket open failures if protocol unavailable (or IPV6 not handled) if (lfd != -1) { // Set the RTSP socket to close on exec() of child processes // otherwise background run_this_before_play_begins or run_this_after_play_ends commands // that are sleeping prevent the daemon from being restarted because // the listening RTSP port is still in use. // See: https://github.com/mikebrady/shairport-sync/issues/329 fcntl(lfd, F_SETFD, FD_CLOEXEC); ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. // since we need to account for two sockets we might as well // always. if (p->ai_family == AF_INET6) { ret |= setsockopt(lfd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); } #endif if (!ret) ret = bind(lfd, p->ai_addr, p->ai_addrlen); // one of the address families will fail on some systems that // report its availability. do not complain. if (ret) { char *family; #ifdef AF_INET6 if (p->ai_family == AF_INET6) { family = "IPv6"; } else #endif family = "IPv4"; debug(1, "unable to listen on %s port %d. The error is: \"%s\".", family, config.port, strerror(errno)); } else { listen(lfd, 255); nsock++; sockfd = realloc(sockfd, (nsock + 1) * sizeof(int)); sockfd[nsock] = lfd; sockfd[0] = nsock; // the first entry is the number of sockets in the array } } /* listen(lfd, 5); nsock++; sockfd = realloc(sockfd, nsock * sizeof(int)); sockfd[nsock - 1] = lfd; */ } freeaddrinfo(info); if (nsock) { int maxfd = -1; fd_set fds; FD_ZERO(&fds); // skip the first element in sockfd -- it's the count for (i = 1; i <= nsock; i++) { if (sockfd[i] > maxfd) maxfd = sockfd[i]; } char **t1 = txt_records; // ap1 text records char **t2 = NULL; // possibly two text records #ifdef CONFIG_AIRPLAY_2 if (config.service_type == APST_airplay2) { // make up a secondary set of text records t2 = secondary_txt_records; // second set of text records in AirPlay 2 only } #endif build_bonjour_strings(NULL); // no conn yet // if a thread is created, e.g. Avahi, it'll inherit the name from this thread mdns_register(t1, t2); // note that the dacp thread could still be using the mdns stuff after // all player threads have been terminated, so mdns_unregister can't be // in the rtsp_listen_loop cleanup. pthread_setcancelstate(oldState, NULL); int acceptfd; struct timeval tv; pthread_cleanup_push(rtsp_listen_loop_cleanup_handler, (void *)sockfd); do { pthread_testcancel(); tv.tv_sec = 60; tv.tv_usec = 0; // skip the first element in sockfd -- it's the count for (i = 1; i <= nsock; i++) FD_SET(sockfd[i], &fds); ret = select(maxfd + 1, &fds, 0, 0, &tv); if (ret < 0) { if (errno == EINTR) continue; break; } cleanup_threads(); acceptfd = -1; // skip the first element in sockfd -- it's the count for (i = 1; i <= nsock; i++) { if (FD_ISSET(sockfd[i], &fds)) { acceptfd = sockfd[i]; break; } } if (acceptfd < 0) // timeout continue; int release_conn = 1; // on exit, deallocate the buffer unless everything was okay rtsp_conn_info *conn = malloc(sizeof(rtsp_conn_info)); if (conn == 0) die("Couldn't allocate memory for an rtsp_conn_info record."); pthread_cleanup_push(malloc_cleanup, &conn); memset(conn, 0, sizeof(rtsp_conn_info)); conn->connection_number = RTSP_connection_index++; debug(2, "Connection %d is at: 0x%" PRIxPTR ".", conn->connection_number, (uintptr_t)conn); // this means that the OPTIONS string we send before getting an ANNOUNCE is for AirPlay 2 #ifdef CONFIG_AIRPLAY_2 conn->airplay_type = ap_2; // changed if an ANNOUNCE is received conn->timing_type = ts_ptp; // changed if an ANNOUNCE is received #else conn->airplay_stream_category = classic_airplay_stream; // really just used for debug messages in Classic AirPlay builds #endif socklen_t size_of_reply = sizeof(SOCKADDR); conn->fd = eintr_checked_accept(acceptfd, (struct sockaddr *)&conn->remote, &size_of_reply); if (conn->fd < 0) { debug(1, "Connection %d: New connection on port %d not accepted:", conn->connection_number, config.port); perror("failed to accept connection"); #ifndef CONFIG_AIRPLAY_2 // in Classic AirPlay, close the connection unless idle or interruptions allowed... } else if ((principal_conn != NULL) && (config.allow_session_interruption == 0)) { debug(1, "Connection %d: %s session interruption not allowed", conn->connection_number, get_category_string(conn->airplay_stream_category)); safe_socket_close(&conn->fd); #endif } else { size_of_reply = sizeof(SOCKADDR); if (getsockname(conn->fd, (struct sockaddr *)&conn->local, &size_of_reply) == 0) { if ((config.dont_check_timeout == 0) && (config.timeout >= 60)) { /* // shouldn't need this! struct timeval tv; tv.tv_sec = config.timeout; // seconds tv.tv_usec = 0; // microseconds if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) != 0) { char errorstring[1024]; strerror_r(errno, (char *)errorstring, sizeof(errorstring)); debug(1, "could not set time limit on read_from_rtsp_connection -- error %d \"%s\".", errno, errorstring); } */ // skip this stuff in OpenBSD #ifndef COMPILE_FOR_OPENBSD // Thanks to https://holmeshe.me/network-essentials-setsockopt-SO_KEEPALIVE/ for this. // turn on keepalive stuff -- wait for keepidle + (keepcnt * keepinttvl time) seconds // before giving up an ETIMEOUT error is returned if the keepalive check fails // if TCP_KEEPINTVL is defined, check a few times before declaring the line dead // otherwise just wait a little while longer #ifdef TCP_KEEPINTVL int keepAliveIdleTime = config.timeout - 5 * 5; // wait this many seconds before checking for a dropped client // a minute seems a bit short... int keepAliveCount = 5; // check this many times int keepAliveInterval = 5; // wait this many seconds between checks #else int keepAliveIdleTime = config.timeout; // wait this many seconds before dropping a client #endif // --- the following is a bit too complicated // decide to use IPPROTO_TCP or SOL_TCP #if defined COMPILE_FOR_BSD || defined COMPILE_FOR_OSX #define SOL_OPTION IPPROTO_TCP #else #define SOL_OPTION SOL_TCP #endif // decide to use TCP_KEEPALIVE or TCP_KEEPIDLE #ifdef COMPILE_FOR_OSX #define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPALIVE #else #define KEEP_ALIVE_OR_IDLE_OPTION TCP_KEEPIDLE #endif debug(3, "Connection %d: set the keepAliveIdleTime to %d seconds.", conn->connection_number, keepAliveIdleTime); if (setsockopt(conn->fd, SOL_OPTION, KEEP_ALIVE_OR_IDLE_OPTION, (void *)&keepAliveIdleTime, sizeof(keepAliveIdleTime))) { debug(1, "can't set the keepAliveIdleTime wait time"); } // --- // if TCP_KEEPINTVL is defined... #ifdef TCP_KEEPINTVL debug(3, "Connection %d: set the keepAliveCount to %d.", conn->connection_number, keepAliveCount); if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPCNT, (void *)&keepAliveCount, sizeof(keepAliveCount))) { debug(1, "can't set the keepAliveCount count"); } debug(3, "Connection %d: set the keepAliveCount interval to %d seconds.", conn->connection_number, keepAliveInterval); if (setsockopt(conn->fd, SOL_OPTION, TCP_KEEPINTVL, (void *)&keepAliveInterval, sizeof(keepAliveInterval))) { debug(1, "can't set the keepAliveCount count interval"); }; #endif debug(3, "Connection %d: enable SO_KEEPALIVE.", conn->connection_number); int flags = 1; if (setsockopt(conn->fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags))) { debug(1, "can't set SO_KEEPALIVE."); } #endif } // initialise the connection info void *client_addr = NULL, *self_addr = NULL; conn->connection_ip_family = conn->local.SAFAMILY; #ifdef AF_INET6 if (conn->connection_ip_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&conn->remote; client_addr = &(sa6->sin6_addr); conn->client_rtsp_port = ntohs(sa6->sin6_port); sa6 = (struct sockaddr_in6 *)&conn->local; self_addr = &(sa6->sin6_addr); conn->self_rtsp_port = ntohs(sa6->sin6_port); conn->self_scope_id = sa6->sin6_scope_id; } #endif if (conn->connection_ip_family == AF_INET) { struct sockaddr_in *sa4 = (struct sockaddr_in *)&conn->remote; client_addr = &(sa4->sin_addr); conn->client_rtsp_port = ntohs(sa4->sin_port); sa4 = (struct sockaddr_in *)&conn->local; self_addr = &(sa4->sin_addr); conn->self_rtsp_port = ntohs(sa4->sin_port); } inet_ntop(conn->connection_ip_family, client_addr, conn->client_ip_string, sizeof(conn->client_ip_string)); inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string, sizeof(conn->self_ip_string)); debug(2, "Connection %d: New connection from %s:%u to self at %s:%u.", conn->connection_number, conn->client_ip_string, conn->client_rtsp_port, conn->self_ip_string, conn->self_rtsp_port); conn->connection_start_time = get_absolute_time_in_ns(); } else { debug(1, "Error figuring out Shairport Sync's own IP number."); } ret = named_pthread_create(&conn->thread, NULL, rtsp_conversation_thread_func, conn, "rtsp_conn_%d", conn->connection_number); // also acts as a memory barrier if (ret) { char errorstring[1024]; strerror_r(ret, (char *)errorstring, sizeof(errorstring)); die("Connection %d: cannot create an RTSP conversation thread. Error %d: \"%s\".", conn->connection_number, ret, (char *)errorstring); } #ifndef CONFIG_AIRPLAY_2 // in Classic AirPlay, since we know (by getting here) that interruptions are allowed, grab // the principal conn if (get_play_lock(conn, config.allow_session_interruption) == play_lock_acquired_without_breaking_in) { // now, if this new session did not break in, then it's okay to reset the next UDP ports // to the start of the range resetFreeUDPPort(); } #endif debug(3, "Successfully created RTSP receiver thread %d.", conn->connection_number); conn->running = 1; // this must happen before the thread is tracked track_thread(conn); release_conn = 0; // successfully initialised } pthread_cleanup_pop(release_conn); // release the conn malloc if any kind of error } while (1); pthread_cleanup_pop(1); // should never happen } else { die("could not establish a service on port %d -- program terminating. Is another instance of " "Shairport Sync running on this device?", config.port); } debug(1, "Fell out of the RTSP select loop -- this should never happen!"); pthread_exit(NULL); }