shairport-sync/pair_ap/server-example.c

701 lines
18 KiB
C

/*
*
* The MIT License (MIT)
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#include "pair.h"
#ifdef CONFIG_GCRYPT
# include <gcrypt.h>
#endif
#define DEVICE_ID "FFEEDDCCBBAA9988"
#define LISTEN_PORT 7000
#define CONTENT_TYPE_OCTET "application/octet-stream"
#define RTSP_VERSION "RTSP/1.0"
#define OPTIONS "OPTIONS *"
struct connection_ctx
{
struct evbuffer *pending;
struct pair_setup_context *setup_ctx;
struct pair_verify_context *verify_ctx;
struct pair_cipher_context *cipher_ctx;
int pair_completed;
};
struct rtsp_msg
{
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;
};
struct pairings
{
char device_id[PAIR_AP_DEVICE_ID_LEN_MAX];
uint8_t public_key[32];
struct pairings *next;
} *pairings;
static void
connection_free(struct connection_ctx *conn_ctx)
{
if (!conn_ctx)
return;
evbuffer_free(conn_ctx->pending);
pair_setup_free(conn_ctx->setup_ctx);
pair_cipher_free(conn_ctx->cipher_ctx);
free(conn_ctx);
}
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: MyServer/1.0\r\n");
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);
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
encryption_enable(struct connection_ctx *conn_ctx, const uint8_t *shared_secret, size_t shared_secret_len)
{
conn_ctx->cipher_ctx = pair_cipher_new(PAIR_SERVER_HOMEKIT, 2, shared_secret, shared_secret_len);
if (!conn_ctx->cipher_ctx)
{
printf("Error setting up ciphering\n");
return -1;
}
return 0;
}
static int
buffer_encrypt(struct evbuffer *output, uint8_t *in, size_t in_len, struct connection_ctx *conn_ctx)
{
uint8_t *out;
size_t out_len;
int ret;
ret = pair_encrypt(&out, &out_len, in, in_len, conn_ctx->cipher_ctx);
if (ret < 0)
{
printf("Error encrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx));
return -1;
}
evbuffer_add(output, out, out_len);
free(out);
return 0;
}
static int
buffer_decrypt(struct evbuffer *output, struct evbuffer *input, struct connection_ctx *conn_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, conn_ctx->cipher_ctx);
if (bytes_decrypted < 0)
{
printf("Error decrypting: %s\n", pair_cipher_errmsg(conn_ctx->cipher_ctx));
return -1;
}
evbuffer_add(output, plain, plain_len);
evbuffer_drain(input, bytes_decrypted);
free(plain);
return 0;
}
/* ---------------------------- Pairing callbacks --------------------------- */
/* Note that none of these callbacks are required if you don't care about */
/* securely verifying the client + don't require support for the pair-add, */
/* pair-remove and pair-list methods. */
static struct pairings *
pairing_find(const char *device_id)
{
struct pairings *pairing;
for (pairing = pairings; pairing; pairing = pairing->next)
{
if (strcmp(device_id, pairing->device_id) == 0)
break;
}
return pairing;
}
static int
pairing_add_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
{
struct pairings *pairing;
printf("Adding paired device %s\n", device_id);
pairing = pairing_find(device_id);
if (pairing)
{
memcpy(pairing->public_key, public_key, sizeof(pairing->public_key));
return 0;
}
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;
return 0;
}
static int
pairing_remove_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
{
struct pairings *pairing;
struct pairings *iter;
printf("Removing paired device %s\n", device_id);
pairing = pairing_find(device_id);
if (!pairing)
{
printf("Remove callback for unknown device\n");
return -1;
}
if (pairing == pairings)
pairings = pairing->next;
else
{
for (iter = pairings; iter && (iter->next != pairing); iter = iter->next)
; /* EMPTY */
if (iter)
iter->next = pairing->next;
}
free(pairing);
return 0;
}
static void
pairing_list_cb(pair_cb enum_cb, void *enum_cb_arg, void *cb_arg)
{
struct pairings *pairing;
printf("Listing paired devices\n");
for (pairing = pairings; pairing; pairing = pairing->next)
{
enum_cb(pairing->public_key, pairing->device_id, enum_cb_arg);
}
}
static int
pairing_get_cb(uint8_t public_key[32], const char *device_id, void *cb_arg)
{
struct pairings *pairing;
printf("Returning public key for paired device %s\n", device_id);
pairing = pairing_find(device_id);
if (!pairing)
return -1;
memcpy(public_key, pairing->public_key, sizeof(pairing->public_key));
return 0;
}
/* -------------------------- Pair request handlers ------------------------- */
static int
handle_pin_start(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
printf("Please pair with code 3939\n");
response_create_from_raw(output, NULL, 0, msg->cseq, NULL);
return 0;
}
static int
handle_pair_setup(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
uint8_t *out;
size_t out_len;
struct pair_result *result;
int ret;
if (!conn_ctx->setup_ctx)
{
conn_ctx->setup_ctx = pair_setup_new(PAIR_SERVER_HOMEKIT, NULL, pairing_add_cb, NULL, DEVICE_ID);
if (!conn_ctx->setup_ctx)
{
printf("Error creating setup context\n");
return -1;
}
}
ret = pair_setup(&out, &out_len, conn_ctx->setup_ctx, msg->body, msg->bodylen);
if (ret < 0)
{
printf("Pair setup error: %s\n", pair_setup_errmsg(conn_ctx->setup_ctx));
return -1;
}
ret = pair_setup_result(NULL, &result, conn_ctx->setup_ctx);
if (ret == 0 && result->shared_secret_len > 0) // Transient pairing completed (step 2)
{
encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len);
conn_ctx->pair_completed = 1;
}
response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
free(out);
return 0;
}
static int
handle_pair_verify(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
uint8_t *out;
size_t out_len;
struct pair_result *result;
int ret;
if (!conn_ctx->verify_ctx)
{
conn_ctx->verify_ctx = pair_verify_new(PAIR_SERVER_HOMEKIT, NULL, pairing_get_cb, NULL, DEVICE_ID);
if (!conn_ctx->verify_ctx)
{
printf("Error creating verify context\n");
return -1;
}
}
ret = pair_verify(&out, &out_len, conn_ctx->verify_ctx, msg->body, msg->bodylen);
if (ret < 0)
{
printf("Pair verify error: %s\n", pair_verify_errmsg(conn_ctx->verify_ctx));
return -1;
}
ret = pair_verify_result(&result, conn_ctx->verify_ctx);
if (ret == 0)
{
encryption_enable(conn_ctx, result->shared_secret, result->shared_secret_len);
conn_ctx->pair_completed = 1;
}
response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
free(out);
return 0;
}
static int
handle_pair_add(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
uint8_t *out;
size_t out_len;
int ret;
ret = pair_add(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_add_cb, NULL, msg->body, msg->bodylen);
if (ret < 0)
{
printf("Error adding device to list\n");
return -1;
}
response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
free(out);
return 0;
}
static int
handle_pair_remove(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
uint8_t *out;
size_t out_len;
int ret;
ret = pair_remove(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_remove_cb, NULL, msg->body, msg->bodylen);
if (ret < 0)
{
printf("Error removing device from list\n");
return -1;
}
response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
free(out);
return 0;
}
static int
handle_pair_list(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
uint8_t *out;
size_t out_len;
int ret;
ret = pair_list(PAIR_SERVER_HOMEKIT, &out, &out_len, pairing_list_cb, NULL, msg->body, msg->bodylen);
if (ret < 0)
{
printf("Error creating list of paired devices\n");
return -1;
}
response_create_from_raw(output, out, out_len, msg->cseq, CONTENT_TYPE_OCTET);
free(out);
return 0;
}
static int
handle_options(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
struct evbuffer *response;
uint8_t *plain;
size_t plain_len;
int ret;
response = evbuffer_new();
response_create_from_raw(response, NULL, 0, msg->cseq, NULL);
if (!conn_ctx->cipher_ctx)
{
evbuffer_add_buffer(output, response);
evbuffer_free(response);
return 0;
}
plain = evbuffer_pullup(response, -1);
plain_len = evbuffer_get_length(response);
ret = buffer_encrypt(output, plain, plain_len, conn_ctx);
evbuffer_free(response);
return ret;
}
static int
response_send(struct evbuffer *output, struct connection_ctx *conn_ctx, struct rtsp_msg *msg)
{
if (!msg->first_line)
return -1;
if (strncmp(msg->first_line, PAIR_AP_POST_PIN_START, strlen(PAIR_AP_POST_PIN_START)) == 0)
return handle_pin_start(output, conn_ctx, msg);
else if (strncmp(msg->first_line, PAIR_AP_POST_SETUP, strlen(PAIR_AP_POST_SETUP)) == 0)
return handle_pair_setup(output, conn_ctx, msg);
else if (strncmp(msg->first_line, PAIR_AP_POST_VERIFY, strlen(PAIR_AP_POST_VERIFY)) == 0)
return handle_pair_verify(output, conn_ctx, msg);
else if (strncmp(msg->first_line, PAIR_AP_POST_ADD, strlen(PAIR_AP_POST_ADD)) == 0)
return handle_pair_add(output, conn_ctx, msg);
else if (strncmp(msg->first_line, PAIR_AP_POST_LIST, strlen(PAIR_AP_POST_LIST)) == 0)
return handle_pair_list(output, conn_ctx, msg);
else if (strncmp(msg->first_line, PAIR_AP_POST_REMOVE, strlen(PAIR_AP_POST_REMOVE)) == 0)
return handle_pair_remove(output, conn_ctx, msg);
else if (strncmp(msg->first_line, OPTIONS, strlen(OPTIONS)) == 0)
return handle_options(output, conn_ctx, msg);
printf("Unknown method: %s\n", msg->first_line);
return -1;
}
/* --------------------- A basic RTSP server implementation ----------------- */
static void
rtsp_clear(struct rtsp_msg *msg)
{
free(msg->first_line);
free(msg->content_type);
}
// Very primitive RTSP message parser, hope you have a better one
static int
rtsp_parse(struct rtsp_msg *msg, uint8_t *in, size_t in_len)
{
char *line;
int i;
line = (char *)in;
for (i = 0; i < in_len; i++)
{
if (in[i] != '\n' && in[i - 1] != '\r')
continue;
if (in[i - 2] == '\n' && in[i - 3] == '\r')
{
msg->bodylen = in_len - (i + 1);
if (msg->bodylen != msg->content_length)
{
printf("Incomplete read (have %zu, content-length %d), waiting for more data\n\n", msg->bodylen, msg->content_length);
rtsp_clear(msg);
return 1;
}
else if (msg->bodylen > 0)
msg->body = in + i + 1;
break;
}
in[i - 1] = '\0';
if (!msg->first_line)
msg->first_line = strdup(line);
if (strncmp(line, "CSeq: ", strlen("CSeq: ")) == 0)
msg->cseq = atoi(line + strlen("CSeq: "));
if (strncmp(line, "Content-Length: ", strlen("Content-Length: ")) == 0)
msg->content_length = atoi(line + strlen("Content-Length: "));
if (strncmp(line, "Content-Type: ", strlen("Content-Type: ")) == 0 && !msg->content_type)
msg->content_type = strdup(line + strlen("Content-Type: "));
in[i - 1] = '\r';
line = (char *)in + i + 1;
}
msg->data = in;
msg->datalen = in_len;
return 0;
}
static void
in_read_cb(struct bufferevent *bev, void *arg)
{
struct connection_ctx *conn_ctx = arg;
struct evbuffer *input;
struct evbuffer *output;
uint8_t *plain;
size_t plain_len;
struct rtsp_msg msg = { 0 };
int ret;
input = bufferevent_get_input(bev);
output = bufferevent_get_output(bev);
printf("\n--------------------------------------------------------------------------\n");
if (conn_ctx->pair_completed)
{
buffer_decrypt(conn_ctx->pending, input, conn_ctx);
}
else
{
evbuffer_add_buffer(conn_ctx->pending, input);
}
// Pending holds all the message we have received so far, incl. what parts we
// might have received in previous callbacks
plain = evbuffer_pullup(conn_ctx->pending, -1);
plain_len = evbuffer_get_length(conn_ctx->pending);
ret = rtsp_parse(&msg, plain, plain_len);
if (ret < 0)
{
printf("Could not parse RTSP message\n");
goto error;
}
else if (ret == 1)
return; // Message incomplete, wait for more data
ret = response_send(output, conn_ctx, &msg);
if (ret < 0)
{
goto error;
}
error:
rtsp_clear(&msg);
evbuffer_drain(conn_ctx->pending, evbuffer_get_length(conn_ctx->pending));
return;
}
static void
in_event_cb(struct bufferevent *bev, short events, void *arg)
{
struct connection_ctx *conn_ctx = arg;
if (events & BEV_EVENT_ERROR)
printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
bufferevent_free(bev);
connection_free(conn_ctx);
}
/*------------------------- General server stuff ----------------------------*/
static void
in_accept_cb(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *address, int socklen, void *ctx)
{
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);
struct connection_ctx *conn_ctx;
conn_ctx = calloc(1, sizeof(struct connection_ctx));
conn_ctx->pending = evbuffer_new();
bufferevent_setcb(bev, in_read_cb, NULL, in_event_cb, conn_ctx);
bufferevent_enable(bev, EV_READ | EV_WRITE);
printf("New connection accepted\n");
}
static void
in_error_cb(struct evconnlistener *listener, void *ctx)
{
int err = EVUTIL_SOCKET_ERROR();
printf("Error occured %d (%s) on the listener\n", err, evutil_socket_error_to_string(err));
}
static struct evconnlistener *
listen_add(struct event_base *evbase, evconnlistener_cb req_cb, evconnlistener_errorcb err_cb, unsigned short port)
{
struct evconnlistener *listener;
struct addrinfo hints = { 0 };
struct addrinfo *servinfo;
char strport[8];
int ret;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
snprintf(strport, sizeof(strport), "%hu", port);
ret = getaddrinfo(NULL, strport, &hints, &servinfo);
if (ret < 0)
{
printf("getaddrinf() failed: %s\n", gai_strerror(ret));
return NULL;
}
listener = evconnlistener_new_bind(evbase, req_cb, NULL, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, servinfo->ai_addr, servinfo->ai_addrlen);
freeaddrinfo(servinfo);
if (!listener)
{
printf("Could not create listener for port %hu\n", port);
return NULL;
}
evconnlistener_set_error_cb(listener, err_cb);
return listener;
}
int
main(int argc, char * argv[])
{
struct event_base *evbase;
struct evconnlistener *listener;
// libgcrypt requires that the application initializes the library
#ifdef CONFIG_GCRYPT
if (!gcry_check_version(NULL))
{
printf("libgcrypt not initialized\n");
return -1;
}
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
#endif
evbase = event_base_new();
listener = listen_add(evbase, in_accept_cb, in_error_cb, LISTEN_PORT);
if (!listener)
return -1;
printf("Listening for pairing requests on port %d\n", LISTEN_PORT);
event_base_dispatch(evbase);
evconnlistener_free(listener);
event_base_free(evbase);
return 0;
}