Merge branch 'master' of https://github.com/juhovh/shairplay into upstream
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 25 Nov 2014 23:43:23 +0000 (00:43 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 25 Nov 2014 23:43:23 +0000 (00:43 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
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

.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

index dbb3f51b8afaa66f81435f427eedddd967a24dfb..95089f257903562d13abd38e3938c35dc559400c 100644 (file)
@@ -26,4 +26,5 @@ autom4te.cache
 aclocal.m4
 stamp-h1
 src/shairplay
+src/test/example
 
index 66ec14004eb3ec93c48ecbf432771a25f598d60c..6b7b617871cdab08b43420973a6bb35127fdb6ac 100644 (file)
--- 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
 ----------------
 
index 94a1bbde808839fe2484c486a6d8c2668968afbf..fff796e1bc08889a7ebe1c65ca456dbd696c0be7 100644 (file)
@@ -10,6 +10,7 @@ AM_INIT_AUTOMAKE([foreign])
 
 # Checks for programs.
 AC_PROG_CC
+AC_LIBTOOL_WIN32_DLL
 AC_PROG_LIBTOOL
 
 # Checks for libraries.
index 78c916ea51dd03e94575fb53db00abc7bc4e69ee..58c857835d96ae36570cb5cba90a81153599f629 100644 (file)
@@ -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;
        }
 
index 813796262f944d812c9dba887c6f284a0d919540..4859e3239c7aa569109a7d27933d4c67508fbb29 100644 (file)
  *  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 <stdlib.h>
 #include <string.h>
 #include <stdio.h>
index 1d9e7e2eb454334efec4f6e7ba106a7f9c9104a7..7cb6fdea61d4f322778f3407ba9f056f6afc54b7 100644 (file)
@@ -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);
index 180d32eed7f6b510880a9a6b1fe4c75351536d74..3fc8838547aeb6857591eb95849f30737a5cd9c8 100644 (file)
@@ -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);
index e5c6539c0b60bb20af6337b9ef492aab3cf1aad0..e59254a81f87f1297deefde9762887316b516126 100644 (file)
@@ -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, "\"");
 
index 15f588c5be0b3e9e3e2a78db381485d07ef7a52e..866134604264c9c95b290c2f97014783a0fe94fb 100644 (file)
@@ -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 */
index 9bb9537fd75bdafaee512b0fefa1705815356fbf..4b873f127a4bf411cec0dbc03ff0c052faad5ccc 100644 (file)
@@ -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<slen; i++) {
+               if (str[i] == ':' && (i%3 == 2)) {
+                       continue;
+               }
+               if (str[i] >= '0' && str[i] <= '9') {
+                       continue;
+               }
+               if (str[i] >= 'a' && str[i] <= 'f') {
+                       continue;
+               }
+               return 1;
+       }
+       for (i=0; i<hwaddrlen; i++) {
+               hwaddr[i] = (char) strtol(str+(i*3), NULL, 16);
+       }
+       return 0;
+}
+
 static ao_device *
 audio_open_device(shairplay_options_t *opt, int bits, int channels, int samplerate)
 {
@@ -106,7 +134,6 @@ audio_open_device(shairplay_options_t *opt, int bits, int channels, int samplera
        }
 
        /* Add all available libao options */
-       ao_append_option(&ao_options, "client_name", opt->apname);
        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) {
index 393b7820efef6ea0dc7f5d1ee9a893dffe7a4817..e93f252cd9b70dd9ea2195d5cea0f081a8690ec3 100644 (file)
@@ -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 <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -9,6 +21,35 @@
 #include "dnssd.h"
 #include "raop.h"
 
+static int running;
+
+#ifndef WIN32
+
+#include <signal.h>
+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);