aclocal.m4
stamp-h1
src/shairplay
+src/test/example
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
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
----------------
# Checks for programs.
AC_PROG_CC
+AC_LIBTOOL_WIN32_DLL
AC_PROG_LIBTOOL
# Checks for libraries.
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;
}
* 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>
/* 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) {
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;
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;
int
httpd_start(httpd_t *httpd, unsigned short *port)
{
+ /* How many connection attempts are kept in queue */
+ int backlog = 5;
+
assert(httpd);
assert(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);
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);
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;
}
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");
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, "\"");
#include "crypto/crypto.h"
#include "alac/alac.h"
-#define RAOP_BUFFER_LENGTH 16
+#define RAOP_BUFFER_LENGTH 32
typedef struct {
/* Packet available */
char apname[56];
char password[56];
unsigned short port;
+ char hwaddr[6];
char ao_driver[56];
char ao_devicename[56];
#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)
{
}
/* 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);
}
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")) {
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)) {
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");
int
main(int argc, char *argv[])
{
- const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 };
-
shairplay_options_t options;
ao_device *device = NULL;
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;
}
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);
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) {
+/*
+ * 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>
#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)
{
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;
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);