Commit | Line | Data |
---|---|---|
2340bcd3 JVH |
1 | #include <stdlib.h> |
2 | #include <stdio.h> | |
3 | #include <string.h> | |
4 | #include <assert.h> | |
5 | ||
6 | #include "raop.h" | |
7 | #include "raop_rtp.h" | |
8 | #include "rsakey.h" | |
9 | #include "httpd.h" | |
10 | #include "sdp.h" | |
11 | ||
12 | #include "global.h" | |
13 | #include "utils.h" | |
14 | #include "netutils.h" | |
15 | #include "logger.h" | |
16 | ||
17 | /* Actually 345 bytes for 2048-bit key */ | |
18 | #define MAX_SIGNATURE_LEN 512 | |
19 | ||
20 | struct raop_s { | |
21 | /* Callbacks for audio */ | |
22 | raop_callbacks_t callbacks; | |
23 | ||
24 | /* Logger instance */ | |
25 | logger_t logger; | |
26 | ||
27 | /* HTTP daemon and RSA key */ | |
28 | httpd_t *httpd; | |
29 | rsakey_t *rsakey; | |
30 | ||
31 | /* Hardware address information */ | |
32 | unsigned char hwaddr[MAX_HWADDR_LEN]; | |
33 | int hwaddrlen; | |
34 | }; | |
35 | ||
36 | struct raop_conn_s { | |
37 | raop_t *raop; | |
38 | raop_rtp_t *raop_rtp; | |
39 | ||
40 | unsigned char *local; | |
41 | int locallen; | |
42 | ||
43 | unsigned char *remote; | |
44 | int remotelen; | |
45 | }; | |
46 | typedef struct raop_conn_s raop_conn_t; | |
47 | ||
48 | static void * | |
49 | conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen) | |
50 | { | |
51 | raop_conn_t *conn; | |
52 | int i; | |
53 | ||
54 | conn = calloc(1, sizeof(raop_conn_t)); | |
55 | if (!conn) { | |
56 | return NULL; | |
57 | } | |
58 | conn->raop = opaque; | |
59 | conn->raop_rtp = NULL; | |
60 | ||
61 | logger_log(&conn->raop->logger, LOGGER_INFO, "Local: "); | |
62 | for (i=0; i<locallen; i++) { | |
63 | logger_log(&conn->raop->logger, LOGGER_INFO, "%02x", local[i]); | |
64 | } | |
65 | logger_log(&conn->raop->logger, LOGGER_INFO, "Remote: "); | |
66 | for (i=0; i<remotelen; i++) { | |
67 | logger_log(&conn->raop->logger, LOGGER_INFO, "%02x", remote[i]); | |
68 | } | |
69 | logger_log(&conn->raop->logger, LOGGER_INFO, "\n"); | |
70 | ||
71 | conn->local = malloc(locallen); | |
72 | assert(conn->local); | |
73 | memcpy(conn->local, local, locallen); | |
74 | ||
75 | conn->remote = malloc(remotelen); | |
76 | assert(conn->remote); | |
77 | memcpy(conn->remote, remote, remotelen); | |
78 | ||
79 | conn->locallen = locallen; | |
80 | conn->remotelen = remotelen; | |
81 | return conn; | |
82 | } | |
83 | ||
84 | static void | |
85 | conn_request(void *ptr, http_request_t *request, http_response_t **response) | |
86 | { | |
87 | raop_conn_t *conn = ptr; | |
88 | raop_t *raop = conn->raop; | |
89 | ||
90 | http_response_t *res; | |
91 | const char *method; | |
92 | const char *cseq; | |
93 | const char *challenge; | |
94 | ||
95 | method = http_request_get_method(request); | |
96 | cseq = http_request_get_header(request, "CSeq"); | |
97 | if (!method || !cseq) { | |
98 | return; | |
99 | } | |
100 | ||
101 | res = http_response_init("RTSP/1.0", 200, "OK"); | |
102 | http_response_add_header(res, "CSeq", cseq); | |
103 | http_response_add_header(res, "Apple-Jack-Status", "connected; type=analog"); | |
104 | ||
105 | challenge = http_request_get_header(request, "Apple-Challenge"); | |
106 | if (challenge) { | |
107 | char signature[MAX_SIGNATURE_LEN]; | |
108 | ||
109 | memset(signature, 0, sizeof(signature)); | |
110 | rsakey_sign(raop->rsakey, signature, sizeof(signature), challenge, | |
111 | conn->local, conn->locallen, raop->hwaddr, raop->hwaddrlen); | |
112 | logger_log(&conn->raop->logger, LOGGER_INFO, "Got signature: %s\n", signature); | |
113 | http_response_add_header(res, "Apple-Response", signature); | |
114 | } | |
115 | if (!strcmp(method, "OPTIONS")) { | |
116 | http_response_add_header(res, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER"); | |
117 | } else if (!strcmp(method, "ANNOUNCE")) { | |
118 | const char *data; | |
119 | int datalen; | |
120 | ||
121 | unsigned char aeskey[16]; | |
122 | unsigned char aesiv[16]; | |
123 | int aeskeylen, aesivlen; | |
124 | ||
125 | data = http_request_get_data(request, &datalen); | |
126 | if (data) { | |
127 | sdp_t *sdp = sdp_init(data, datalen); | |
128 | logger_log(&conn->raop->logger, LOGGER_INFO, "rsaaeskey: %s\n", sdp_get_rsaaeskey(sdp)); | |
129 | logger_log(&conn->raop->logger, LOGGER_INFO, "aesiv: %s\n", sdp_get_aesiv(sdp)); | |
130 | ||
131 | aeskeylen = rsakey_decrypt(raop->rsakey, aeskey, sizeof(aeskey), | |
132 | sdp_get_rsaaeskey(sdp)); | |
133 | aesivlen = rsakey_parseiv(raop->rsakey, aesiv, sizeof(aesiv), | |
134 | sdp_get_aesiv(sdp)); | |
135 | logger_log(&conn->raop->logger, LOGGER_INFO, "aeskeylen: %d\n", aeskeylen); | |
136 | logger_log(&conn->raop->logger, LOGGER_INFO, "aesivlen: %d\n", aesivlen); | |
137 | ||
138 | conn->raop_rtp = raop_rtp_init(&raop->logger, &raop->callbacks, sdp_get_fmtp(sdp), aeskey, aesiv); | |
139 | sdp_destroy(sdp); | |
140 | } | |
141 | } else if (!strcmp(method, "SETUP")) { | |
142 | unsigned short cport=0, tport=0, dport=0; | |
143 | const char *transport; | |
144 | char buffer[1024]; | |
145 | int use_udp; | |
146 | ||
147 | transport = http_request_get_header(request, "Transport"); | |
148 | assert(transport); | |
149 | ||
150 | logger_log(&conn->raop->logger, LOGGER_INFO, "Transport: %s\n", transport); | |
151 | use_udp = strncmp(transport, "RTP/AVP/TCP", 11); | |
152 | ||
153 | /* FIXME: Should use the parsed ports for resend */ | |
154 | raop_rtp_start(conn->raop_rtp, use_udp, 1234, 1234, &cport, &tport, &dport); | |
155 | logger_log(&conn->raop->logger, LOGGER_INFO, "cport %d tport %d dport %d\n", cport, tport, dport); | |
156 | ||
157 | memset(buffer, 0, sizeof(buffer)); | |
158 | if (use_udp) { | |
159 | snprintf(buffer, sizeof(buffer)-1, | |
160 | "RTP/AVP/UDP;unicast;mode=record;timing_port=%u;events;control_port=%u;server_port=%u", | |
161 | tport, cport, dport); | |
162 | } else { | |
163 | snprintf(buffer, sizeof(buffer)-1, | |
164 | "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record;server_port=%u", | |
165 | dport); | |
166 | } | |
167 | logger_log(&conn->raop->logger, LOGGER_INFO, "Responding with %s\n", buffer); | |
168 | http_response_add_header(res, "Transport", buffer); | |
169 | http_response_add_header(res, "Session", "DEADBEEF"); | |
170 | } else if (!strcmp(method, "SET_PARAMETER")) { | |
171 | const char *data; | |
172 | int datalen; | |
173 | char *datastr; | |
174 | ||
175 | data = http_request_get_data(request, &datalen); | |
176 | datastr = calloc(1, datalen+1); | |
177 | if (datastr) { | |
178 | memcpy(datastr, data, datalen); | |
179 | if (!strncmp(datastr, "volume: ", 8)) { | |
180 | float vol = 0.0; | |
181 | sscanf(data+8, "%f", &vol); | |
182 | raop_rtp_set_volume(conn->raop_rtp, vol); | |
183 | } | |
184 | } | |
185 | } else if (!strcmp(method, "FLUSH")) { | |
186 | const char *rtpinfo; | |
187 | int next_seq = -1; | |
188 | ||
189 | rtpinfo = http_request_get_header(request, "RTP-Info"); | |
190 | assert(rtpinfo); | |
191 | ||
192 | logger_log(&conn->raop->logger, LOGGER_INFO, "RTP-Info: %s\n", rtpinfo); | |
193 | if (!strncmp(rtpinfo, "seq=", 4)) { | |
194 | next_seq = strtol(rtpinfo+4, NULL, 10); | |
195 | } | |
196 | raop_rtp_flush(conn->raop_rtp, next_seq); | |
197 | } else if (!strcmp(method, "TEARDOWN")) { | |
198 | http_response_add_header(res, "Connection", "close"); | |
199 | raop_rtp_stop(conn->raop_rtp); | |
200 | raop_rtp_destroy(conn->raop_rtp); | |
201 | conn->raop_rtp = NULL; | |
202 | } | |
203 | http_response_finish(res, NULL, 0); | |
204 | ||
205 | logger_log(&conn->raop->logger, LOGGER_INFO, "Got request %s with URL %s\n", method, http_request_get_url(request)); | |
206 | *response = res; | |
207 | } | |
208 | ||
209 | static void | |
210 | conn_destroy(void *ptr) | |
211 | { | |
212 | raop_conn_t *conn = ptr; | |
213 | ||
214 | if (conn->raop_rtp) { | |
215 | raop_rtp_destroy(conn->raop_rtp); | |
216 | } | |
217 | free(conn->local); | |
218 | free(conn->remote); | |
219 | free(conn); | |
220 | } | |
221 | ||
222 | raop_t * | |
223 | raop_init(raop_callbacks_t *callbacks, const char *pemkey, const char *hwaddr, int hwaddrlen) | |
224 | { | |
225 | raop_t *raop; | |
226 | httpd_t *httpd; | |
227 | rsakey_t *rsakey; | |
228 | httpd_callbacks_t httpd_cbs; | |
229 | ||
230 | assert(callbacks); | |
231 | assert(pemkey); | |
232 | assert(hwaddr); | |
233 | ||
234 | /* Initialize the network */ | |
235 | if (netutils_init() < 0) { | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | /* Validate the callbacks structure */ | |
240 | if (!callbacks->audio_init || !callbacks->audio_set_volume || | |
241 | !callbacks->audio_process || !callbacks->audio_flush || | |
242 | !callbacks->audio_destroy) { | |
243 | return NULL; | |
244 | } | |
245 | ||
246 | /* Validate hardware address */ | |
247 | if (hwaddrlen > MAX_HWADDR_LEN) { | |
248 | return NULL; | |
249 | } | |
250 | ||
251 | /* Allocate the raop_t structure */ | |
252 | raop = calloc(1, sizeof(raop_t)); | |
253 | if (!raop) { | |
254 | return NULL; | |
255 | } | |
256 | ||
257 | /* Initialize the logger */ | |
258 | logger_init(&raop->logger); | |
259 | ||
260 | /* Set HTTP callbacks to our handlers */ | |
261 | memset(&httpd_cbs, 0, sizeof(httpd_cbs)); | |
262 | httpd_cbs.opaque = raop; | |
263 | httpd_cbs.conn_init = &conn_init; | |
264 | httpd_cbs.conn_request = &conn_request; | |
265 | httpd_cbs.conn_destroy = &conn_destroy; | |
266 | ||
267 | /* Initialize the http daemon */ | |
268 | httpd = httpd_init(&raop->logger, &httpd_cbs, 10, 1); | |
269 | if (!httpd) { | |
270 | free(raop); | |
271 | return NULL; | |
272 | } | |
273 | ||
274 | /* Copy callbacks structure */ | |
275 | memcpy(&raop->callbacks, callbacks, sizeof(raop_callbacks_t)); | |
276 | ||
277 | /* Initialize RSA key handler */ | |
278 | rsakey = rsakey_init_pem(pemkey); | |
279 | if (!rsakey) { | |
280 | free(httpd); | |
281 | free(raop); | |
282 | return NULL; | |
283 | } | |
284 | ||
285 | raop->httpd = httpd; | |
286 | raop->rsakey = rsakey; | |
287 | ||
288 | /* Copy hwaddr to resulting structure */ | |
289 | memcpy(raop->hwaddr, hwaddr, hwaddrlen); | |
290 | raop->hwaddrlen = hwaddrlen; | |
291 | ||
292 | return raop; | |
293 | } | |
294 | ||
295 | raop_t * | |
296 | raop_init_from_keyfile(raop_callbacks_t *callbacks, const char *keyfile, const char *hwaddr, int hwaddrlen) | |
297 | { | |
298 | raop_t *raop; | |
299 | char *pemstr; | |
300 | ||
301 | if (utils_read_file(&pemstr, keyfile) < 0) { | |
302 | return NULL; | |
303 | } | |
304 | raop = raop_init(callbacks, pemstr, hwaddr, hwaddrlen); | |
305 | free(pemstr); | |
306 | return raop; | |
307 | } | |
308 | ||
309 | void | |
310 | raop_destroy(raop_t *raop) | |
311 | { | |
312 | if (raop) { | |
313 | raop_stop(raop); | |
314 | ||
315 | httpd_destroy(raop->httpd); | |
316 | rsakey_destroy(raop->rsakey); | |
317 | free(raop); | |
318 | ||
319 | /* Cleanup the network */ | |
320 | netutils_cleanup(); | |
321 | } | |
322 | } | |
323 | ||
324 | int | |
325 | raop_start(raop_t *raop, unsigned short *port) | |
326 | { | |
327 | assert(raop); | |
328 | assert(port); | |
329 | ||
330 | return httpd_start(raop->httpd, port); | |
331 | } | |
332 | ||
333 | void | |
334 | raop_stop(raop_t *raop) | |
335 | { | |
336 | assert(raop); | |
337 | ||
338 | httpd_stop(raop->httpd); | |
339 | } | |
340 |