From: Martin Blanchard <tchaik@xxxxxxx> RAOP authentication is using standard HTTP challenge-response authentication scheme. This patch adds two helper functions that generate the proper hash (for both techniques) given a username, a password and session related tokens. --- src/modules/raop/raop_client.c | 245 +++++++++++++++++++++++++++++++++++++++-- src/modules/raop/raop_client.h | 4 + src/modules/raop/raop_util.c | 40 +++++++ src/modules/raop/raop_util.h | 4 + src/modules/rtp/rtsp_client.c | 83 ++++++++++---- src/modules/rtp/rtsp_client.h | 26 +++-- 6 files changed, 359 insertions(+), 43 deletions(-) diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c index a2ec91e..1323cd0 100644 --- a/src/modules/raop/raop_client.c +++ b/src/modules/raop/raop_client.c @@ -65,6 +65,9 @@ #define VOLUME_MIN -144 #define VOLUME_MAX 0 +#define USER_AGENT "iTunes/11.0.4 (Windows; N)" +#define USER_NAME "iTunes" + #define DEFAULT_RAOP_PORT 5000 #define UDP_DEFAULT_AUDIO_PORT 6000 #define UDP_DEFAULT_CONTROL_PORT 6001 @@ -85,13 +88,15 @@ struct pa_raop_client { pa_core *core; char *host; uint16_t port; - char *sid; pa_rtsp_client *rtsp; - pa_raop_protocol_t protocol; + char *sci, *sid; + char *pwd; uint8_t jack_type; uint8_t jack_status; + pa_raop_protocol_t protocol; + int encryption; /* Enable encryption? */ pa_raop_secret *aes; @@ -125,6 +130,9 @@ struct pa_raop_client { uint32_t udp_sync_interval; uint32_t udp_sync_count; + pa_raop_client_auth_cb_t udp_auth_callback; + void *udp_auth_userdata; + pa_raop_client_setup_cb_t udp_setup_callback; void *udp_setup_userdata; @@ -576,7 +584,7 @@ static void do_rtsp_announce(pa_raop_client *c) { pa_xfree(sdp); } -static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { +static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist* headers, void *userdata) { pa_raop_client* c = userdata; pa_assert(c); pa_assert(rtsp); @@ -675,12 +683,13 @@ static void tcp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist } } -static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist *headers, void *userdata) { +static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) { pa_raop_client *c = userdata; pa_assert(c); pa_assert(rtsp); pa_assert(rtsp == c->rtsp); + pa_assert(STATUS_OK == status); switch (state) { case STATE_CONNECT: { @@ -982,6 +991,178 @@ static void udp_rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist } } +static void rtsp_authentication_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_rtsp_status status, pa_headerlist *headers, void *userdata) { + pa_raop_client *c = userdata; + + pa_assert(c); + pa_assert(rtsp); + pa_assert(rtsp == c->rtsp); + + switch (state) { + case STATE_CONNECT: { + char *sci = NULL, *sac = NULL; + uint16_t rac; + struct { + uint32_t ci1; + uint32_t ci2; + } rci; + + pa_random(&rci, sizeof(rci)); + /* Generate a random Client-Instance number */ + sci = pa_sprintf_malloc("%08x%08x",rci.ci1, rci.ci2); + pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); + + pa_random(&rac, sizeof(rac)); + /* Generate a random Apple-Challenge key */ + pa_raop_base64_encode(&rac, 8*sizeof(rac), &sac); + pa_log_debug ("APPLECHALLENGE >> %s | %d", sac, (int) sizeof(rac)); + rtrimchar(sac, '='); + pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); + + pa_rtsp_options(c->rtsp); + + pa_xfree(sac); + pa_xfree(sci); + break; + } + + case STATE_OPTIONS: { + static bool waiting = false; + const char *current = NULL; + char space[] = " "; + char *token,*ath = NULL; + char *publ, *wath, *mth, *val; + char *realm = NULL, *nonce = NULL, *response = NULL; + char comma[] = ","; + + pa_log_debug("RAOP: OPTIONS"); + /* We do not consider the Apple-Response */ + pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); + + if (STATUS_UNAUTHORIZED == status) { + wath = pa_xstrdup(pa_headerlist_gets(headers, "WWW-Authenticate")); + if (true == waiting) { + pa_xfree(wath); + goto failure; + } + + if (wath) + mth = pa_split(wath, space, ¤t); + while ((token = pa_split(wath, comma, ¤t))) { + val = NULL; + if ((val = strstr(token, "="))) { + if (NULL == realm && val > strstr(token, "realm")) + realm = pa_xstrdup(val + 2); + else if (NULL == nonce && val > strstr(token, "nonce")) + nonce = pa_xstrdup(val + 2); + val = NULL; + } + + pa_xfree(token); + } + + if (pa_safe_streq(mth, "Basic")) { + rtrimchar(realm, '\"'); + + pa_raop_basic_response(USER_NAME, c->pwd, &response); + ath = pa_sprintf_malloc("Basic %s", + response); + + pa_xfree(response); + pa_xfree(realm); + } else if (pa_safe_streq(mth, "Digest")) { + rtrimchar(realm, '\"'); + rtrimchar(nonce, '\"'); + + pa_raop_digest_response(USER_NAME, realm, c->pwd, nonce, "*", &response); + ath = pa_sprintf_malloc("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"*\", response=\"%s\"", + USER_NAME, realm, nonce, + response); + + pa_xfree(response); + pa_xfree(realm); + pa_xfree(nonce); + } else { + pa_log_error("unsupported authentication method: %s", mth); + pa_xfree(wath); + pa_xfree(mth); + goto error; + } + + pa_xfree(wath); + pa_xfree(mth); + + pa_rtsp_add_header(c->rtsp, "Authorization", ath); + pa_xfree(ath); + + waiting = true; + pa_rtsp_options(c->rtsp); + break; + } + + if (STATUS_OK == status) { + publ = pa_xstrdup(pa_headerlist_gets(headers, "Public")); + c->sci = pa_xstrdup(pa_rtsp_get_header(c->rtsp, "Client-Instance")); + + if (c->pwd) + pa_xfree(c->pwd); + pa_xfree(publ); + c->pwd = NULL; + } + + if (c->udp_auth_callback) + c->udp_auth_callback((int) status, c->udp_auth_userdata); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + + waiting = false; + break; + + failure: + if (c->udp_auth_callback) + c->udp_auth_callback((int) STATUS_UNAUTHORIZED, c->udp_auth_userdata); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + + pa_log_error("aborting RTSP authentication, wrong password"); + + waiting = false; + break; + + error: + if (c->udp_auth_callback) + c->udp_auth_callback((int) status, c->udp_auth_userdata); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + + pa_log_error("aborting RTSP authentication, unexpected failure"); + + waiting = false; + break; + } + + case STATE_ANNOUNCE: + case STATE_SETUP: + case STATE_RECORD: + case STATE_SET_PARAMETER: + case STATE_FLUSH: + case STATE_TEARDOWN: + case STATE_DISCONNECTED: + default: { + if (c->udp_auth_callback) + c->udp_auth_callback((int) STATUS_BAD_REQUEST, c->udp_auth_userdata); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + + if (c->sci) + pa_xfree(c->sci); + c->sci = NULL; + + break; + } + } +} + pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol) { pa_raop_client* c; pa_parsed_address a; @@ -1047,15 +1228,40 @@ void pa_raop_client_free(pa_raop_client *c) { if (c->sid) pa_xfree(c->sid); + if (c->sci) + pa_xfree(c->sci); + c->sci = c->sid = NULL; + if (c->aes) pa_raop_secret_free(c->aes); if (c->rtsp) pa_rtsp_client_free(c->rtsp); - pa_xfree(c->host); + c->rtsp = NULL; + pa_xfree(c->host); pa_xfree(c); } +int pa_raop_client_authenticate (pa_raop_client *c, const char *password) { + + pa_assert(c); + + if (c->rtsp || c->pwd) { + pa_log_debug("Connection already in progress"); + return 0; + } + + c->pwd = NULL; + if (password) + c->pwd = pa_xstrdup(password); + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT); + + pa_assert(c->rtsp); + + pa_rtsp_set_callback(c->rtsp, rtsp_authentication_cb, c); + return pa_rtsp_connect(c->rtsp); +} + int pa_raop_client_connect(pa_raop_client *c) { char *sci; struct { @@ -1074,7 +1280,7 @@ int pa_raop_client_connect(pa_raop_client *c) { if (c->protocol == RAOP_TCP) c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); else - c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, "iTunes/7.6.2 (Windows; N;)"); + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, c->port, USER_AGENT); /* Generate random instance id. */ pa_random(&rand_data, sizeof(rand_data)); @@ -1116,6 +1322,17 @@ int pa_raop_client_teardown(pa_raop_client *c) { return rv; } +int pa_raop_client_udp_is_authenticated(pa_raop_client *c) { + int rv = 0; + + pa_assert(c); + + if (c->sci != NULL) + rv = 1; + + return rv; +} + int pa_raop_client_udp_is_alive(pa_raop_client *c) { int rv = 0; @@ -1147,7 +1364,6 @@ int pa_raop_client_udp_stream(pa_raop_client *c) { if (!c->is_recording) { c->udp_first_packet = true; c->udp_sync_count = 0; - c->is_recording = true; } @@ -1326,6 +1542,14 @@ ssize_t pa_raop_client_udp_send_audio_packet(pa_raop_client *c, pa_memchunk *blo return len; } +void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { + pa_assert(c); + + c->encryption = encryption; + if (c->encryption) + c->aes = pa_raop_secret_new(); +} + /* Adjust volume so that it fits into VOLUME_DEF <= v <= 0 dB */ pa_volume_t pa_raop_client_adjust_volume(pa_raop_client *c, pa_volume_t volume) { double minv, maxv; @@ -1469,12 +1693,11 @@ void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_cl c->tcp_closed_userdata = userdata; } -void pa_raop_client_set_encryption(pa_raop_client *c, int encryption) { +void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata) { pa_assert(c); - c->encryption = encryption; - if (c->encryption) - c->aes = pa_raop_secret_new(); + c->udp_auth_callback = callback; + c->udp_auth_userdata = userdata; } void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata) { diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h index 5d0bb14..e208349 100644 --- a/src/modules/raop/raop_client.h +++ b/src/modules/raop/raop_client.h @@ -37,10 +37,12 @@ typedef struct pa_raop_client pa_raop_client; pa_raop_client* pa_raop_client_new(pa_core *core, const char *host, pa_raop_protocol_t protocol); void pa_raop_client_free(pa_raop_client *c); +int pa_raop_client_authenticate (pa_raop_client *c, const char *password); int pa_raop_client_connect(pa_raop_client *c); int pa_raop_client_flush(pa_raop_client *c); int pa_raop_client_teardown(pa_raop_client *c); +int pa_raop_client_udp_is_authenticated(pa_raop_client *c); int pa_raop_client_udp_is_alive(pa_raop_client *c); int pa_raop_client_udp_can_stream(pa_raop_client *c); int pa_raop_client_udp_stream(pa_raop_client *c); @@ -61,6 +63,8 @@ void pa_raop_client_tcp_set_callback(pa_raop_client *c, pa_raop_client_cb_t call typedef void (*pa_raop_client_closed_cb_t)(void *userdata); void pa_raop_client_tcp_set_closed_callback(pa_raop_client *c, pa_raop_client_closed_cb_t callback, void *userdata); +typedef void (*pa_raop_client_auth_cb_t)(int status, void *userdata); +void pa_raop_client_udp_set_auth_callback(pa_raop_client *c, pa_raop_client_auth_cb_t callback, void *userdata); typedef void (*pa_raop_client_setup_cb_t)(int control_fd, int timing_fd, void *userdata); void pa_raop_client_udp_set_setup_callback(pa_raop_client *c, pa_raop_client_setup_cb_t callback, void *userdata); diff --git a/src/modules/raop/raop_util.c b/src/modules/raop/raop_util.c index c9021ec..709ec27 100644 --- a/src/modules/raop/raop_util.c +++ b/src/modules/raop/raop_util.c @@ -35,6 +35,7 @@ #include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> #include <pulsecore/macro.h> #include "raop_util.h" @@ -168,3 +169,42 @@ int pa_raop_md5_hash(const char *data, int len, char **str) { s[MD5_HASH_LENGTH] = 0; return strlen(s); } + +int pa_raop_basic_response(const char *user, const char *pwd, char **str) { + char *tmp, *B = NULL; + + pa_assert(str); + + tmp = pa_sprintf_malloc("%s:%s", user, pwd); + pa_raop_base64_encode(tmp, strlen(tmp), &B); + pa_xfree(tmp); + + *str = B; + return strlen(B); +} + +int pa_raop_digest_response(const char *user, const char *realm, const char *password, + const char *nonce, const char *uri, char **str) { + char *A1, *HA1, *A2, *HA2; + char *tmp, *KD = NULL; + + pa_assert(str); + + A1 = pa_sprintf_malloc("%s:%s:%s", user, realm, password); + pa_raop_md5_hash(A1, strlen(A1), &HA1); + pa_xfree(A1); + + A2 = pa_sprintf_malloc("OPTIONS:%s", uri); + pa_raop_md5_hash(A2, strlen(A2), &HA2); + pa_xfree(A2); + + tmp = pa_sprintf_malloc("%s:%s:%s", HA1, nonce, HA2); + pa_raop_md5_hash(tmp, strlen(tmp), &KD); + pa_xfree(tmp); + + pa_xfree(HA1); + pa_xfree(HA2); + + *str = KD; + return strlen(KD); +} diff --git a/src/modules/raop/raop_util.h b/src/modules/raop/raop_util.h index dc0b767..d3f7566 100644 --- a/src/modules/raop/raop_util.h +++ b/src/modules/raop/raop_util.h @@ -32,4 +32,8 @@ int pa_raop_base64_decode(const char *str, void *data); int pa_raop_md5_hash(const char *data, int len, char **str); +int pa_raop_basic_response(const char *user, const char *pwd, char **str); +int pa_raop_digest_response(const char *user, const char *realm, const char *password, + const char *nonce, const char *uri, char **str); + #endif diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 198417e..c6d9ac6 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -59,6 +59,7 @@ struct pa_rtsp_client { const char *useragent; pa_rtsp_state state; + pa_rtsp_status status; uint8_t waiting; pa_headerlist* headers; @@ -119,8 +120,8 @@ void pa_rtsp_client_free(pa_rtsp_client *c) { } static void headers_read(pa_rtsp_client *c) { - char* token; char delimiters[] = ";"; + char* token; pa_assert(c); pa_assert(c->response_headers); @@ -165,14 +166,14 @@ static void headers_read(pa_rtsp_client *c) { } /* Call our callback */ - c->callback(c, c->state, c->response_headers, c->userdata); + c->callback(c, c->state, c->status, c->response_headers, c->userdata); } static void line_callback(pa_ioline *line, const char *s, void *userdata) { + pa_rtsp_client *c = userdata; char *delimpos; char *s2, *s2p; - pa_rtsp_client *c = userdata; pa_assert(line); pa_assert(c); pa_assert(c->callback); @@ -180,7 +181,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { if (!s) { /* Keep the ioline/iochannel open as they will be freed automatically */ c->ioline = NULL; - c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); + c->callback(c, STATE_DISCONNECTED, STATUS_NO_RESPONSE, NULL, c->userdata); return; } @@ -191,17 +192,35 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { *s2p = '\0'; s2p -= 1; } + if (c->waiting && pa_streq(s2, "RTSP/1.0 200 OK")) { + if (c->response_headers) + pa_headerlist_free(c->response_headers); + c->response_headers = pa_headerlist_new(); + + c->status = STATUS_OK; c->waiting = 0; + goto exit; + } else if (c->waiting && pa_streq(s2, "RTSP/1.0 401 Unauthorized")) { if (c->response_headers) pa_headerlist_free(c->response_headers); c->response_headers = pa_headerlist_new(); + + c->status = STATUS_UNAUTHORIZED; + c->waiting = 0; goto exit; - } - if (c->waiting) { - pa_log_warn("Unexpected response: %s", s2); + } else if (c->waiting) { + pa_log_warn("Unexpected/Unhandled response: %s", s2); + + if (pa_streq(s2, "RTSP/1.0 400 Bad Request")) + c->status = STATUS_BAD_REQUEST; + else if (pa_streq(s2, "RTSP/1.0 500 Internal Server Error")) + c->status = STATUS_INTERNAL_ERROR; + else + c->status = STATUS_NO_RESPONSE; goto exit; } + if (!strlen(s2)) { /* End of headers */ /* We will have a header left from our looping iteration, so add it in :) */ @@ -228,7 +247,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { if (c->last_header && ' ' == s2[0]) { pa_assert(c->header_buffer); - /* Add this line to the buffer (sans the space. */ + /* Add this line to the buffer (sans the space) */ pa_strbuf_puts(c->header_buffer, &(s2[1])); goto exit; } @@ -270,6 +289,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { /* Save the header name */ c->last_header = pa_xstrdup(s2); + exit: pa_xfree(s2); } @@ -317,7 +337,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_log_debug("Established RTSP connection from local ip %s", c->localip); if (c->callback) - c->callback(c, c->state, NULL, c->userdata); + c->callback(c, c->state, STATUS_OK, NULL, c->userdata); } int pa_rtsp_connect(pa_rtsp_client *c) { @@ -336,6 +356,7 @@ int pa_rtsp_connect(pa_rtsp_client *c) { pa_socket_client_set_callback(c->sc, on_connection, c); c->waiting = 1; c->state = STATE_CONNECT; + c->status = STATUS_NO_RESPONSE; return 0; } @@ -368,12 +389,25 @@ uint32_t pa_rtsp_serverport(pa_rtsp_client *c) { return c->rtp_port; } +bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { + pa_assert(c); + + return c->url != NULL && c->ioline != NULL; +} + void pa_rtsp_set_url(pa_rtsp_client *c, const char *url) { pa_assert(c); c->url = pa_xstrdup(url); } +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key) { + pa_assert(c); + pa_assert(key); + + return pa_headerlist_contains(c->headers, key); +} + void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { pa_assert(c); pa_assert(key); @@ -382,17 +416,18 @@ void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value) { pa_headerlist_puts(c->headers, key, value); } -void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key) { pa_assert(c); pa_assert(key); - pa_headerlist_remove(c->headers, key); + return pa_headerlist_gets(c->headers, key); } -bool pa_rtsp_exec_ready(const pa_rtsp_client *c) { +void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key) { pa_assert(c); + pa_assert(key); - return c->url != NULL && c->ioline != NULL; + pa_headerlist_remove(c->headers, key); } static int rtsp_exec(pa_rtsp_client *c, const char *cmd, @@ -527,17 +562,6 @@ int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime) { return rv; } -int pa_rtsp_teardown(pa_rtsp_client *c) { - int rv; - - pa_assert(c); - - c->state = STATE_TEARDOWN; - rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); - - return rv; -} - int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param) { int rv; @@ -570,3 +594,14 @@ int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { pa_headerlist_free(headers); return rv; } + +int pa_rtsp_teardown(pa_rtsp_client *c) { + int rv; + + pa_assert(c); + + c->state = STATE_TEARDOWN; + rv = rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); + + return rv; +} diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index abc60ee..4a35851 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -31,43 +31,53 @@ #include "headerlist.h" typedef struct pa_rtsp_client pa_rtsp_client; + typedef enum { STATE_CONNECT, STATE_OPTIONS, STATE_ANNOUNCE, STATE_SETUP, STATE_RECORD, + STATE_SET_PARAMETER, STATE_FLUSH, STATE_TEARDOWN, - STATE_SET_PARAMETER, STATE_DISCONNECTED } pa_rtsp_state; -typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist *hl, void *userdata); + +typedef enum { + STATUS_OK = 200, + STATUS_BAD_REQUEST = 400, + STATUS_UNAUTHORIZED = 401, + STATUS_NO_RESPONSE = 444, + STATUS_INTERNAL_ERROR = 500 +} pa_rtsp_status; + +typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_rtsp_status code, pa_headerlist *headers, void *userdata); pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char *hostname, uint16_t port, const char *useragent); void pa_rtsp_client_free(pa_rtsp_client *c); int pa_rtsp_connect(pa_rtsp_client *c); void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); - void pa_rtsp_disconnect(pa_rtsp_client *c); const char* pa_rtsp_localip(pa_rtsp_client *c); uint32_t pa_rtsp_serverport(pa_rtsp_client *c); +bool pa_rtsp_exec_ready(const pa_rtsp_client *c); + void pa_rtsp_set_url(pa_rtsp_client *c, const char *url); + +bool pa_rtsp_has_header(pa_rtsp_client *c, const char *key); void pa_rtsp_add_header(pa_rtsp_client *c, const char *key, const char *value); +const char* pa_rtsp_get_header(pa_rtsp_client *c, const char *key); void pa_rtsp_remove_header(pa_rtsp_client *c, const char *key); -bool pa_rtsp_exec_ready(const pa_rtsp_client *c); - int pa_rtsp_options(pa_rtsp_client *c); int pa_rtsp_announce(pa_rtsp_client *c, const char *sdp); - int pa_rtsp_setup(pa_rtsp_client *c, const char *transport); int pa_rtsp_record(pa_rtsp_client *c, uint16_t *seq, uint32_t *rtptime); -int pa_rtsp_teardown(pa_rtsp_client *c); - int pa_rtsp_setparameter(pa_rtsp_client *c, const char *param); int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime); +int pa_rtsp_teardown(pa_rtsp_client *c); #endif -- 2.9.3