2 * Copyright (C) 2011-2012 Juho Vähä-Herttua
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
33 /* Actually 345 bytes for 2048-bit key */
34 #define MAX_SIGNATURE_LEN 512
36 /* Let's just decide on some length */
37 #define MAX_PASSWORD_LEN 64
39 /* MD5 as hex fits here */
40 #define MAX_NONCE_LEN 32
43 /* Callbacks for audio */
44 raop_callbacks_t callbacks
;
49 /* HTTP daemon and RSA key */
53 /* Hardware address information */
54 unsigned char hwaddr
[MAX_HWADDR_LEN
];
57 /* Password information */
58 char password
[MAX_PASSWORD_LEN
+1];
68 unsigned char *remote
;
71 char nonce
[MAX_NONCE_LEN
+1];
73 typedef struct raop_conn_s raop_conn_t
;
76 conn_init(void *opaque
, unsigned char *local
, int locallen
, unsigned char *remote
, int remotelen
)
80 conn
= calloc(1, sizeof(raop_conn_t
));
85 conn
->raop_rtp
= NULL
;
88 logger_log(conn
->raop
->logger
, LOGGER_INFO
,
90 local
[0], local
[1], local
[2], local
[3]);
91 } else if (locallen
== 16) {
92 logger_log(conn
->raop
->logger
, LOGGER_INFO
,
93 "Local: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
94 local
[0], local
[1], local
[2], local
[3], local
[4], local
[5], local
[6], local
[7],
95 local
[8], local
[9], local
[10], local
[11], local
[12], local
[13], local
[14], local
[15]);
98 logger_log(conn
->raop
->logger
, LOGGER_INFO
,
99 "Remote: %d.%d.%d.%d",
100 remote
[0], remote
[1], remote
[2], remote
[3]);
101 } else if (remotelen
== 16) {
102 logger_log(conn
->raop
->logger
, LOGGER_INFO
,
103 "Remote: %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
104 remote
[0], remote
[1], remote
[2], remote
[3], remote
[4], remote
[5], remote
[6], remote
[7],
105 remote
[8], remote
[9], remote
[10], remote
[11], remote
[12], remote
[13], remote
[14], remote
[15]);
108 conn
->local
= malloc(locallen
);
110 memcpy(conn
->local
, local
, locallen
);
112 conn
->remote
= malloc(remotelen
);
113 assert(conn
->remote
);
114 memcpy(conn
->remote
, remote
, remotelen
);
116 conn
->locallen
= locallen
;
117 conn
->remotelen
= remotelen
;
119 digest_generate_nonce(conn
->nonce
, sizeof(conn
->nonce
));
124 conn_request(void *ptr
, http_request_t
*request
, http_response_t
**response
)
128 const char realm
[] = "airplay";
129 >>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf
130 raop_conn_t
*conn
= ptr
;
131 raop_t
*raop
= conn
->raop
;
133 http_response_t
*res
;
136 const char *challenge
;
137 int require_auth
= 0;
139 method
= http_request_get_method(request
);
140 cseq
= http_request_get_header(request
, "CSeq");
141 if (!method
|| !cseq
) {
145 res
= http_response_init("RTSP/1.0", 200, "OK");
147 if (strlen(raop
->password
)) {
150 /* We need authorization for everything else than OPTIONS request */
151 if (strcmp(method
, "OPTIONS") != 0 && strlen(raop
->password
)) {
152 >>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf
153 const char *authorization
;
155 authorization
= http_request_get_header(request
, "Authorization");
157 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Our nonce: %s", conn
->nonce
);
158 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Authorization: %s", authorization
);
161 if (!digest_is_valid("AppleTV", raop
->password
, conn
->nonce
, method
, http_request_get_url(request
), authorization
)) {
163 if (!digest_is_valid(realm
, raop
->password
, conn
->nonce
, method
, http_request_get_url(request
), authorization
)) {
164 >>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf
168 /* Allocate the authenticate string */
170 authstrlen
= sizeof("Digest realm=\"AppleTV\", nonce=\"\"") + sizeof(conn
->nonce
) + 1;
172 authstrlen
= sizeof("Digest realm=\"\", nonce=\"\"") + sizeof(realm
) + sizeof(conn
->nonce
) + 1;
173 >>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf
174 authstr
= malloc(authstrlen
);
176 /* Concatenate the authenticate string */
177 memset(authstr
, 0, authstrlen
);
179 strcat(authstr
, "Digest realm=\"AppleTV\", nonce=\"");
181 strcat(authstr
, "Digest realm=\"");
182 strcat(authstr
, realm
);
183 strcat(authstr
, "\", nonce=\"");
184 >>>>>>> 64d59e3087f829006d091fa0d114efb50972a2bf
185 strcat(authstr
, conn
->nonce
);
186 strcat(authstr
, "\"");
188 /* Construct a new response */
190 http_response_destroy(res
);
191 res
= http_response_init("RTSP/1.0", 401, "Unauthorized");
192 http_response_add_header(res
, "WWW-Authenticate", authstr
);
194 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Authentication unsuccessful, sending Unauthorized");
196 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Authentication successful!");
200 http_response_add_header(res
, "CSeq", cseq
);
201 http_response_add_header(res
, "Apple-Jack-Status", "connected; type=analog");
203 challenge
= http_request_get_header(request
, "Apple-Challenge");
204 if (!require_auth
&& challenge
) {
205 char signature
[MAX_SIGNATURE_LEN
];
207 memset(signature
, 0, sizeof(signature
));
208 rsakey_sign(raop
->rsakey
, signature
, sizeof(signature
), challenge
,
209 conn
->local
, conn
->locallen
, raop
->hwaddr
, raop
->hwaddrlen
);
210 http_response_add_header(res
, "Apple-Response", signature
);
212 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Got challenge: %s", challenge
);
213 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Got response: %s", signature
);
217 /* Do nothing in case of authentication request */
218 } else if (!strcmp(method
, "OPTIONS")) {
219 http_response_add_header(res
, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
220 } else if (!strcmp(method
, "ANNOUNCE")) {
224 unsigned char aeskey
[16];
225 unsigned char aesiv
[16];
226 int aeskeylen
, aesivlen
;
228 data
= http_request_get_data(request
, &datalen
);
231 const char *remotestr
, *rtpmapstr
, *fmtpstr
, *aeskeystr
, *aesivstr
;
233 sdp
= sdp_init(data
, datalen
);
234 remotestr
= sdp_get_connection(sdp
);
235 rtpmapstr
= sdp_get_rtpmap(sdp
);
236 fmtpstr
= sdp_get_fmtp(sdp
);
237 aeskeystr
= sdp_get_rsaaeskey(sdp
);
238 aesivstr
= sdp_get_aesiv(sdp
);
240 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "connection: %s", remotestr
);
241 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "rtpmap: %s", rtpmapstr
);
242 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "fmtp: %s", fmtpstr
);
243 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "rsaaeskey: %s", aeskeystr
);
244 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "aesiv: %s", aesivstr
);
246 aeskeylen
= rsakey_decrypt(raop
->rsakey
, aeskey
, sizeof(aeskey
), aeskeystr
);
247 aesivlen
= rsakey_parseiv(raop
->rsakey
, aesiv
, sizeof(aesiv
), aesivstr
);
248 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "aeskeylen: %d", aeskeylen
);
249 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "aesivlen: %d", aesivlen
);
251 if (conn
->raop_rtp
) {
252 /* This should never happen */
253 raop_rtp_destroy(conn
->raop_rtp
);
254 conn
->raop_rtp
= NULL
;
256 conn
->raop_rtp
= raop_rtp_init(raop
->logger
, &raop
->callbacks
, remotestr
, rtpmapstr
, fmtpstr
, aeskey
, aesiv
);
257 if (!conn
->raop_rtp
) {
258 logger_log(conn
->raop
->logger
, LOGGER_ERR
, "Error initializing the audio decoder");
259 http_response_set_disconnect(res
, 1);
263 } else if (!strcmp(method
, "SETUP")) {
264 unsigned short remote_cport
=0, remote_tport
=0;
265 unsigned short cport
=0, tport
=0, dport
=0;
266 const char *transport
;
270 transport
= http_request_get_header(request
, "Transport");
273 logger_log(conn
->raop
->logger
, LOGGER_INFO
, "Transport: %s", transport
);
274 use_udp
= strncmp(transport
, "RTP/AVP/TCP", 11);
276 char *original
, *current
, *tmpstr
;
278 current
= original
= strdup(transport
);
280 while ((tmpstr
= utils_strsep(¤t
, ";")) != NULL
) {
281 unsigned short value
;
284 ret
= sscanf(tmpstr
, "control_port=%hu", &value
);
286 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Found remote control port: %hu", value
);
287 remote_cport
= value
;
289 ret
= sscanf(tmpstr
, "timing_port=%hu", &value
);
291 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Found remote timing port: %hu", value
);
292 remote_tport
= value
;
298 if (conn
->raop_rtp
) {
299 raop_rtp_start(conn
->raop_rtp
, use_udp
, remote_cport
, remote_tport
, &cport
, &tport
, &dport
);
301 logger_log(conn
->raop
->logger
, LOGGER_ERR
, "RAOP not initialized at SETUP, playing will fail!");
302 http_response_set_disconnect(res
, 1);
305 memset(buffer
, 0, sizeof(buffer
));
307 snprintf(buffer
, sizeof(buffer
)-1,
308 "RTP/AVP/UDP;unicast;mode=record;timing_port=%hu;events;control_port=%hu;server_port=%hu",
309 tport
, cport
, dport
);
311 snprintf(buffer
, sizeof(buffer
)-1,
312 "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record;server_port=%u",
315 logger_log(conn
->raop
->logger
, LOGGER_INFO
, "Responding with %s", buffer
);
316 http_response_add_header(res
, "Transport", buffer
);
317 http_response_add_header(res
, "Session", "DEADBEEF");
318 } else if (!strcmp(method
, "SET_PARAMETER")) {
319 const char *content_type
;
323 content_type
= http_request_get_header(request
, "Content-Type");
324 data
= http_request_get_data(request
, &datalen
);
325 if (!strcmp(content_type
, "text/parameters")) {
327 datastr
= calloc(1, datalen
+1);
328 if (data
&& datastr
&& conn
->raop_rtp
) {
329 memcpy(datastr
, data
, datalen
);
330 if (!strncmp(datastr
, "volume: ", 8)) {
332 sscanf(datastr
+8, "%f", &vol
);
333 raop_rtp_set_volume(conn
->raop_rtp
, vol
);
335 } else if (!conn
->raop_rtp
) {
336 logger_log(conn
->raop
->logger
, LOGGER_WARNING
, "RAOP not initialized at SET_PARAMETER volume");
339 } else if (!strcmp(content_type
, "image/jpeg")) {
340 logger_log(conn
->raop
->logger
, LOGGER_INFO
, "Got image data of %d bytes", datalen
);
341 if (conn
->raop_rtp
) {
342 raop_rtp_set_coverart(conn
->raop_rtp
, data
, datalen
);
344 logger_log(conn
->raop
->logger
, LOGGER_WARNING
, "RAOP not initialized at SET_PARAMETER coverart");
346 } else if (!strcmp(content_type
, "application/x-dmap-tagged")) {
347 logger_log(conn
->raop
->logger
, LOGGER_INFO
, "Got metadata of %d bytes", datalen
);
348 if (conn
->raop_rtp
) {
349 raop_rtp_set_metadata(conn
->raop_rtp
, data
, datalen
);
351 logger_log(conn
->raop
->logger
, LOGGER_WARNING
, "RAOP not initialized at SET_PARAMETER metadata");
354 } else if (!strcmp(method
, "FLUSH")) {
358 rtpinfo
= http_request_get_header(request
, "RTP-Info");
360 logger_log(conn
->raop
->logger
, LOGGER_INFO
, "Flush with RTP-Info: %s", rtpinfo
);
361 if (!strncmp(rtpinfo
, "seq=", 4)) {
362 next_seq
= strtol(rtpinfo
+4, NULL
, 10);
365 if (conn
->raop_rtp
) {
366 raop_rtp_flush(conn
->raop_rtp
, next_seq
);
368 logger_log(conn
->raop
->logger
, LOGGER_WARNING
, "RAOP not initialized at FLUSH");
370 } else if (!strcmp(method
, "TEARDOWN")) {
371 http_response_add_header(res
, "Connection", "close");
372 if (conn
->raop_rtp
) {
373 /* Destroy our RTP session */
374 raop_rtp_stop(conn
->raop_rtp
);
375 raop_rtp_destroy(conn
->raop_rtp
);
376 conn
->raop_rtp
= NULL
;
379 http_response_finish(res
, NULL
, 0);
381 logger_log(conn
->raop
->logger
, LOGGER_DEBUG
, "Handled request %s with URL %s", method
, http_request_get_url(request
));
386 conn_destroy(void *ptr
)
388 raop_conn_t
*conn
= ptr
;
390 if (conn
->raop_rtp
) {
391 /* This is done in case TEARDOWN was not called */
392 raop_rtp_destroy(conn
->raop_rtp
);
400 raop_init(int max_clients
, raop_callbacks_t
*callbacks
, const char *pemkey
, int *error
)
405 httpd_callbacks_t httpd_cbs
;
408 assert(max_clients
> 0);
409 assert(max_clients
< 100);
412 /* Initialize the network */
413 if (netutils_init() < 0) {
417 /* Validate the callbacks structure */
418 if (!callbacks
->audio_init
||
419 !callbacks
->audio_process
||
420 !callbacks
->audio_destroy
) {
424 /* Allocate the raop_t structure */
425 raop
= calloc(1, sizeof(raop_t
));
430 /* Initialize the logger */
431 raop
->logger
= logger_init();
433 /* Set HTTP callbacks to our handlers */
434 memset(&httpd_cbs
, 0, sizeof(httpd_cbs
));
435 httpd_cbs
.opaque
= raop
;
436 httpd_cbs
.conn_init
= &conn_init
;
437 httpd_cbs
.conn_request
= &conn_request
;
438 httpd_cbs
.conn_destroy
= &conn_destroy
;
440 /* Initialize the http daemon */
441 httpd
= httpd_init(raop
->logger
, &httpd_cbs
, max_clients
);
447 /* Copy callbacks structure */
448 memcpy(&raop
->callbacks
, callbacks
, sizeof(raop_callbacks_t
));
450 /* Initialize RSA key handler */
451 rsakey
= rsakey_init_pem(pemkey
);
459 raop
->rsakey
= rsakey
;
465 raop_init_from_keyfile(int max_clients
, raop_callbacks_t
*callbacks
, const char *keyfile
, int *error
)
470 if (utils_read_file(&pemstr
, keyfile
) < 0) {
473 raop
= raop_init(max_clients
, callbacks
, pemstr
, error
);
479 raop_destroy(raop_t
*raop
)
484 httpd_destroy(raop
->httpd
);
485 rsakey_destroy(raop
->rsakey
);
486 logger_destroy(raop
->logger
);
489 /* Cleanup the network */
495 raop_is_running(raop_t
*raop
)
499 return httpd_is_running(raop
->httpd
);
503 raop_set_log_level(raop_t
*raop
, int level
)
507 logger_set_level(raop
->logger
, level
);
511 raop_set_log_callback(raop_t
*raop
, raop_log_callback_t callback
, void *cls
)
515 logger_set_callback(raop
->logger
, callback
, cls
);
519 raop_start(raop_t
*raop
, unsigned short *port
, const char *hwaddr
, int hwaddrlen
, const char *password
)
525 /* Validate hardware address */
526 if (hwaddrlen
> MAX_HWADDR_LEN
) {
530 memset(raop
->password
, 0, sizeof(raop
->password
));
532 /* Validate password */
533 if (strlen(password
) > MAX_PASSWORD_LEN
) {
537 /* Copy password to the raop structure */
538 strncpy(raop
->password
, password
, MAX_PASSWORD_LEN
);
541 /* Copy hwaddr to the raop structure */
542 memcpy(raop
->hwaddr
, hwaddr
, hwaddrlen
);
543 raop
->hwaddrlen
= hwaddrlen
;
545 return httpd_start(raop
->httpd
, port
);
549 raop_stop(raop_t
*raop
)
553 httpd_stop(raop
->httpd
);