From: Jérôme Benoit Date: Tue, 25 Nov 2014 23:43:23 +0000 (+0100) Subject: Merge branch 'master' of https://github.com/juhovh/shairplay into upstream X-Git-Tag: upstream/0.9.0~4 X-Git-Url: https://git.piment-noir.org/?p=deb_shairplay.git;a=commitdiff_plain;h=dd28c44f7f25627039b067c2d4f4b40e6a228ab4;hp=00bd24db775c9db500834677db929cd319621f0d Merge branch 'master' of https://github.com/juhovh/shairplay into upstream Signed-off-by: Jérôme Benoit Conflicts: .gitignore README.md configure.ac src/lib/digest.c src/lib/dnssd.c src/lib/httpd.c src/lib/netutils.c src/lib/raop.c src/lib/raop_buffer.c src/shairplay.c src/test/example.c --- diff --git a/.gitignore b/.gitignore index dbb3f51..95089f2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ autom4te.cache aclocal.m4 stamp-h1 src/shairplay +src/test/example diff --git a/README.md b/README.md index 66ec140..6b7b617 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,16 @@ all local laws are adhered to. Installation ------------ +First you need to install some dependencies, for example on Ubuntu you would +write: ``` +sudo apt-get install autoconf automake libtool +sudo apt-get install libltdl-dev libao-dev libavahi-compat-libdnssd-dev +sudo apt-get install avahi-daemon +``` + +``` +./autogen.sh ./configure make sudo make install @@ -44,6 +53,10 @@ Start the server with ```shairplay```, if you are connected to a Wi-Fi the server should show as an AirPort Express on your iOS devices and Mac OS X computers in the same network. +Notice that you need to have the airport.key file in your working directory when +starting the shairplay service. It is not included in the binary for possible +legal reasons. + Related software ---------------- diff --git a/configure.ac b/configure.ac index 94a1bbd..fff796e 100644 --- a/configure.ac +++ b/configure.ac @@ -10,6 +10,7 @@ AM_INIT_AUTOMAKE([foreign]) # Checks for programs. AC_PROG_CC +AC_LIBTOOL_WIN32_DLL AC_PROG_LIBTOOL # Checks for libraries. diff --git a/src/lib/digest.c b/src/lib/digest.c index 78c916e..58c8578 100644 --- a/src/lib/digest.c +++ b/src/lib/digest.c @@ -128,22 +128,25 @@ digest_is_valid(const char *our_realm, const char *password, else *last = '\0'; /* Store value if it is relevant */ - if (!strncmp("username=\"", first, 10)) + if (!strncmp("username=\"", first, 10)) { username = first+10; - if (!strncmp("realm=\"", first, 7)) + } else if (!strncmp("realm=\"", first, 7)) { realm = first+7; - if (!strncmp("nonce=\"", first, 7)) + } else if (!strncmp("nonce=\"", first, 7)) { nonce = first+7; - if (!strncmp("uri=\"", first, 5)) + } else if (!strncmp("uri=\"", first, 5)) { uri = first+5; - if (!strncmp("response=\"", first, 10)) + } else if (!strncmp("response=\"", first, 10)) { response = first+10; + } } if (!username || !realm || !nonce || !uri || !response) { + free(auth); return 0; } if (strcmp(realm, our_realm) || strcmp(nonce, our_nonce) || strcmp(uri, our_uri)) { + free(auth); return 0; } diff --git a/src/lib/dnssd.c b/src/lib/dnssd.c index 8137962..4859e32 100644 --- a/src/lib/dnssd.c +++ b/src/lib/dnssd.c @@ -12,6 +12,14 @@ * Lesser General Public License for more details. */ +/* These defines allow us to compile on iOS */ +#ifndef __has_feature +# define __has_feature(x) 0 +#endif +#ifndef __has_extension +# define __has_extension __has_feature +#endif + #include #include #include diff --git a/src/lib/httpd.c b/src/lib/httpd.c index 1d9e7e2..7cb6fde 100644 --- a/src/lib/httpd.c +++ b/src/lib/httpd.c @@ -198,8 +198,12 @@ httpd_thread(void *arg) /* Get the correct nfds value and set rfds */ FD_ZERO(&rfds); if (httpd->open_connections < httpd->max_connections) { - FD_SET(httpd->server_fd4, &rfds); - nfds = httpd->server_fd4+1; + if (httpd->server_fd4 != -1) { + FD_SET(httpd->server_fd4, &rfds); + if (nfds <= httpd->server_fd4) { + nfds = httpd->server_fd4+1; + } + } if (httpd->server_fd6 != -1) { FD_SET(httpd->server_fd6, &rfds); if (nfds <= httpd->server_fd6) { @@ -229,7 +233,8 @@ httpd_thread(void *arg) break; } - if (FD_ISSET(httpd->server_fd4, &rfds)) { + if (httpd->open_connections < httpd->max_connections && + httpd->server_fd4 != -1 && FD_ISSET(httpd->server_fd4, &rfds)) { ret = httpd_accept_connection(httpd, httpd->server_fd4, 0); if (ret == -1) { break; @@ -237,7 +242,8 @@ httpd_thread(void *arg) continue; } } - if (httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) { + if (httpd->open_connections < httpd->max_connections && + httpd->server_fd6 != -1 && FD_ISSET(httpd->server_fd6, &rfds)) { ret = httpd_accept_connection(httpd, httpd->server_fd6, 1); if (ret == -1) { break; @@ -348,6 +354,9 @@ httpd_thread(void *arg) int httpd_start(httpd_t *httpd, unsigned short *port) { + /* How many connection attempts are kept in queue */ + int backlog = 5; + assert(httpd); assert(port); @@ -369,14 +378,14 @@ httpd_start(httpd_t *httpd, unsigned short *port) logger_log(httpd->logger, LOGGER_WARNING, "Continuing without IPv6 support"); } - if (listen(httpd->server_fd4, 5) == -1) { + if (httpd->server_fd4 != -1 && listen(httpd->server_fd4, backlog) == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv4 socket"); closesocket(httpd->server_fd4); closesocket(httpd->server_fd6); MUTEX_UNLOCK(httpd->run_mutex); return -2; } - if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, 5) == -1) { + if (httpd->server_fd6 != -1 && listen(httpd->server_fd6, backlog) == -1) { logger_log(httpd->logger, LOGGER_ERR, "Error listening to IPv6 socket"); closesocket(httpd->server_fd4); closesocket(httpd->server_fd6); diff --git a/src/lib/netutils.c b/src/lib/netutils.c index 180d32e..3fc8838 100644 --- a/src/lib/netutils.c +++ b/src/lib/netutils.c @@ -78,9 +78,11 @@ netutils_init_socket(unsigned short *port, int use_ipv6, int use_udp) sin6ptr->sin6_addr = in6addr_any; sin6ptr->sin6_port = htons(*port); +#ifndef WIN32 /* Make sure we only listen to IPv6 addresses */ setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &v6only, sizeof(v6only)); +#endif socklen = sizeof(*sin6ptr); ret = bind(server_fd, (struct sockaddr *)sin6ptr, socklen); diff --git a/src/lib/raop.c b/src/lib/raop.c index e5c6539..e59254a 100644 --- a/src/lib/raop.c +++ b/src/lib/raop.c @@ -123,6 +123,10 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot static void conn_request(void *ptr, http_request_t *request, http_response_t **response) { +<<<<<<< HEAD +======= + const char realm[] = "airplay"; +>>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf raop_conn_t *conn = ptr; raop_t *raop = conn->raop; @@ -139,7 +143,13 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) } res = http_response_init("RTSP/1.0", 200, "OK"); +<<<<<<< HEAD if (strlen(raop->password)) { +======= + + /* We need authorization for everything else than OPTIONS request */ + if (strcmp(method, "OPTIONS") != 0 && strlen(raop->password)) { +>>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf const char *authorization; authorization = http_request_get_header(request, "Authorization"); @@ -147,17 +157,31 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) logger_log(conn->raop->logger, LOGGER_DEBUG, "Our nonce: %s", conn->nonce); logger_log(conn->raop->logger, LOGGER_DEBUG, "Authorization: %s", authorization); } +<<<<<<< HEAD if (!digest_is_valid("AppleTV", raop->password, conn->nonce, method, http_request_get_url(request), authorization)) { +======= + if (!digest_is_valid(realm, raop->password, conn->nonce, method, http_request_get_url(request), authorization)) { +>>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf char *authstr; int authstrlen; /* Allocate the authenticate string */ +<<<<<<< HEAD authstrlen = sizeof("Digest realm=\"AppleTV\", nonce=\"\"") + sizeof(conn->nonce) + 1; +======= + authstrlen = sizeof("Digest realm=\"\", nonce=\"\"") + sizeof(realm) + sizeof(conn->nonce) + 1; +>>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf authstr = malloc(authstrlen); /* Concatenate the authenticate string */ memset(authstr, 0, authstrlen); +<<<<<<< HEAD strcat(authstr, "Digest realm=\"AppleTV\", nonce=\""); +======= + strcat(authstr, "Digest realm=\""); + strcat(authstr, realm); + strcat(authstr, "\", nonce=\""); +>>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf strcat(authstr, conn->nonce); strcat(authstr, "\""); diff --git a/src/lib/raop_buffer.c b/src/lib/raop_buffer.c index 15f588c..8661346 100644 --- a/src/lib/raop_buffer.c +++ b/src/lib/raop_buffer.c @@ -25,7 +25,7 @@ #include "crypto/crypto.h" #include "alac/alac.h" -#define RAOP_BUFFER_LENGTH 16 +#define RAOP_BUFFER_LENGTH 32 typedef struct { /* Packet available */ diff --git a/src/shairplay.c b/src/shairplay.c index 9bb9537..4b873f1 100644 --- a/src/shairplay.c +++ b/src/shairplay.c @@ -43,6 +43,7 @@ typedef struct { char apname[56]; char password[56]; unsigned short port; + char hwaddr[6]; char ao_driver[56]; char ao_devicename[56]; @@ -90,6 +91,33 @@ init_signals(void) #endif +static int +parse_hwaddr(const char *str, char *hwaddr, int hwaddrlen) +{ + int slen, i; + + slen = 3*hwaddrlen-1; + if (strlen(str) != slen) { + return 1; + } + for (i=0; i= '0' && str[i] <= '9') { + continue; + } + if (str[i] >= 'a' && str[i] <= 'f') { + continue; + } + return 1; + } + for (i=0; iapname); if (strlen(opt->ao_devicename)) { ao_append_option(&ao_options, "dev", opt->ao_devicename); } @@ -223,12 +250,15 @@ audio_set_volume(void *cls, void *opaque, float volume) static int parse_options(shairplay_options_t *opt, int argc, char *argv[]) { + const char default_hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + char *path = argv[0]; char *arg; /* Set default values for apname and port */ strncpy(opt->apname, "Shairplay", sizeof(opt->apname)-1); opt->port = 5000; + memcpy(opt->hwaddr, default_hwaddr, sizeof(opt->hwaddr)); while ((arg = *++argv)) { if (!strcmp(arg, "-a")) { @@ -243,6 +273,12 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) opt->port = atoi(*++argv); } else if (!strncmp(arg, "--server_port=", 14)) { opt->port = atoi(arg+14); + } else if (!strncmp(arg, "--hwaddr=", 9)) { + if (parse_hwaddr(arg+9, opt->hwaddr, sizeof(opt->hwaddr))) { + fprintf(stderr, "Invalid format given for hwaddr, aborting...\n"); + fprintf(stderr, "Please use hwaddr format: 01:45:89:ab:cd:ef\n"); + return 1; + } } else if (!strncmp(arg, "--ao_driver=", 12)) { strncpy(opt->ao_driver, arg+12, sizeof(opt->ao_driver)-1); } else if (!strncmp(arg, "--ao_devicename=", 16)) { @@ -256,6 +292,7 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) fprintf(stderr, " -a, --apname=AirPort Sets Airport name\n"); fprintf(stderr, " -p, --password=secret Sets password\n"); fprintf(stderr, " -o, --server_port=5000 Sets port for RAOP service\n"); + fprintf(stderr, " --hwaddr=address Sets the MAC address, useful if running multiple instances\n"); fprintf(stderr, " --ao_driver=driver Sets the ao driver (optional)\n"); fprintf(stderr, " --ao_devicename=devicename Sets the ao device name (optional)\n"); fprintf(stderr, " --ao_deviceid=id Sets the ao device id (optional)\n"); @@ -271,8 +308,6 @@ parse_options(shairplay_options_t *opt, int argc, char *argv[]) int main(int argc, char *argv[]) { - const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; - shairplay_options_t options; ao_device *device = NULL; @@ -314,6 +349,7 @@ main(int argc, char *argv[]) raop = raop_init_from_keyfile(10, &raop_cbs, "airport.key", NULL); if (raop == NULL) { fprintf(stderr, "Could not initialize the RAOP service\n"); + fprintf(stderr, "Please make sure the airport.key file is in the current directory.\n"); return -1; } @@ -321,7 +357,7 @@ main(int argc, char *argv[]) password = options.password; } raop_set_log_level(raop, RAOP_LOG_DEBUG); - raop_start(raop, &options.port, hwaddr, sizeof(hwaddr), password); + raop_start(raop, &options.port, options.hwaddr, sizeof(options.hwaddr), password); error = 0; dnssd = dnssd_init(&error); @@ -335,7 +371,7 @@ main(int argc, char *argv[]) return -1; } - dnssd_register_raop(dnssd, options.apname, options.port, hwaddr, sizeof(hwaddr), 0); + dnssd_register_raop(dnssd, options.apname, options.port, options.hwaddr, sizeof(options.hwaddr), 0); running = 1; while (running) { diff --git a/src/test/example.c b/src/test/example.c index 393b782..e93f252 100644 --- a/src/test/example.c +++ b/src/test/example.c @@ -1,3 +1,15 @@ +/* + * Starts the AirPlay server (name "FakePort") and dumps the received contents + * to files: + * - audio.pcm : decoded audio content + * - metadata.bin : meta data + * - covertart.jpg : cover art + * + * Requires avahi-daemon to run. Also requires file "airplay.key" in the same directory. + * + * Compile with: gcc -o example -g -I../../include/shairplay example.c -lshairplay + */ + #include #include #include @@ -9,6 +21,35 @@ #include "dnssd.h" #include "raop.h" +static int running; + +#ifndef WIN32 + +#include +static void +signal_handler(int sig) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + running = 0; + break; + } +} +static void +init_signals(void) +{ + struct sigaction sigact; + + sigact.sa_handler = signal_handler; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); +} + +#endif + static void * audio_init(void *cls, int bits, int channels, int samplerate) { @@ -75,14 +116,16 @@ raop_log_callback(void *cls, int level, const char *msg) int main(int argc, char *argv[]) { - const char *name = "AppleTV"; - unsigned short raop_port = 5000; - const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + const char *name = "FakePort"; + unsigned short raop_port = 5000; + const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; dnssd_t *dnssd; raop_t *raop; raop_callbacks_t raop_cbs; + int error; + raop_cbs.cls = NULL; raop_cbs.audio_init = audio_init; raop_cbs.audio_set_volume = audio_set_volume; @@ -93,18 +136,39 @@ main(int argc, char *argv[]) raop_cbs.audio_destroy = audio_destroy; raop = raop_init_from_keyfile(10, &raop_cbs, "airport.key", NULL); + if (raop == NULL) { + fprintf(stderr, "Could not initialize the RAOP service (airport.key missing?)\n"); + return -1; + } + raop_set_log_level(raop, RAOP_LOG_DEBUG); raop_set_log_callback(raop, &raop_log_callback, NULL); - raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr), "test"); + raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr), NULL); + + error = 0; + dnssd = dnssd_init(&error); + if (error) { + fprintf(stderr, "ERROR: Could not initialize dnssd library!\n"); + fprintf(stderr, "------------------------------------------\n"); + fprintf(stderr, "You could try the following resolutions based on your OS:\n"); + fprintf(stderr, "Windows: Try installing http://support.apple.com/kb/DL999\n"); + fprintf(stderr, "Debian/Ubuntu: Try installing libavahi-compat-libdnssd-dev package\n"); + raop_destroy(raop); + return -1; + } - dnssd = dnssd_init(NULL); dnssd_register_raop(dnssd, name, raop_port, hwaddr, sizeof(hwaddr), 1); + printf("Startup complete... Kill with Ctrl+C\n"); + + running = 1; + while (running != 0) { #ifndef WIN32 - sleep(100); + sleep(1); #else - Sleep(100*1000); + Sleep(1000); #endif + } dnssd_unregister_raop(dnssd); dnssd_destroy(dnssd);