/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "airplay_events.h" #include "commands.h" #include "misc.h" #include "logger.h" #include "player.h" #include "pair_ap/pair.h" #define RTSP_VERSION "RTSP/1.0" enum airplay_events { AIRPLAY_EVENT_UNKNOWN, AIRPLAY_EVENT_PLAY, AIRPLAY_EVENT_PAUSE, AIRPLAY_EVENT_NEXT, AIRPLAY_EVENT_PREV, }; struct airplay_events_client { char *name; int fd; struct event *listener; struct pair_cipher_context *cipher_ctx; struct evbuffer *incoming; struct evbuffer *pending; struct airplay_events_client *next; }; struct rtsp_message { int content_length; char *content_type; char *first_line; int cseq; const uint8_t *body; size_t bodylen; const uint8_t *data; size_t datalen; }; static pthread_t thread_id; static struct event_base *evbase; static struct commands_base *cmdbase; static struct airplay_events_client *airplay_events_clients; // Forwards static void incoming_cb(int fd, short what, void *arg); /* ---------------------------- Client handling ----------------------------- */ static void client_free(struct airplay_events_client *client) { if (!client) return; if (client->listener) event_free(client->listener); evbuffer_free(client->incoming); evbuffer_free(client->pending); free(client->name); pair_cipher_free(client->cipher_ctx); free(client); } static void client_remove(struct airplay_events_client *client) { struct airplay_events_client *iter; if (client == airplay_events_clients) airplay_events_clients = client->next; else { for (iter = airplay_events_clients; iter && (iter->next != client); iter = iter->next) ; /* EMPTY */ if (iter) iter->next = client->next; } client_free(client); } static int client_add(const char *name, int fd, const uint8_t *key, size_t key_len) { struct airplay_events_client *client; CHECK_NULL(L_AIRPLAY, client = calloc(1, sizeof(struct airplay_events_client))); CHECK_NULL(L_AIRPLAY, client->name = strdup(name)); CHECK_NULL(L_AIRPLAY, client->incoming = evbuffer_new()); CHECK_NULL(L_AIRPLAY, client->pending = evbuffer_new()); client->fd = fd; client->listener = event_new(evbase, fd, EV_READ | EV_PERSIST, incoming_cb, client); if (!client->listener) { DPRINTF(E_LOG, L_AIRPLAY, "Could not listen for AirPlay events from '%s', invalid fd or out of memory\n", name); goto error; } client->cipher_ctx = pair_cipher_new(PAIR_CLIENT_HOMEKIT_NORMAL, 1, key, key_len); if (!client->cipher_ctx) { DPRINTF(E_LOG, L_AIRPLAY, "Could not listen for AirPlay events from '%s': Could not create ciphering context\n", name); goto error; } event_add(client->listener, NULL); client->next = airplay_events_clients; airplay_events_clients = client; return 0; error: client_free(client); return -1; } /* -------------------------------- Ciphering ------------------------------- */ static int buffer_decrypt(struct evbuffer *output, struct evbuffer *input, struct pair_cipher_context *cipher_ctx) { uint8_t *in; size_t in_len; ssize_t bytes_decrypted; uint8_t *plain; size_t plain_len; in = evbuffer_pullup(input, -1); in_len = evbuffer_get_length(input); // Note that bytes_decrypted is not necessarily equal to plain_len bytes_decrypted = pair_decrypt(&plain, &plain_len, in, in_len, cipher_ctx); if (bytes_decrypted < 0) return -1; evbuffer_add(output, plain, plain_len); evbuffer_drain(input, bytes_decrypted); free(plain); return 0; } static int buffer_encrypt(struct evbuffer *output, uint8_t *in, size_t in_len, struct pair_cipher_context *cipher_ctx) { uint8_t *out; size_t out_len; int ret; ret = pair_encrypt(&out, &out_len, in, in_len, cipher_ctx); if (ret < 0) return -1; evbuffer_add(output, out, out_len); free(out); return 0; } /* --------------------- Message construction/parsing ----------------------- */ static void response_headers_add(struct evbuffer *response, int cseq, size_t content_length, const char *content_type) { evbuffer_add_printf(response, "%s 200 OK\r\n", RTSP_VERSION); evbuffer_add_printf(response, "Server: %s/1.0\r\n", PACKAGE_NAME); if (content_length) evbuffer_add_printf(response, "Content-Length: %zu\r\n", content_length); if (content_type) evbuffer_add_printf(response, "Content-Type: %s\r\n", content_type); if (cseq) evbuffer_add_printf(response, "CSeq: %d\r\n", cseq); evbuffer_add_printf(response, "\r\n"); } static void response_create_from_raw(struct evbuffer *response, uint8_t *body, size_t body_len, int cseq, const char *content_type) { response_headers_add(response, cseq, body_len, content_type); if (body) evbuffer_add(response, body, body_len); } static int body_find(uint8_t **body, size_t *body_len, uint8_t *in, size_t in_len) { const char *plist_header = "bplist"; size_t plist_header_len = strlen(plist_header); *body_len = in_len; for (*body = in; *body_len > plist_header_len; (*body)++, (*body_len)--) { if (memcmp(*body, plist_header, plist_header_len) == 0) return 0; } return -1; } static int rtsp_parse(enum airplay_events *event, uint8_t *in, size_t in_len) { uint8_t *body; size_t body_len; plist_t request = NULL; plist_t item; char *type = NULL; char *value = NULL; int ret; DHEXDUMP(E_DBG, L_AIRPLAY, in, in_len, "Incoming event\n"); ret = body_find(&body, &body_len, in, in_len); if (ret < 0) { DPRINTF(E_WARN, L_AIRPLAY, "Could not parse incoming event, no plist body found\n"); return -1; } plist_from_bin((char *)body, (uint32_t)body_len, &request); if (!request) { DPRINTF(E_WARN, L_AIRPLAY, "Could not parse incoming event plist\n"); return -1; } // TODO remove char *xml = NULL; uint32_t xml_len; plist_to_xml(request, &xml, &xml_len); DPRINTF(E_DBG, L_AIRPLAY, "%s\n", xml); item = plist_dict_get_item(request, "type"); if (item) { plist_get_string_val(item, &type); } item = plist_dict_get_item(request, "value"); if (item) { plist_get_string_val(item, &value); } if (!type || !value) { DPRINTF(E_DBG, L_AIRPLAY, "AirPlay event has no type/value: type=%s, value=%s\n", type, value); goto error; } else if (strcmp(type, "sendMediaRemoteCommand") != 0) { DPRINTF(E_DBG, L_AIRPLAY, "Incoming event not of type sendMediaRemoteCommand\n"); goto error; } DPRINTF(E_INFO, L_AIRPLAY, "Received event type '%s', value '%s'\n", type, value); if (strcmp(value, "paus") == 0) *event = AIRPLAY_EVENT_PAUSE; else if (strcmp(value, "play") == 0) *event = AIRPLAY_EVENT_PLAY; else if (strcmp(value, "nitm") == 0) *event = AIRPLAY_EVENT_NEXT; else if (strcmp(value, "pitm") == 0) *event = AIRPLAY_EVENT_PREV; else *event = AIRPLAY_EVENT_UNKNOWN; free(type); free(value); plist_free(request); return 0; error: free(type); free(value); plist_free(request); return -1; } /* --------------------------- Message handling ----------------------------- */ static void handle_event(enum airplay_events event) { struct player_status status; player_get_status(&status); switch (event) { case AIRPLAY_EVENT_PLAY: case AIRPLAY_EVENT_PAUSE: if (status.status == PLAY_PLAYING) player_playback_pause(); else player_playback_start(); break; case AIRPLAY_EVENT_NEXT: player_playback_next(); break; case AIRPLAY_EVENT_PREV: player_playback_prev(); break; default: return; } } static int respond(struct airplay_events_client *client) { struct evbuffer *response; struct evbuffer *encrypted; uint8_t *plain; size_t plain_len; int ret; CHECK_NULL(L_AIRPLAY, response = evbuffer_new()); response_create_from_raw(response, NULL, 0, 0, NULL); plain = evbuffer_pullup(response, -1); plain_len = evbuffer_get_length(response); CHECK_NULL(L_AIRPLAY, encrypted = evbuffer_new()); ret = buffer_encrypt(encrypted, plain, plain_len, client->cipher_ctx); if (ret < 0) { DPRINTF(E_WARN, L_AIRPLAY, "Could not encrypt AirPlay event data response: %s\n", pair_cipher_errmsg(client->cipher_ctx)); return -1; } do { ret = evbuffer_write(encrypted, client->fd); if (ret <= 0) goto error; } while (evbuffer_get_length(encrypted) > 0); evbuffer_free(encrypted); evbuffer_free(response); return 0; error: evbuffer_free(encrypted); evbuffer_free(response); return -1; } static void incoming_cb(int fd, short what, void *arg) { struct airplay_events_client *client = arg; enum airplay_events event; uint8_t *plain; size_t plain_len; int ret; DPRINTF(E_DBG, L_AIRPLAY, "AirPlay event from '%s'\n", client->name); ret = evbuffer_read(client->incoming, fd, -1); if (ret == 0) { DPRINTF(E_DBG, L_AIRPLAY, "'%s' disconnected from the event channel\n", client->name); goto disconnect; } else if (ret < 0) { DPRINTF(E_WARN, L_AIRPLAY, "AirPlay event connection to '%s' returned an error\n", client->name); goto disconnect; } ret = buffer_decrypt(client->pending, client->incoming, client->cipher_ctx); if (ret < 0) { DPRINTF(E_WARN, L_AIRPLAY, "Could not decrypt incoming AirPlay event data: %s\n", pair_cipher_errmsg(client->cipher_ctx)); goto disconnect; } plain = evbuffer_pullup(client->pending, -1); plain_len = evbuffer_get_length(client->pending); ret = rtsp_parse(&event, plain, plain_len); if (ret < 0) // A message type we don't know about, so ignore { evbuffer_drain(client->pending, -1); return; } else if (ret == 1) { DPRINTF(E_SPAM, L_AIRPLAY, "Incomplete RTSP event message, waiting for more data\n"); return; } evbuffer_drain(client->pending, -1); handle_event(event); ret = respond(client); if (ret < 0) { DPRINTF(E_WARN, L_AIRPLAY, "Could not send AirPlay event response\n"); goto disconnect; } return; disconnect: client_remove(client); return; } /* -------------------- Event loop (thread: airplay events ) ---------------- */ static void * airplay_events(void *arg) { event_base_dispatch(evbase); pthread_exit(NULL); } /* ------------------------------- Interface -------------------------------- */ int airplay_events_listen(const char *name, const char *address, unsigned short port, const uint8_t *key, size_t key_len) { int fd; int ret; fd = net_connect(address, port, SOCK_STREAM, "AirPlay events"); if (fd < 0) { return -1; } ret = client_add(name, fd, key, key_len); if (ret < 0) { close(fd); return -1; } return fd; } /* Thread: main */ int airplay_events_init(void) { int ret; CHECK_NULL(L_AIRPLAY, evbase = event_base_new()); CHECK_NULL(L_AIRPLAY, cmdbase = commands_base_new(evbase, NULL)); DPRINTF(E_INFO, L_AIRPLAY, "AirPlay events thread init\n"); ret = pthread_create(&thread_id, NULL, airplay_events, NULL); if (ret < 0) { DPRINTF(E_LOG, L_AIRPLAY, "Could not spawn AirPlay events thread: %s\n", strerror(errno)); goto error; } // TODO thread_name_set(thread_id, "airplay events"); return 0; error: airplay_events_deinit(); return -1; } /* Thread: main */ void airplay_events_deinit(void) { int ret; commands_base_destroy(cmdbase); ret = pthread_join(thread_id, NULL); if (ret != 0) { DPRINTF(E_LOG, L_AIRPLAY, "Could not join AirPlay events thread: %s\n", strerror(errno)); return; } while (airplay_events_clients) { client_remove(airplay_events_clients); } event_base_free(evbase); }