/* * RTSP protocol handler. This file is part of Shairport. * Copyright (c) James Laird 2013 * All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "player.h" #include "rtp.h" #include "mdns.h" #ifdef AF_INET6 #define INETx_ADDRSTRLEN INET6_ADDRSTRLEN #else #define INETx_ADDRSTRLEN INET_ADDRSTRLEN #endif // only one thread is allowed to use the player at once. // it monitors the request variable (at least when interrupted) static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER; static int please_shutdown = 0; static pthread_t playing_thread = 0; typedef struct { int fd; stream_cfg stream; SOCKADDR remote; int running; pthread_t thread; } rtsp_conn_info; static inline int rtsp_playing(void) { return playing_thread == pthread_self(); } static void rtsp_take_player(void) { if (pthread_mutex_trylock(&playing_mutex)) { debug(1, "shutting down playing thread\n"); // XXX minor race condition between please_shutdown and signal delivery please_shutdown = 1; pthread_kill(playing_thread, SIGUSR1); pthread_mutex_lock(&playing_mutex); } playing_thread = pthread_self(); } void rtsp_shutdown_stream(void) { rtsp_take_player(); pthread_mutex_unlock(&playing_mutex); } // keep track of the threads we have spawned so we can join() them static rtsp_conn_info **conns = NULL; static int nconns = 0; static void track_thread(rtsp_conn_info *conn) { conns = realloc(conns, sizeof(rtsp_conn_info*) * (nconns + 1)); conns[nconns] = conn; nconns++; } static void cleanup_threads(void) { void *retval; int i; debug(2, "culling threads.\n"); for (i=0; irunning == 0) { pthread_join(conns[i]->thread, &retval); free(conns[i]); debug(2, "one joined\n"); nconns--; if (nconns) conns[i] = conns[nconns]; } else { i++; } } } // park a null at the line ending, and return the next line pointer // accept \r, \n, or \r\n static char *nextline(char *in, int inbuf) { char *out = NULL; while (inbuf) { if (*in == '\r') { *in++ = 0; out = in; } if (*in == '\n') { *in++ = 0; out = in; } if (out) break; in++; inbuf--; } return out; } typedef struct { int nheaders; char *name[16]; char *value[16]; int contentlength; char *content; // for requests char method[16]; // for responses int respcode; } rtsp_message; static rtsp_message * msg_init(void) { rtsp_message *msg = malloc(sizeof(rtsp_message)); memset(msg, 0, sizeof(rtsp_message)); return msg; } static int msg_add_header(rtsp_message *msg, char *name, char *value) { if (msg->nheaders >= sizeof(msg->name)/sizeof(char*)) { warn("too many headers?!"); return 1; } msg->name[msg->nheaders] = strdup(name); msg->value[msg->nheaders] = strdup(value); msg->nheaders++; return 0; } static char *msg_get_header(rtsp_message *msg, char *name) { int i; for (i=0; inheaders; i++) if (!strcasecmp(msg->name[i], name)) return msg->value[i]; return NULL; } static void msg_free(rtsp_message *msg) { int i; for (i=0; inheaders; i++) { free(msg->name[i]); free(msg->value[i]); } if (msg->content) free(msg->content); free(msg); } static int msg_handle_line(rtsp_message **pmsg, char *line) { rtsp_message *msg = *pmsg; if (!msg) { msg = msg_init(); *pmsg = msg; char *sp, *p; debug(1, "received request: %s\n", line); p = strtok_r(line, " ", &sp); if (!p) goto fail; strncpy(msg->method, p, sizeof(msg->method)-1); p = strtok_r(NULL, " ", &sp); if (!p) goto fail; p = strtok_r(NULL, " ", &sp); if (!p) goto fail; if (strcmp(p, "RTSP/1.0")) goto fail; return -1; } if (strlen(line)) { char *p; p = strstr(line, ": "); if (!p) { warn("bad header: >>%s<<", line); goto fail; } *p = 0; p += 2; msg_add_header(msg, line, p); debug(2, " %s: %s\n", line, p); return -1; } else { char *cl = msg_get_header(msg, "Content-Length"); if (cl) return atoi(cl); else return 0; } fail: *pmsg = NULL; msg_free(msg); return 0; } static rtsp_message * rtsp_read_request(int fd) { ssize_t buflen = 512; char *buf = malloc(buflen+1); rtsp_message *msg = NULL; ssize_t nread; ssize_t inbuf = 0; int msg_size = -1; while (msg_size < 0) { if (please_shutdown) { debug(1, "RTSP shutdown requested\n"); goto shutdown; } nread = read(fd, buf+inbuf, buflen - inbuf); if (!nread) { debug(1, "RTSP connection closed\n"); goto shutdown; } if (nread < 0) { if (errno==EINTR) continue; perror("read failure"); goto shutdown; } inbuf += nread; char *next; while (msg_size < 0 && (next = nextline(buf, inbuf))) { msg_size = msg_handle_line(&msg, buf); if (!msg) { warn("no RTSP header received"); goto shutdown; } inbuf -= next-buf; if (inbuf) memmove(buf, next, inbuf); } } if (msg_size > buflen) { buf = realloc(buf, msg_size); if (!buf) { warn("too much content"); goto shutdown; } buflen = msg_size; } while (inbuf < msg_size) { nread = read(fd, buf+inbuf, msg_size-inbuf); if (!nread) goto shutdown; if (nread==EINTR) continue; if (nread < 0) { perror("read failure"); goto shutdown; } inbuf += nread; } msg->contentlength = inbuf; msg->content = buf; return msg; shutdown: free(buf); if (msg) { msg_free(msg); } return NULL; } static void msg_write_response(int fd, rtsp_message *resp) { char rbuf[30]; int nrbuf; nrbuf = snprintf(rbuf, sizeof(rbuf), "RTSP/1.0 %d %s\r\n", resp->respcode, resp->respcode==200 ? "OK" : "Error"); write(fd, rbuf, nrbuf); debug(1, "sending response: %s", rbuf); int i; for (i=0; inheaders; i++) { debug(2, " %s: %s\n", resp->name[i], resp->value[i]); write(fd, resp->name[i], strlen(resp->name[i])); write(fd, ": ", 2); write(fd, resp->value[i], strlen(resp->value[i])); write(fd, "\r\n", 2); } write(fd, "\r\n", 2); } static void handle_options(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; msg_add_header(resp, "Public", "ANNOUNCE, SETUP, RECORD, " "PAUSE, FLUSH, TEARDOWN, " "OPTIONS, GET_PARAMETER, SET_PARAMETER"); } static void handle_teardown(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) return; resp->respcode = 200; msg_add_header(resp, "Connection", "close"); please_shutdown = 1; } static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!rtsp_playing()) return; player_flush(); resp->respcode = 200; } static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { int cport, tport; char *hdr = msg_get_header(req, "Transport"); if (!hdr) return; char *p; p = strstr(hdr, "control_port="); if (!p) return; p = strchr(p, '=') + 1; cport = atoi(p); p = strstr(hdr, "timing_port="); if (!p) return; p = strchr(p, '=') + 1; tport = atoi(p); rtsp_take_player(); int sport = rtp_setup(&conn->remote, cport, tport); if (!sport) return; player_play(&conn->stream); char *resphdr = malloc(strlen(hdr) + 20); strcpy(resphdr, hdr); sprintf(resphdr + strlen(resphdr), ";server_port=%d", sport); msg_add_header(resp, "Transport", resphdr); msg_add_header(resp, "Session", "1"); resp->respcode = 200; } static void handle_ignore(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { resp->respcode = 200; } static void handle_set_parameter(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { if (!req->contentlength) return; char *cp = req->content; int cp_left = req->contentlength; char *next; while (cp_left && cp) { next = nextline(cp, cp_left); cp_left -= next-cp; if (!strncmp(cp, "volume: ", 8)) { float volume = atof(cp + 8); debug(1, "volume: %f\n", volume); player_volume(volume); } else { debug(1, "unrecognised parameter: >>%s<< (%d)\n", cp, strlen(cp)); } cp = next; } resp->respcode = 200; } static void handle_announce(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) { char *paesiv = NULL; char *prsaaeskey = NULL; char *pfmtp = NULL; char *cp = req->content; int cp_left = req->contentlength; char *next; while (cp_left && cp) { next = nextline(cp, cp_left); cp_left -= next-cp; if (!strncmp(cp, "a=fmtp:", 7)) pfmtp = cp+7; if (!strncmp(cp, "a=aesiv:", 8)) paesiv = cp+8; if (!strncmp(cp, "a=rsaaeskey:", 12)) prsaaeskey = cp+12; cp = next; } if (!paesiv || !prsaaeskey || !pfmtp) { warn("required params missing from announce"); return; } int len, keylen; uint8_t *aesiv = base64_dec(paesiv, &len); if (len != 16) { warn("client announced aeskey of %d bytes, wanted 16", len); free(aesiv); return; } memcpy(conn->stream.aesiv, aesiv, 16); free(aesiv); uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len); uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY); free(rsaaeskey); if (keylen != 16) { warn("client announced rsaaeskey of %d bytes, wanted 16", keylen); free(aeskey); return; } memcpy(conn->stream.aeskey, aeskey, 16); free(aeskey); int i; for (i=0; istream.fmtp)/sizeof(conn->stream.fmtp[0]); i++) conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t")); resp->respcode = 200; } static struct method_handler { char *method; void (*handler)(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp); } method_handlers[] = { {"OPTIONS", handle_options}, {"ANNOUNCE", handle_announce}, {"FLUSH", handle_flush}, {"TEARDOWN", handle_teardown}, {"SETUP", handle_setup}, {"GET_PARAMETER", handle_ignore}, {"SET_PARAMETER", handle_set_parameter}, {"RECORD", handle_ignore}, {NULL, NULL} }; static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) { char *hdr = msg_get_header(req, "Apple-Challenge"); if (!hdr) return; SOCKADDR fdsa; socklen_t sa_len = sizeof(fdsa); getsockname(fd, (struct sockaddr*)&fdsa, &sa_len); int chall_len; uint8_t *chall = base64_dec(hdr, &chall_len); uint8_t buf[48], *bp = buf; int i; memset(buf, 0, sizeof(buf)); if (chall_len > 16) { warn("oversized Apple-Challenge!"); free(chall); return; } memcpy(bp, chall, chall_len); free(chall); bp += chall_len; #ifdef AF_INET6 if (fdsa.SAFAMILY == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(&fdsa); memcpy(bp, sa6->sin6_addr.s6_addr, 16); bp += 16; } else #endif { struct sockaddr_in *sa = (struct sockaddr_in*)(&fdsa); memcpy(bp, &sa->sin_addr.s_addr, 4); bp += 4; } for (i=0; i<6; i++) *bp++ = config.hw_addr[i]; int buflen, resplen; buflen = bp-buf; if (buflen < 0x20) buflen = 0x20; uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH); char *encoded = base64_enc(challresp, resplen); // strip the padding. char *padding = strchr(encoded, '='); if (padding) *padding = 0; msg_add_header(resp, "Apple-Response", encoded); free(challresp); free(encoded); } static char *make_nonce(void) { uint8_t random[8]; int fd = open("/dev/random", O_RDONLY); if (fd < 0) die("could not open /dev/random!"); read(fd, random, sizeof(random)); close(fd); return base64_enc(random, 8); } static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) { if (!config.password) return 0; if (!*nonce) { *nonce = make_nonce(); goto authenticate; } char *hdr = msg_get_header(req, "Authorization"); if (!hdr || strncmp(hdr, "Digest ", 7)) goto authenticate; char *realm = strstr(hdr, "realm=\""); char *username = strstr(hdr, "username=\""); char *response = strstr(hdr, "response=\""); char *uri = strstr(hdr, "uri=\""); if (!realm || !username || !response || !uri) goto authenticate; char *quote; realm = strchr(realm, '"') + 1; if (!(quote = strchr(realm, '"'))) goto authenticate; *quote = 0; username = strchr(username, '"') + 1; if (!(quote = strchr(username, '"'))) goto authenticate; *quote = 0; response = strchr(response, '"') + 1; if (!(quote = strchr(response, '"'))) goto authenticate; *quote = 0; uri = strchr(uri, '"') + 1; if (!(quote = strchr(uri, '"'))) goto authenticate; *quote = 0; uint8_t digest_urp[16], digest_mu[16], digest_total[16]; MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, username, strlen(username)); MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, realm, strlen(realm)); MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, config.password, strlen(config.password)); MD5_Final(digest_urp, &ctx); MD5_Init(&ctx); MD5_Update(&ctx, req->method, strlen(req->method)); MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, uri, strlen(uri)); MD5_Final(digest_mu, &ctx); int i; char buf[33]; for (i=0; i<16; i++) sprintf(buf + 2*i, "%02X", digest_urp[i]); MD5_Init(&ctx); MD5_Update(&ctx, buf, 32); MD5_Update(&ctx, ":", 1); MD5_Update(&ctx, *nonce, strlen(*nonce)); MD5_Update(&ctx, ":", 1); for (i=0; i<16; i++) sprintf(buf + 2*i, "%02X", digest_mu[i]); MD5_Update(&ctx, buf, 32); MD5_Final(digest_total, &ctx); for (i=0; i<16; i++) sprintf(buf + 2*i, "%02X", digest_total[i]); if (!strcmp(response, buf)) return 0; warn("auth failed"); authenticate: resp->respcode = 401; int hdrlen = strlen(*nonce) + 40; char *authhdr = malloc(hdrlen); snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce); msg_add_header(resp, "WWW-Authenticate", authhdr); free(authhdr); return 1; } static void *rtsp_conversation_thread_func(void *pconn) { // SIGUSR1 is used to interrupt this thread if blocked for read sigset_t set; sigemptyset(&set); sigaddset(&set, SIGUSR1); pthread_sigmask(SIG_UNBLOCK, &set, NULL); rtsp_conn_info *conn = pconn; rtsp_message *req, *resp; char *hdr, *auth_nonce = NULL; while ((req = rtsp_read_request(conn->fd))) { resp = msg_init(); resp->respcode = 400; apple_challenge(conn->fd, req, resp); hdr = msg_get_header(req, "CSeq"); if (hdr) msg_add_header(resp, "CSeq", hdr); msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog"); if (rtsp_auth(&auth_nonce, req, resp)) goto respond; struct method_handler *mh; for (mh=method_handlers; mh->method; mh++) { if (!strcmp(mh->method, req->method)) { mh->handler(conn, req, resp); break; } } respond: msg_write_response(conn->fd, resp); msg_free(req); msg_free(resp); } debug(1, "closing RTSP connection\n"); if (conn->fd > 0) close(conn->fd); if (rtsp_playing()) { rtp_shutdown(); player_stop(); please_shutdown = 0; pthread_mutex_unlock(&playing_mutex); } if (auth_nonce) free(auth_nonce); conn->running = 0; debug(2, "terminating RTSP thread\n"); return NULL; } // this function is not thread safe. static const char* format_address(struct sockaddr *fsa) { static char string[INETx_ADDRSTRLEN]; void *addr; #ifdef AF_INET6 if (fsa->sa_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(fsa); addr = &(sa6->sin6_addr); } else #endif { struct sockaddr_in *sa = (struct sockaddr_in*)(fsa); addr = &(sa->sin_addr); } return inet_ntop(fsa->sa_family, addr, string, sizeof(string)); } void rtsp_listen_loop(void) { struct addrinfo hints, *info, *p; char portstr[6]; int *sockfd = NULL; int nsock = 0; int i, ret; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; snprintf(portstr, 6, "%d", config.port); ret = getaddrinfo(NULL, portstr, &hints, &info); if (ret) { die("getaddrinfo failed: %s", gai_strerror(ret)); } for (p=info; p; p=p->ai_next) { int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP); int yes = 1; ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); #ifdef IPV6_V6ONLY // some systems don't support v4 access on v6 sockets, but some do. // since we need to account for two sockets we might as well // always. if (p->ai_family == AF_INET6) ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)); #endif if (!ret) ret = bind(fd, p->ai_addr, p->ai_addrlen); // one of the address families will fail on some systems that // report its availability. do not complain. if (ret) { debug(1, "Failed to bind to address %s\n", format_address(p->ai_addr)); continue; } debug(1, "Bound to address %s\n", format_address(p->ai_addr)); listen(fd, 5); nsock++; sockfd = realloc(sockfd, nsock*sizeof(int)); sockfd[nsock-1] = fd; } freeaddrinfo(info); if (!nsock) die("could not bind any listen sockets!"); int maxfd = -1; fd_set fds; FD_ZERO(&fds); for (i=0; i maxfd) maxfd = sockfd[i]; } mdns_register(); if (config.daemonise) { pid_t pid = fork(); if (pid) { printf("%d\n", pid); exit(0); } } printf("Listening for connections.\n"); int acceptfd; struct timeval tv; while (1) { tv.tv_sec = 300; tv.tv_usec = 0; for (i=0; iremote); debug(1, "new RTSP connection\n"); conn->fd = accept(acceptfd, (struct sockaddr *)&conn->remote, &slen); if (conn->fd < 0) { perror("failed to accept connection"); free(conn); } else { pthread_t rtsp_conversation_thread; ret = pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, conn); if (ret) die("Failed to create RTSP receiver thread!"); conn->thread = rtsp_conversation_thread; conn->running = 1; track_thread(conn); } } perror("select"); die("fell out of the RTSP select loop"); }