From b2b91998d97d74a75d8ce245ca158c0573fc9254 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Thu, 14 Jan 2021 21:36:47 +0100 Subject: [PATCH] [airplay] Fix decryption of large responses If the response is large evrtsp will not read the entire message from the socket. This change allows for decryption and parsing of partial messages. --- src/evrtsp/evrtsp.h | 10 ++- src/evrtsp/rtsp-internal.h | 9 +- src/evrtsp/rtsp.c | 170 +++++++++++++++++++------------------ src/outputs/airplay.c | 29 +++---- 4 files changed, 115 insertions(+), 103 deletions(-) diff --git a/src/evrtsp/evrtsp.h b/src/evrtsp/evrtsp.h index f8253aa6..2d5d0917 100644 --- a/src/evrtsp/evrtsp.h +++ b/src/evrtsp/evrtsp.h @@ -136,9 +136,15 @@ void evrtsp_connection_free(struct evrtsp_connection *evcon); void evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, void (*)(struct evrtsp_connection *, void *), void *); -/** Set a callback for encryption/decryption. */ +/** + * Set a callback for encryption/decryption. Callback must return -1 on error, + * and the callback must drain the part of *in that has been encrypted/decrypted. + * If the callback receives an incomplete ciphertext were nothing can be + * decrypted it should just leave the buffers and return 0. + * + */ void evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, - void (*)(struct evbuffer *, void *, int encrypt), void *); + int (*)(struct evbuffer *out, struct evbuffer *in, void *, int encrypt), void *); /** * Associates an event base with the connection - can only be called diff --git a/src/evrtsp/rtsp-internal.h b/src/evrtsp/rtsp-internal.h index 957f3c33..ce0099a0 100644 --- a/src/evrtsp/rtsp-internal.h +++ b/src/evrtsp/rtsp-internal.h @@ -59,8 +59,11 @@ struct evrtsp_connection { int fd; struct event ev; struct event close_ev; - struct evbuffer *input_buffer; - struct evbuffer *output_buffer; + struct evbuffer *input_buffer; /* Always plaintext */ + struct evbuffer *output_buffer; /* Always plaintext */ + + struct evbuffer *input_raw; /* Possibly ciphered */ + struct evbuffer *output_raw; /* Possibly ciphered */ char *bind_address; /* address to use for binding the src */ u_short bind_port; /* local port for binding the src */ @@ -85,7 +88,7 @@ struct evrtsp_connection { void (*closecb)(struct evrtsp_connection *, void *); void *closecb_arg; - void (*ciphercb)(struct evbuffer *evbuf, void *, int encrypt); + int (*ciphercb)(struct evbuffer *out, struct evbuffer *in, void *, int encrypt); void *ciphercb_arg; struct event_base *base; diff --git a/src/evrtsp/rtsp.c b/src/evrtsp/rtsp.c index 0f4ff376..daf07b55 100644 --- a/src/evrtsp/rtsp.c +++ b/src/evrtsp/rtsp.c @@ -474,7 +474,7 @@ evrtsp_write(int fd, short what, void *arg) return; } - n = evbuffer_write(evcon->output_buffer, fd); + n = evbuffer_write(evcon->output_raw, fd); if (n == -1) { event_warn("%s: evbuffer_write", __func__); evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); @@ -487,7 +487,7 @@ evrtsp_write(int fd, short what, void *arg) return; } - if (evbuffer_get_length(evcon->output_buffer) != 0) { + if (evbuffer_get_length(evcon->output_raw) != 0) { evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_WRITE_TIMEOUT); return; @@ -581,48 +581,9 @@ evrtsp_read_body(struct evrtsp_connection *evcon, struct evrtsp_request *req) evrtsp_add_event(&evcon->ev, evcon->timeout, RTSP_READ_TIMEOUT); } -/* - * Reads data into a buffer structure until no more data - * can be read on the file descriptor or we have read all - * the data that we wanted to read. - * Execute callback when done. - */ - -void -evrtsp_read(int fd, short what, void *arg) +static void +evrtsp_read_message(struct evrtsp_connection *evcon, struct evrtsp_request *req) { - struct evrtsp_connection *evcon = arg; - struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); - struct evbuffer *buf = evcon->input_buffer; - int n; - - if (what == EV_TIMEOUT) { - event_warn("%s: read timeout", __func__); - evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT); - return; - } - n = evbuffer_read(buf, fd, -1); - event_debug(("%s: got %d on %d", __func__, n, fd)); - - if (n == -1) { - if (errno != EINTR && errno != EAGAIN) { - event_warn("%s: evbuffer_read", __func__); - evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); - } else { - evrtsp_add_event(&evcon->ev, evcon->timeout, - RTSP_READ_TIMEOUT); - } - return; - } else if (n == 0) { - /* Connection closed */ - evcon->state = EVCON_DISCONNECTED; - evrtsp_connection_done(evcon); - return; - } - - if (evcon->ciphercb) - evcon->ciphercb(evcon->input_buffer, evcon->ciphercb_arg, 0); - switch (evcon->state) { case EVCON_READING_FIRSTLINE: evrtsp_read_firstline(evcon, req); @@ -646,6 +607,64 @@ evrtsp_read(int fd, short what, void *arg) } } +/* + * Reads data into a buffer structure until no more data + * can be read on the file descriptor or we have read all + * the data that we wanted to read. + * Execute callback when done. + */ + +void +evrtsp_read(int fd, short what, void *arg) +{ + struct evrtsp_connection *evcon = arg; + struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); + int ret; + int n; + + if (what == EV_TIMEOUT) { + event_warn("%s: read timeout", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_TIMEOUT); + return; + } + n = evbuffer_read(evcon->input_raw, fd, -1); + event_debug(("%s: got %d on %d", __func__, n, fd)); + + if (n == -1) { + if (errno != EINTR && errno != EAGAIN) { + event_warn("%s: evbuffer_read", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); + } else { + evrtsp_add_event(&evcon->ev, evcon->timeout, + RTSP_READ_TIMEOUT); + } + return; + } else if (n == 0) { + /* Connection closed */ + evcon->state = EVCON_DISCONNECTED; + evrtsp_connection_done(evcon); + return; + } + + if (!evcon->ciphercb) { + evbuffer_add_buffer(evcon->input_buffer, evcon->input_raw); + } else { + // May get an incomplete ciphered messages, in which case + // input_buffer will be filled with only the part that could be + // decrypted, and the callback will drain input_raw so that only + // any remaining undecryptable seqment is left. Next read should + // then add what is missing. + ret = evcon->ciphercb(evcon->input_buffer, evcon->input_raw, evcon->ciphercb_arg, 0); + if (ret < 0) { + event_warn("%s: evbuffer_read", __func__); + evrtsp_connection_fail(evcon, EVCON_RTSP_EOF); + return; + } + } + + evrtsp_read_message(evcon, req); +} + static void evrtsp_write_connectioncb(struct evrtsp_connection *evcon, void *arg) { @@ -705,6 +724,12 @@ evrtsp_connection_free(struct evrtsp_connection *evcon) if (evcon->output_buffer != NULL) evbuffer_free(evcon->output_buffer); + if (evcon->input_raw != NULL) + evbuffer_free(evcon->input_raw); + + if (evcon->output_raw != NULL) + evbuffer_free(evcon->output_raw); + free(evcon); } @@ -729,8 +754,10 @@ evrtsp_request_dispatch(struct evrtsp_connection* evcon) evrtsp_make_header(evcon, req); /* forked-daapd customisation for encryption */ - if (evcon->ciphercb) - evcon->ciphercb(evcon->output_buffer, evcon->ciphercb_arg, 1); + if (!evcon->ciphercb) + evbuffer_add_buffer(evcon->output_raw, evcon->output_buffer); + else + evcon->ciphercb(evcon->output_raw, evcon->output_buffer, evcon->ciphercb_arg, 1); evrtsp_write_buffer(evcon, evrtsp_write_connectioncb, NULL); } @@ -756,6 +783,10 @@ evrtsp_connection_reset(struct evrtsp_connection *evcon) evbuffer_get_length(evcon->input_buffer)); evbuffer_drain(evcon->output_buffer, evbuffer_get_length(evcon->output_buffer)); + evbuffer_drain(evcon->input_raw, + evbuffer_get_length(evcon->input_raw)); + evbuffer_drain(evcon->output_raw, + evbuffer_get_length(evcon->output_raw)); } static void @@ -1285,6 +1316,16 @@ evrtsp_connection_new(const char *address, unsigned short port) goto error; } + if ((evcon->input_raw = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + + if ((evcon->output_raw = evbuffer_new()) == NULL) { + event_warn("%s: evbuffer_new failed", __func__); + goto error; + } + evcon->state = EVCON_DISCONNECTED; TAILQ_INIT(&evcon->requests); @@ -1321,7 +1362,8 @@ evrtsp_connection_set_closecb(struct evrtsp_connection *evcon, void evrtsp_connection_set_ciphercb(struct evrtsp_connection *evcon, - void (*cb)(struct evbuffer *, void *, int encrypt), void *cbarg) + int (*cb)(struct evbuffer *, struct evbuffer *, void *, int encrypt), + void *cbarg) { evcon->ciphercb = cb; evcon->ciphercb_arg = cbarg; @@ -1482,42 +1524,6 @@ evrtsp_start_read(struct evrtsp_connection *evcon) evcon->state = EVCON_READING_FIRSTLINE; } -static void -evrtsp_send_done(struct evrtsp_connection *evcon, void *arg) -{ - struct evrtsp_request *req = TAILQ_FIRST(&evcon->requests); - TAILQ_REMOVE(&evcon->requests, req, next); - - /* delete possible close detection events */ - evrtsp_connection_stop_detectclose(evcon); - - assert(req->flags & EVRTSP_REQ_OWN_CONNECTION); - evrtsp_request_free(req); -} - -/* Requires that headers and response code are already set up */ - -static inline void -evrtsp_send(struct evrtsp_request *req, struct evbuffer *databuf) -{ - struct evrtsp_connection *evcon = req->evcon; - - if (evcon == NULL) { - evrtsp_request_free(req); - return; - } - - assert(TAILQ_FIRST(&evcon->requests) == req); - - /* xxx: not sure if we really should expose the data buffer this way */ - if (databuf != NULL) - evbuffer_add_buffer(req->output_buffer, databuf); - - /* Adds headers to the response */ - evrtsp_make_header(evcon, req); - - evrtsp_write_buffer(evcon, evrtsp_send_done, NULL); -} /* * Request related functions diff --git a/src/outputs/airplay.c b/src/outputs/airplay.c index cd4a90d1..865e18a2 100644 --- a/src/outputs/airplay.c +++ b/src/outputs/airplay.c @@ -1000,18 +1000,18 @@ rtpinfo_header_add(struct evrtsp_request *req, struct airplay_session *rs, struc return 0; } -static void -rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt) +static int +rtsp_cipher(struct evbuffer *outbuf, struct evbuffer *inbuf, void *arg, int encrypt) { struct airplay_session *rs = arg; uint8_t *in; size_t in_len; uint8_t *out = NULL; size_t out_len = 0; - int ret; + ssize_t processed; - in = evbuffer_pullup(evbuf, -1); - in_len = evbuffer_get_length(evbuf); + in = evbuffer_pullup(inbuf, -1); + in_len = evbuffer_get_length(inbuf); if (encrypt) { @@ -1022,14 +1022,14 @@ rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt) DPRINTF(E_DBG, L_AIRPLAY, "Encrypting outgoing request (size %zu)\n", in_len); #endif - ret = pair_encrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); - if (ret < 0) + processed = pair_encrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); + if (processed < 0) goto error; } else { - ret = pair_decrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); - if (ret < 0) + processed = pair_decrypt(&out, &out_len, in, in_len, rs->control_cipher_ctx); + if (processed < 0) goto error; #if AIRPLAY_DUMP_TRAFFIC @@ -1040,18 +1040,15 @@ rtsp_cipher(struct evbuffer *evbuf, void *arg, int encrypt) #endif } - evbuffer_drain(evbuf, in_len); - evbuffer_add(evbuf, out, out_len); + evbuffer_drain(inbuf, processed); + evbuffer_add(outbuf, out, out_len); - return; + return 0; error: DPRINTF(E_LOG, L_AIRPLAY, "Error while %s (len=%zu): %s\n", encrypt ? "encrypting" : "decrypting", in_len, pair_cipher_errmsg(rs->control_cipher_ctx)); - // This will lead evrtsp to timeout the request, which takes a while, so we - // should consider some quicker error handling instead - evbuffer_drain(evbuf, in_len); - return; + return -1; }