CFLAGS:=-g -Wall -Isrc/include/
LDFLAGS:=-lm
-LIB_OBJS=src/lib/alac/alac.o src/lib/crypto/aes.o src/lib/crypto/bigint.o src/lib/crypto/hmac.o src/lib/crypto/md5.o src/lib/crypto/rc4.o src/lib/crypto/sha1.o src/lib/sdp.o src/lib/raop_buffer.o src/lib/raop_rtp.o src/lib/http_response.o src/lib/http_request.o src/lib/http_parser.o src/lib/httpd.o src/lib/raop.o src/lib/rsakey.o src/lib/rsapem.o src/lib/dnssd.o src/lib/netutils.o src/lib/utils.o src/lib/base64.o src/lib/logger.o
+LIB_OBJS=src/lib/alac/alac.o src/lib/crypto/aes.o src/lib/crypto/bigint.o src/lib/crypto/hmac.o src/lib/crypto/md5.o src/lib/crypto/rc4.o src/lib/crypto/sha1.o src/lib/sdp.o src/lib/raop_buffer.o src/lib/raop_rtp.o src/lib/digest.o src/lib/http_response.o src/lib/http_request.o src/lib/http_parser.o src/lib/httpd.o src/lib/raop.o src/lib/rsakey.o src/lib/rsapem.o src/lib/dnssd.o src/lib/netutils.o src/lib/utils.o src/lib/base64.o src/lib/logger.o
all: example
DNSSD_API dnssd_t *dnssd_init(int *error);
-DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen);
+DNSSD_API int dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password);
DNSSD_API int dnssd_register_airplay(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen);
DNSSD_API void dnssd_unregister_raop(dnssd_t *dnssd);
RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks, const char *pemkey);
RAOP_API raop_t *raop_init_from_keyfile(raop_callbacks_t *callbacks, const char *keyfile);
-RAOP_API int raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen);
+RAOP_API int raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password);
RAOP_API void raop_stop(raop_t *raop);
RAOP_API void raop_destroy(raop_t *raop);
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "compat.h"
+#include "utils.h"
+#include "crypto/crypto.h"
+
+void
+digest_md5_to_hex(const unsigned char *md5buf, char *md5hex)
+{
+ int i;
+ for (i=0; i<MD5_SIZE*2; i++) {
+ int val = (i%2) ? md5buf[i/2]&0x0f : (md5buf[i/2]&0xf0)>>4;
+ md5hex[i] = (val<10) ? '0'+val : 'a'+(val-10);
+ }
+}
+
+void
+digest_get_response(const char *username, const char *realm,
+ const char *password, const char *nonce,
+ const char *method, const char *uri,
+ char *response)
+{
+ MD5_CTX md5ctx;
+ unsigned char md5buf[MD5_SIZE];
+ char md5hex[MD5_SIZE*2];
+
+ /* Calculate first inner MD5 hash */
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, (const unsigned char *)username, strlen(username));
+ MD5_Update(&md5ctx, (const unsigned char *)":", 1);
+ MD5_Update(&md5ctx, (const unsigned char *)realm, strlen(realm));
+ MD5_Update(&md5ctx, (const unsigned char *)":", 1);
+ MD5_Update(&md5ctx, (const unsigned char *)password, strlen(password));
+ MD5_Final(md5buf, &md5ctx);
+ digest_md5_to_hex(md5buf, md5hex);
+
+ /* Calculate second inner MD5 hash */
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, (const unsigned char *)method, strlen(method));
+ MD5_Update(&md5ctx, (const unsigned char *)":", 1);
+ MD5_Update(&md5ctx, (const unsigned char *)uri, strlen(uri));
+ MD5_Final(md5buf, &md5ctx);
+
+ /* Calculate outer MD5 hash */
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, (const unsigned char *)md5hex, sizeof(md5hex));
+ MD5_Update(&md5ctx, (const unsigned char *)":", 1);
+ MD5_Update(&md5ctx, (const unsigned char *)nonce, strlen(nonce));
+ MD5_Update(&md5ctx, (const unsigned char *)":", 1);
+ digest_md5_to_hex(md5buf, md5hex);
+ MD5_Update(&md5ctx, (const unsigned char *)md5hex, sizeof(md5hex));
+ MD5_Final(md5buf, &md5ctx);
+
+ /* Store the final result to response */
+ digest_md5_to_hex(md5buf, response);
+}
+
+void
+digest_generate_nonce(char *result, int resultlen)
+{
+ MD5_CTX md5ctx;
+ unsigned char md5buf[MD5_SIZE];
+ char md5hex[MD5_SIZE*2];
+ unsigned int time;
+
+ SYSTEM_GET_TIME(time);
+
+ MD5_Init(&md5ctx);
+ MD5_Update(&md5ctx, (unsigned char *)&time, sizeof(time));
+ MD5_Final(md5buf, &md5ctx);
+ digest_md5_to_hex(md5buf, md5hex);
+
+ strncpy(result, md5hex, resultlen-1);
+ result[resultlen-1] = '\0';
+}
+
+int
+digest_is_valid(const char *our_realm, const char *password,
+ const char *our_nonce, const char *method,
+ const char *authorization)
+{
+ char *auth;
+ char *current;
+ char *value;
+ int success;
+
+ /* Get values from authorization */
+ char *username = NULL;
+ char *realm = NULL;
+ char *nonce = NULL;
+ char *uri = NULL;
+ char *response = NULL;
+
+ /* Buffer for our response */
+ char our_response[MD5_SIZE*2+1];
+
+ if (!authorization) {
+ return 0;
+ }
+ current = auth = strdup(authorization);
+ if (!auth) {
+ return 0;
+ }
+
+ /* Check that the type is digest */
+ if (strncmp("Digest", current, 6)) {
+ free(auth);
+ return 0;
+ }
+ current += 6;
+
+ while ((value = utils_strsep(¤t, ",")) != NULL) {
+ char *first, *last;
+
+ /* Find first and last characters */
+ first = value;
+ last = value+strlen(value)-1;
+
+ /* Trim spaces from the value */
+ while (*first == ' ' && first < last) first++;
+ while (*last == ' ' && last > first) last--;
+
+ /* Validate the last character */
+ if (*last != '"') continue;
+ else *last = '\0';
+
+ /* Store value if it is relevant */
+ if (!strncmp("username=\"", first, 10))
+ username = first+10;
+ if (!strncmp("realm=\"", first, 7))
+ realm = first+7;
+ if (!strncmp("nonce=\"", first, 7))
+ nonce = first+7;
+ if (!strncmp("uri=\"", first, 5))
+ uri = first+5;
+ if (!strncmp("response=\"", first, 10))
+ response = first+10;
+ }
+
+ /* Calculate our response */
+ memset(our_response, 0, sizeof(our_response));
+ digest_get_response(username, realm, password, nonce,
+ method, uri, our_response);
+ success = !strcmp(response, our_response);
+ free(auth);
+
+ return success;
+}
+
+
--- /dev/null
+#ifndef DIGEST_H
+#define DIGEST_H
+
+void digest_generate_nonce(char *result, int resultlen);
+int digest_is_valid(const char *our_realm, const char *password,
+ const char *our_nonce, const char *method,
+ const char *authorization);
+
+#endif
}
int
-dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen)
+dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password)
{
TXTRecordRef txtRecord;
char servname[MAX_SERVNAME];
dnssd->TXTRecordSetValue(&txtRecord, "da", strlen(RAOP_DA), RAOP_DA);
dnssd->TXTRecordSetValue(&txtRecord, "sr", strlen(RAOP_SR), RAOP_SR);
dnssd->TXTRecordSetValue(&txtRecord, "ss", strlen(RAOP_SS), RAOP_SS);
- dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen(RAOP_PW), RAOP_PW);
+ if (password) {
+ dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen("true"), "true");
+ } else {
+ dnssd->TXTRecordSetValue(&txtRecord, "pw", strlen("false"), "false");
+ }
dnssd->TXTRecordSetValue(&txtRecord, "vn", strlen(RAOP_VN), RAOP_VN);
dnssd->TXTRecordSetValue(&txtRecord, "tp", strlen(RAOP_TP), RAOP_TP);
dnssd->TXTRecordSetValue(&txtRecord, "md", strlen(RAOP_MD), RAOP_MD);
}
int
-dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen)
+dnssd_register_raop(dnssd_t *dnssd, const char *name, unsigned short port, const char *hwaddr, int hwaddrlen, int password)
{
char hwaddrstr[MAX_SERVNAME];
NSString *serviceString;
[txtDict setValue:[NSString stringWithUTF8String:RAOP_DA] forKey:@"da"];
[txtDict setValue:[NSString stringWithUTF8String:RAOP_SR] forKey:@"sr"];
[txtDict setValue:[NSString stringWithUTF8String:RAOP_SS] forKey:@"ss"];
- [txtDict setValue:[NSString stringWithUTF8String:RAOP_PW] forKey:@"pw"];
+ if (password) {
+ [txtDict setValue:@"true" forKey:@"pw"];
+ } else {
+ [txtDict setValue:@"false" forKey:@"pw"];
+ }
[txtDict setValue:[NSString stringWithUTF8String:RAOP_VN] forKey:@"vn"];
[txtDict setValue:[NSString stringWithUTF8String:RAOP_TP] forKey:@"tp"];
[txtDict setValue:[NSString stringWithUTF8String:RAOP_MD] forKey:@"md"];
#define RAOP_DA "true"
#define RAOP_SR "44100"
#define RAOP_SS "16"
-#define RAOP_PW "false"
#define RAOP_VN "65537"
#define RAOP_TP "TCP,UDP"
#define RAOP_MD "0,1,2"
#include "raop.h"
#include "raop_rtp.h"
#include "rsakey.h"
+#include "digest.h"
#include "httpd.h"
#include "sdp.h"
/* Actually 345 bytes for 2048-bit key */
#define MAX_SIGNATURE_LEN 512
+/* Let's just decide on some length */
+#define MAX_PASSWORD_LEN 64
+
+/* MD5 as hex fits here */
+#define MAX_NONCE_LEN 33
+
struct raop_s {
/* Callbacks for audio */
raop_callbacks_t callbacks;
/* Hardware address information */
unsigned char hwaddr[MAX_HWADDR_LEN];
int hwaddrlen;
+
+ /* Password information */
+ char password[MAX_PASSWORD_LEN+1];
};
struct raop_conn_s {
unsigned char *remote;
int remotelen;
+
+ char nonce[MAX_NONCE_LEN+1];
};
typedef struct raop_conn_s raop_conn_t;
conn->locallen = locallen;
conn->remotelen = remotelen;
+
+ digest_generate_nonce(conn->nonce, sizeof(conn->nonce));
return conn;
}
const char *method;
const char *cseq;
const char *challenge;
+ int require_auth = 0;
method = http_request_get_method(request);
cseq = http_request_get_header(request, "CSeq");
}
res = http_response_init("RTSP/1.0", 200, "OK");
+ if (strlen(raop->password)) {
+ const char *authorization;
+
+ authorization = http_request_get_header(request, "Authorization");
+ if (authorization) {
+ logger_log(&conn->raop->logger, LOGGER_DEBUG, "Authorization: %s\n", authorization);
+ }
+ if (!digest_is_valid("AppleTV", raop->password, conn->nonce, method, authorization)) {
+ char *authstr;
+ int authstrlen;
+
+ /* Allocate the authenticate string */
+ authstrlen = sizeof("Digest realm=\"AppleTV\", nonce=\"\"") + sizeof(conn->nonce) + 1;
+ authstr = malloc(authstrlen);
+
+ /* Concatenate the authenticate string */
+ memset(authstr, 0, authstrlen);
+ strcat(authstr, "Digest realm=\"AppleTV\", nonce=\"");
+ strcat(authstr, conn->nonce);
+ strcat(authstr, "\"");
+
+ /* Construct a new response */
+ require_auth = 1;
+ http_response_destroy(res);
+ res = http_response_init("RTSP/1.0", 401, "Unauthorized");
+ http_response_add_header(res, "WWW-Authenticate", authstr);
+ free(authstr);
+ } else {
+ logger_log(&conn->raop->logger, LOGGER_DEBUG, "AUTHENTICATION SUCCESS!\n");
+ }
+ }
+
http_response_add_header(res, "CSeq", cseq);
http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog");
logger_log(&conn->raop->logger, LOGGER_DEBUG, "Got challenge: %s\n", challenge);
logger_log(&conn->raop->logger, LOGGER_DEBUG, "Got response: %s\n", signature);
}
- if (!strcmp(method, "OPTIONS")) {
+
+ if (require_auth) {
+ /* Do nothing in case of authentication request */
+ } else if (!strcmp(method, "OPTIONS")) {
http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
} else if (!strcmp(method, "ANNOUNCE")) {
const char *data;
}
int
-raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen)
+raop_start(raop_t *raop, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password)
{
assert(raop);
assert(port);
return -1;
}
+ /* Validate password */
+ if (strlen(password) > MAX_PASSWORD_LEN) {
+ return -1;
+ }
+
/* Copy hwaddr to the raop structure */
memcpy(raop->hwaddr, hwaddr, hwaddrlen);
raop->hwaddrlen = hwaddrlen;
+ /* Copy password to the raop structure */
+ strncpy(raop->password, password, MAX_PASSWORD_LEN);
+
return httpd_start(raop->httpd, port);
}
raop_cbs.audio_destroy = audio_destroy;
raop = raop_init_from_keyfile(&raop_cbs, "airport.key");
- raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr));
+ raop_start(raop, &raop_port, hwaddr, sizeof(hwaddr), "test");
dnssd = dnssd_init(NULL);
- dnssd_register_raop(dnssd, name, raop_port, hwaddr, sizeof(hwaddr));
+ dnssd_register_raop(dnssd, name, raop_port, hwaddr, sizeof(hwaddr), 1);
#ifndef WIN32
sleep(100);