mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-01 01:53:23 -05:00
f4983b8a11
Closes #1294
553 lines
12 KiB
C
553 lines
12 KiB
C
/*
|
|
* 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 <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <pthread.h>
|
|
#include <event2/event.h>
|
|
#include <event2/buffer.h>
|
|
#include <plist/plist.h>
|
|
|
|
#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);
|
|
}
|