1 /* -*- c-basic-offset: 8; -*- */
2 /* proto_http.c: Implementation of protocol HTTP.
3 *
4 * Copyright (C) 2002-2004 the Icecast team <team@icecast.org>,
5 * Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the Free
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 * $Id$
22 */
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #ifdef HAVE_STRINGS_H
32 # include <strings.h>
33 #endif
34
35 #include <shout/shout.h>
36 #include "shout_private.h"
37 #include "common/httpp/httpp.h"
38
39 typedef enum {
40 STATE_CHALLENGE = 0,
41 STATE_SOURCE,
42 STATE_UPGRADE,
43 STATE_POKE
44 } shout_http_protocol_state_t;
45
shout_http_basic_authorization(shout_t * self)46 static char *shout_http_basic_authorization(shout_t *self)
47 {
48 char *out, *in;
49 int len;
50
51 if (!self || !self->user || !self->password)
52 return NULL;
53
54 len = strlen(self->user) + strlen(self->password) + 2;
55 if (!(in = malloc(len)))
56 return NULL;
57 snprintf(in, len, "%s:%s", self->user, self->password);
58 out = _shout_util_base64_encode(in);
59 free(in);
60
61 len = strlen(out) + 24;
62 if (!(in = malloc(len))) {
63 free(out);
64 return NULL;
65 }
66 snprintf(in, len, "Authorization: Basic %s\r\n", out);
67 free(out);
68
69 return in;
70 }
71
shout_parse_http_select_next_state(shout_t * self,shout_connection_t * connection,int can_reuse,shout_http_protocol_state_t state)72 static shout_connection_return_state_t shout_parse_http_select_next_state(shout_t *self, shout_connection_t *connection, int can_reuse, shout_http_protocol_state_t state)
73 {
74 if (!can_reuse) {
75 shout_connection_disconnect(connection);
76 shout_connection_connect(connection, self);
77 }
78 connection->current_message_state = SHOUT_MSGSTATE_CREATING0;
79 connection->target_message_state = SHOUT_MSGSTATE_SENDING1;
80 connection->current_protocol_state = state;
81 return SHOUT_RS_NOTNOW;
82 }
83
shout_create_http_request_source(shout_t * self,shout_connection_t * connection,int auth,int poke)84 static shout_connection_return_state_t shout_create_http_request_source(shout_t *self, shout_connection_t *connection, int auth, int poke)
85 {
86 char *basic_auth;
87 char *ai;
88 int ret = SHOUTERR_MALLOC;
89 util_dict *dict;
90 const char *key, *val;
91 const char *mimetype;
92 char *mount = NULL;
93
94 mimetype = shout_get_mimetype_from_self(self);
95 if (!mimetype) {
96 shout_connection_set_error(connection, SHOUTERR_INSANE);
97 return SHOUT_RS_ERROR;
98 }
99
100 /* this is lazy code that relies on the only error from queue_* being
101 * SHOUTERR_MALLOC
102 */
103 do {
104 if (!(mount = _shout_util_url_encode_resource(self->mount)))
105 break;
106 if (connection->server_caps & LIBSHOUT_CAP_PUT) {
107 if (shout_queue_printf(connection, "PUT %s HTTP/1.1\r\n", mount))
108 break;
109 } else {
110 if (shout_queue_printf(connection, "SOURCE %s HTTP/1.0\r\n", mount))
111 break;
112 }
113 if (self->password && auth) {
114 if (! (basic_auth = shout_http_basic_authorization(self)))
115 break;
116 if (shout_queue_str(connection, basic_auth)) {
117 free(basic_auth);
118 break;
119 }
120 free(basic_auth);
121 }
122 if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port))
123 break;
124 if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent))
125 break;
126 if (shout_queue_printf(connection, "Content-Type: %s\r\n", mimetype))
127 break;
128 if (self->content_language && shout_queue_printf(connection, "Content-Language: %s\r\n", self->content_language))
129 break;
130 if (poke) {
131 if (shout_queue_str(connection, "Content-Length: 0\r\nConnection: Keep-Alive\r\n"))
132 break;
133 } else if (connection->server_caps & LIBSHOUT_CAP_PUT) {
134 if (shout_queue_printf(connection, "Expect: 100-continue\r\n", mount))
135 break;
136 /* Set timeout for 100-continue to 4s = 4000 ms. This is a little less than the default source_timeout/2. */
137 shout_connection_set_wait_timeout(connection, self, 4000 /* [ms] */);
138 }
139 if (shout_queue_printf(connection, "ice-public: %d\r\n", self->public))
140 break;
141
142 _SHOUT_DICT_FOREACH(self->meta, dict, key, val) {
143 if (val && shout_queue_printf(connection, "ice-%s: %s\r\n", key, val))
144 break;
145 }
146
147 if ((ai = _shout_util_dict_urlencode(self->audio_info, ';'))) {
148 if (shout_queue_printf(connection, "ice-audio-info: %s\r\n", ai)) {
149 free(ai);
150 break;
151 }
152 free(ai);
153 }
154 if (shout_queue_str(connection, "Prefer: return=minimal\r\n"))
155 break;
156 if (shout_queue_str(connection, "\r\n"))
157 break;
158
159 ret = SHOUTERR_SUCCESS;
160 } while (0);
161
162 if (mount)
163 free(mount);
164
165 shout_connection_set_error(connection, ret);
166 return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR;
167 }
168
shout_create_http_request_generic(shout_t * self,shout_connection_t * connection,const char * method,const char * res,const char * param,int fake_ua,const char * upgrade,int auth)169 static shout_connection_return_state_t shout_create_http_request_generic(shout_t *self, shout_connection_t *connection, const char *method, const char *res, const char *param, int fake_ua, const char *upgrade, int auth)
170 {
171 int ret = SHOUTERR_MALLOC;
172 int is_post = 0;
173 char *basic_auth;
174
175 if (method) {
176 is_post = strcmp(method, "POST") == 0;
177 } else {
178 if (connection->server_caps & LIBSHOUT_CAP_POST) {
179 method = "POST";
180 is_post = 1;
181 } else {
182 method = "GET";
183 is_post = 0;
184 }
185 }
186
187 /* this is lazy code that relies on the only error from queue_* being
188 * SHOUTERR_MALLOC
189 */
190 do {
191 ret = SHOUTERR_SUCCESS;
192
193 if (!param || is_post) {
194 if (shout_queue_printf(connection, "%s %s HTTP/1.1\r\n", method, res))
195 break;
196 } else {
197 if (shout_queue_printf(connection, "%s %s?%s HTTP/1.1\r\n", method, res, param))
198 break;
199 }
200
201 /* Send Host:-header as this one may be used to select cert! */
202 if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port))
203 break;
204
205 if (fake_ua) {
206 /* Thank you Nullsoft for your broken software. */
207 if (self->useragent && shout_queue_printf(connection, "User-Agent: %s (Mozilla compatible)\r\n", self->useragent))
208 break;
209 } else {
210 if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent))
211 break;
212 }
213
214 if (self->password && auth) {
215 if (! (basic_auth = shout_http_basic_authorization(self)))
216 break;
217 if (shout_queue_str(connection, basic_auth)) {
218 free(basic_auth);
219 break;
220 }
221 free(basic_auth);
222 }
223
224 if (upgrade) {
225 if (shout_queue_printf(connection, "Connection: Upgrade\r\nUpgrade: %s\r\n", upgrade))
226 break;
227 }
228
229 if (param && is_post) {
230 if (shout_queue_printf(connection, "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %llu\r\n", (long long unsigned int)strlen(param)))
231 break;
232 }
233
234 /* End of request */
235 if (shout_queue_str(connection, "\r\n"))
236 break;
237 if (param && is_post) {
238 if (shout_queue_str(connection, param))
239 break;
240 }
241 } while (0);
242
243 shout_connection_set_error(connection, ret);
244 return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR;
245 }
246
shout_create_http_request(shout_t * self,shout_connection_t * connection)247 static shout_connection_return_state_t shout_create_http_request(shout_t *self, shout_connection_t *connection)
248 {
249 const shout_http_plan_t *plan = connection->plan;
250
251 if (!plan) {
252 shout_connection_set_error(connection, SHOUTERR_INSANE);
253 return SHOUT_RS_ERROR;
254 }
255
256 #ifdef HAVE_OPENSSL
257 if (!connection->tls) {
258 /* Why not try Upgrade? */
259 if ((connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) &&
260 !(connection->server_caps & LIBSHOUT_CAP_GOTCAPS) &&
261 connection->current_protocol_state == STATE_CHALLENGE) {
262 connection->current_protocol_state = STATE_UPGRADE;
263 }
264
265 if (connection->selected_tls_mode == SHOUT_TLS_RFC2817) {
266 connection->current_protocol_state = STATE_UPGRADE;
267 }
268 }
269 #endif
270
271 switch ((shout_http_protocol_state_t)connection->current_protocol_state) {
272 case STATE_CHALLENGE:
273 connection->server_caps |= LIBSHOUT_CAP_CHALLENGED;
274 if (plan->is_source) {
275 return shout_create_http_request_source(self, connection, 0, 1);
276 } else {
277 return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, 0);
278 }
279 break;
280 case STATE_SOURCE:
281 /* Just an extra layer of safety */
282 switch (connection->selected_tls_mode) {
283 case SHOUT_TLS_AUTO_NO_PLAIN:
284 case SHOUT_TLS_RFC2817:
285 case SHOUT_TLS_RFC2818:
286 #ifdef HAVE_OPENSSL
287 if (!connection->tls) {
288 /* TLS requested but for some reason not established. NOT sending credentials. */
289 shout_connection_set_error(connection, SHOUTERR_INSANE);
290 return SHOUT_RS_ERROR;
291 }
292 #else
293 shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED);
294 return SHOUT_RS_ERROR;
295 #endif
296 break;
297 }
298
299 if (plan->is_source) {
300 return shout_create_http_request_source(self, connection, 1, 0);
301 } else {
302 return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, plan->auth);
303 }
304 break;
305 case STATE_UPGRADE:
306 return shout_create_http_request_generic(self, connection, "OPTIONS", "*", NULL, 0, "TLS/1.0, HTTP/1.1", 0);
307 break;
308 case STATE_POKE:
309 return shout_create_http_request_generic(self, connection, "GET", "/admin/!POKE", NULL, 0, NULL, 0);
310 break;
311 default:
312 shout_connection_set_error(connection, SHOUTERR_INSANE);
313 return SHOUT_RS_ERROR;
314 break;
315 }
316 }
317
shout_get_http_response(shout_t * self,shout_connection_t * connection)318 static shout_connection_return_state_t shout_get_http_response(shout_t *self, shout_connection_t *connection)
319 {
320 int blen;
321 char *pc;
322 shout_buf_t *queue;
323 int newlines = 0;
324
325 if (!connection->rqueue.len) {
326 #ifdef HAVE_OPENSSL
327 if (!connection->tls && (connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN)) {
328 if (connection->current_protocol_state == STATE_POKE) {
329 shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818);
330 return shout_parse_http_select_next_state(self, connection, 0, STATE_CHALLENGE);
331 } else {
332 return shout_parse_http_select_next_state(self, connection, 0, STATE_POKE);
333 }
334 }
335 #endif
336 shout_connection_set_error(connection, SHOUTERR_SOCKET);
337 return SHOUT_RS_ERROR;
338 }
339
340 /* work from the back looking for \r?\n\r?\n. Anything else means more
341 * is coming.
342 */
343 for (queue = connection->rqueue.head; queue->next; queue = queue->next) ;
344 pc = (char*)queue->data + queue->len - 1;
345 blen = queue->len;
346 while (blen) {
347 if (*pc == '\n') {
348 newlines++;
349 } else if (*pc != '\r') {
350 /* we may have to scan the entire queue if we got a response with
351 * data after the head line (this can happen with eg 401)
352 */
353 newlines = 0;
354 }
355
356 if (newlines == 2) {
357 return SHOUT_RS_DONE;
358 }
359
360 blen--;
361 pc--;
362
363 if (!blen && queue->prev) {
364 queue = queue->prev;
365 pc = (char*)queue->data + queue->len - 1;
366 blen = queue->len;
367 }
368 }
369
370 return SHOUT_RS_NOTNOW;
371 }
372
parse_http_response_caps(shout_t * self,shout_connection_t * connection,const char * header,const char * str)373 static inline void parse_http_response_caps(shout_t *self, shout_connection_t *connection, const char *header, const char *str) {
374 const char *end;
375 size_t len;
376 char buf[64];
377
378 if (!self || !header || !str)
379 return;
380
381 do {
382 for (; *str == ' '; str++) ;
383 end = strstr(str, ",");
384 if (end) {
385 len = end - str;
386 } else {
387 len = strlen(str);
388 }
389
390 if (len > (sizeof(buf) - 1))
391 return;
392 memcpy(buf, str, len);
393 buf[len] = 0;
394
395 if (strcmp(header, "Allow") == 0) {
396 if (strcasecmp(buf, "SOURCE") == 0) {
397 connection->server_caps |= LIBSHOUT_CAP_SOURCE;
398 } else if (strcasecmp(buf, "PUT") == 0) {
399 connection->server_caps |= LIBSHOUT_CAP_PUT;
400 } else if (strcasecmp(buf, "POST") == 0) {
401 connection->server_caps |= LIBSHOUT_CAP_POST;
402 } else if (strcasecmp(buf, "GET") == 0) {
403 connection->server_caps |= LIBSHOUT_CAP_GET;
404 } else if (strcasecmp(buf, "OPTIONS") == 0) {
405 connection->server_caps |= LIBSHOUT_CAP_OPTIONS;
406 }
407 } else if (strcmp(header, "Accept-Encoding") == 0) {
408 if (strcasecmp(buf, "chunked") == 0) {
409 connection->server_caps |= LIBSHOUT_CAP_CHUNKED;
410 }
411 } else if (strcmp(header, "Upgrade") == 0) {
412 if (strcasecmp(buf, "TLS/1.0") == 0) {
413 connection->server_caps |= LIBSHOUT_CAP_UPGRADETLS;
414 }
415 } else {
416 return; /* unknown header */
417 }
418
419 str += len + 1;
420 } while (end);
421
422 return;
423 }
424
eat_body(shout_t * self,shout_connection_t * connection,size_t len,const char * buf,size_t buflen)425 static inline int eat_body(shout_t *self, shout_connection_t *connection, size_t len, const char *buf, size_t buflen)
426 {
427 const char *p;
428 size_t header_len = 0;
429 char buffer[256];
430 ssize_t got;
431
432 if (!len)
433 return 0;
434
435 for (p = buf; p < (buf + buflen - 3); p++) {
436 if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') {
437 header_len = p - buf + 4;
438 break;
439 } else if (p[0] == '\n' && p[1] == '\n') {
440 header_len = p - buf + 2;
441 break;
442 }
443 }
444 if (!header_len && buflen >= 3 && buf[buflen - 2] == '\n' && buf[buflen - 3] == '\n') {
445 header_len = buflen - 1;
446 } else if (!header_len && buflen >= 2 && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\n') {
447 header_len = buflen;
448 }
449
450 if ((buflen - header_len) > len)
451 return -1;
452
453 len -= buflen - header_len;
454
455 while (len) {
456 got = shout_connection__read(connection, self, buffer, len > sizeof(buffer) ? sizeof(buffer) : len);
457 if (got == -1 && shout_connection__recoverable(connection, self)) {
458 continue;
459 } else if (got == -1) {
460 return -1;
461 }
462
463 len -= got;
464 }
465
466 return 0;
467 }
468
shout_parse_http_response(shout_t * self,shout_connection_t * connection)469 static shout_connection_return_state_t shout_parse_http_response(shout_t *self, shout_connection_t *connection)
470 {
471 http_parser_t *parser;
472 char *header = NULL;
473 ssize_t hlen;
474 int code;
475 const char *retcode;
476 int ret;
477 char *mount;
478 int consider_retry = 0;
479 int can_reuse = 0;
480 #ifdef HAVE_STRCASESTR
481 const char *tmp;
482 #endif
483
484 /* all this copying! */
485 hlen = shout_queue_collect(connection->rqueue.head, &header);
486 if (hlen <= 0) {
487 if (connection->current_protocol_state == STATE_SOURCE && shout_connection_get_wait_timeout_happened(connection, self) > 0) {
488 connection->current_message_state = SHOUT_MSGSTATE_SENDING1;
489 connection->target_message_state = SHOUT_MSGSTATE_WAITING1;
490 return SHOUT_RS_DONE;
491 } else {
492 shout_connection_set_error(connection, SHOUTERR_MALLOC);
493 return SHOUT_RS_ERROR;
494 }
495 }
496 shout_queue_free(&connection->rqueue);
497
498 parser = httpp_create_parser();
499 httpp_initialize(parser, NULL);
500
501 if (!(mount = _shout_util_url_encode(self->mount))) {
502 httpp_destroy(parser);
503 free(header);
504 shout_connection_set_error(connection, SHOUTERR_MALLOC);
505 return SHOUT_RS_ERROR;
506 }
507
508 ret = httpp_parse_response(parser, header, hlen, mount);
509 free(mount);
510
511 if (ret) {
512 /* TODO: Headers to Handle:
513 * Allow:, Accept-Encoding:, Warning:, Upgrade:
514 */
515 parse_http_response_caps(self, connection, "Allow", httpp_getvar(parser, "allow"));
516 parse_http_response_caps(self, connection, "Accept-Encoding", httpp_getvar(parser, "accept-encoding"));
517 parse_http_response_caps(self, connection, "Upgrade", httpp_getvar(parser, "upgrade"));
518 connection->server_caps |= LIBSHOUT_CAP_GOTCAPS;
519 retcode = httpp_getvar(parser, HTTPP_VAR_ERROR_CODE);
520 code = atoi(retcode);
521
522 #ifdef HAVE_STRCASESTR
523 tmp = httpp_getvar(parser, HTTPP_VAR_VERSION);
524 if (tmp && strcmp(tmp, "1.1") == 0) {
525 can_reuse = 1;
526 }
527 tmp = httpp_getvar(parser, "connection");
528 if (tmp && strcasestr(tmp, "keep-alive")) {
529 can_reuse = 1;
530 }
531 if (tmp && strcasestr(tmp, "close")) {
532 can_reuse = 0;
533 }
534 #else
535 /* get a real OS */
536 can_reuse = 0;
537 #endif
538
539 if ((code == 100 || (code >= 200 && code < 300)) && connection->current_protocol_state == STATE_SOURCE) {
540 httpp_destroy(parser);
541 free(header);
542 connection->current_message_state = SHOUT_MSGSTATE_SENDING1;
543 connection->target_message_state = SHOUT_MSGSTATE_WAITING1;
544 return SHOUT_RS_DONE;
545 } else if ((code >= 200 && code < 300) || code == 400 || code == 401 || code == 405 || code == 426 || code == 101) {
546 const char *content_length = httpp_getvar(parser, "content-length");
547 if (content_length) {
548 if (eat_body(self, connection, atoi(content_length), header, hlen) == -1) {
549 can_reuse = 0;
550 goto failure;
551 }
552 }
553 #ifdef HAVE_OPENSSL
554 switch (code) {
555 case 400:
556 if (connection->current_protocol_state != STATE_UPGRADE && connection->current_protocol_state != STATE_POKE) {
557 free(header);
558 httpp_destroy(parser);
559 shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
560 return SHOUT_RS_ERROR;
561 }
562 if (connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) {
563 can_reuse = 0;
564 shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818);
565 }
566 break;
567
568 case 426:
569 if (connection->tls) {
570 free(header);
571 httpp_destroy(parser);
572 shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
573 return SHOUT_RS_ERROR;
574 } else if (connection->selected_tls_mode == SHOUT_TLS_DISABLED) {
575 free(header);
576 httpp_destroy(parser);
577 shout_connection_set_error(connection, SHOUTERR_NOCONNECT);
578 return SHOUT_RS_ERROR;
579 } else {
580 /* Reset challenge state here as we do not know if it's the same inside TLS */
581 connection->server_caps |= LIBSHOUT_CAP_CHALLENGED;
582 connection->server_caps -= LIBSHOUT_CAP_CHALLENGED;
583 shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817);
584 free(header);
585 httpp_destroy(parser);
586 return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_UPGRADE);
587 }
588 break;
589
590 case 101:
591 shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817);
592 shout_connection_starttls(connection, self);
593 break;
594 }
595 #endif
596 consider_retry = 1;
597 }
598
599 if (code >= 100 && code < 200) {
600 connection->current_message_state = SHOUT_MSGSTATE_WAITING0;
601 return SHOUT_RS_NOTNOW;
602 }
603 }
604
605 failure:
606 free(header);
607 httpp_destroy(parser);
608
609 if (consider_retry) {
610 switch ((shout_http_protocol_state_t)connection->current_protocol_state) {
611 case STATE_CHALLENGE:
612 return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE);
613 break;
614 case STATE_UPGRADE:
615 case STATE_POKE:
616 if (connection->server_caps & LIBSHOUT_CAP_CHALLENGED) {
617 return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE);
618 } else {
619 return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_CHALLENGE);
620 }
621 break;
622 case STATE_SOURCE:
623 /* no-op */
624 break;
625 }
626 }
627 shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
628 return SHOUT_RS_ERROR;
629 }
630
631 static const shout_protocol_impl_t shout_http_impl_real = {
632 .msg_create = shout_create_http_request,
633 .msg_get = shout_get_http_response,
634 .msg_parse = shout_parse_http_response
635 };
636 const shout_protocol_impl_t * shout_http_impl = &shout_http_impl_real;
637