1 /*
2 * Copyright (C) 2009-2010 Howard Chu
3 *
4 * This file is part of librtmp.
5 *
6 * librtmp is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1,
9 * or (at your option) any later version.
10 *
11 * librtmp is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with librtmp see the file COPYING. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/lgpl.html
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <time.h>
28
29 #include "rtmp_sys.h"
30 #include "log.h"
31 #include "http.h"
32
33 #ifdef CRYPTO
34 #ifdef USE_POLARSSL
35 #include <polarssl/sha2.h>
36 #ifndef SHA256_DIGEST_LENGTH
37 #define SHA256_DIGEST_LENGTH 32
38 #endif
39 #define HMAC_CTX sha2_context
40 #define HMAC_setup(ctx, key, len) do { \
41 if (ctx == NULL) \
42 ctx = calloc(1, sizeof(*ctx)); \
43 sha2_hmac_starts(ctx, (unsigned char *)key, len, 0); \
44 } while (0);
45 #define HMAC_crunch(ctx, buf, len) sha2_hmac_update(ctx, buf, len)
46 #define HMAC_finish(ctx, dig, dlen) dlen = SHA256_DIGEST_LENGTH; sha2_hmac_finish(ctx, dig)
47 #define HMAC_close(ctx) do { \
48 free(ctx); \
49 ctx = NULL; \
50 } while (0)
51 #elif defined(USE_GNUTLS)
52 #include <nettle/hmac.h>
53 #ifndef SHA256_DIGEST_LENGTH
54 #define SHA256_DIGEST_LENGTH 32
55 #endif
56 #undef HMAC_CTX
57 #define HMAC_CTX struct hmac_sha256_ctx
58 #define HMAC_setup(ctx, key, len) do { \
59 if (ctx == NULL) \
60 ctx = calloc(1, sizeof(*ctx)); \
61 hmac_sha256_set_key(ctx, len, key); \
62 } while (0)
63 #define HMAC_crunch(ctx, buf, len) hmac_sha256_update(ctx, len, buf)
64 #define HMAC_finish(ctx, dig, dlen) dlen = SHA256_DIGEST_LENGTH; hmac_sha256_digest(ctx, SHA256_DIGEST_LENGTH, dig)
65 #define HMAC_close(ctx) do { \
66 free(ctx); \
67 ctx = NULL; \
68 } while (0)
69 #else /* USE_OPENSSL */
70 #include <openssl/ssl.h>
71 #include <openssl/sha.h>
72 #include <openssl/hmac.h>
73 #include <openssl/rc4.h>
74 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
75 #define HMAC_setup(ctx, key, len) do { \
76 if (ctx == NULL) \
77 ctx = calloc(1, sizeof(*ctx)); \
78 HMAC_CTX_init(ctx); \
79 HMAC_Init_ex(ctx, (unsigned char *)key, len, EVP_sha256(), 0); \
80 } while (0)
81 #else
82 #define HMAC_setup(ctx, key, len) do { \
83 if (ctx == NULL) \
84 ctx = HMAC_CTX_new(); \
85 HMAC_Init_ex(ctx, (unsigned char *)key, len, EVP_sha256(), 0); \
86 } while (0);
87 #endif
88 #define HMAC_crunch(ctx, buf, len) HMAC_Update(ctx, (unsigned char *)buf, len)
89 #define HMAC_finish(ctx, dig, dlen) HMAC_Final(ctx, (unsigned char *)dig, &dlen);
90 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
91 #define HMAC_close(ctx) do { \
92 HMAC_CTX_cleanup(ctx); \
93 free(ctx); \
94 ctx = NULL; \
95 } while (0)
96 #else
97 #define HMAC_close(ctx) do { \
98 HMAC_CTX_reset(ctx); \
99 HMAC_CTX_free(ctx); \
100 ctx = NULL; \
101 } while (0)
102 #endif
103 #endif
104
105 extern void RTMP_TLS_Init();
106 extern TLS_CTX RTMP_TLS_ctx;
107
108 #include <zlib.h>
109
110 #endif /* CRYPTO */
111
112 #define DATELEN 64
113
114 #define AGENT "Mozilla/5.0"
115
116 HTTPResult
HTTP_get(struct HTTP_ctx * http,const char * url,HTTP_read_callback * cb)117 HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb)
118 {
119 char *host, *path;
120 char *p1, *p2;
121 char hbuf[256];
122 int port = 80;
123 #ifdef CRYPTO
124 int ssl = 0;
125 #endif
126 int hlen;
127 long flen = 0;
128 int rc, i;
129 int len_known;
130 HTTPResult ret = HTTPRES_OK;
131 struct sockaddr_in sa;
132 RTMPSockBuf sb = {0};
133
134 http->status = -1;
135
136 memset(&sa, 0, sizeof(struct sockaddr_in));
137 sa.sin_family = AF_INET;
138
139 /* we only handle http here */
140 if (strncasecmp(url, "http", 4))
141 return HTTPRES_BAD_REQUEST;
142
143 if (url[4] == 's')
144 {
145 #ifdef CRYPTO
146 ssl = 1;
147 port = 443;
148 if (!RTMP_TLS_ctx)
149 RTMP_TLS_Init();
150 #else
151 return HTTPRES_BAD_REQUEST;
152 #endif
153 }
154
155 p1 = strchr(url + 4, ':');
156 if (!p1 || strncmp(p1, "://", 3))
157 return HTTPRES_BAD_REQUEST;
158
159 host = p1 + 3;
160 path = strchr(host, '/');
161 hlen = path - host;
162 strncpy(hbuf, host, hlen);
163 hbuf[hlen] = '\0';
164 host = hbuf;
165 p1 = strrchr(host, ':');
166 if (p1)
167 {
168 *p1++ = '\0';
169 port = atoi(p1);
170 }
171
172 sa.sin_addr.s_addr = inet_addr(host);
173 if (sa.sin_addr.s_addr == INADDR_NONE)
174 {
175 struct hostent *hp = gethostbyname(host);
176 if (!hp || !hp->h_addr)
177 return HTTPRES_LOST_CONNECTION;
178 sa.sin_addr = *(struct in_addr *)hp->h_addr;
179 }
180 sa.sin_port = htons(port);
181 sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
182 if (sb.sb_socket == -1)
183 return HTTPRES_LOST_CONNECTION;
184 i =
185 sprintf(sb.sb_buf,
186 "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\nReferer: %.*s\r\n",
187 path, AGENT, host, (int)(path - url + 1), url);
188 if (http->date[0])
189 i += sprintf(sb.sb_buf + i, "If-Modified-Since: %s\r\n", http->date);
190 i += sprintf(sb.sb_buf + i, "\r\n");
191
192 if (connect
193 (sb.sb_socket, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0)
194 {
195 ret = HTTPRES_LOST_CONNECTION;
196 goto leave;
197 }
198 #ifdef CRYPTO
199 if (ssl)
200 {
201 #ifdef NO_SSL
202 RTMP_Log(RTMP_LOGERROR, "%s, No SSL/TLS support", __FUNCTION__);
203 ret = HTTPRES_BAD_REQUEST;
204 goto leave;
205 #else
206 TLS_client(RTMP_TLS_ctx, sb.sb_ssl);
207 TLS_setfd(sb.sb_ssl, sb.sb_socket);
208 if (TLS_connect(sb.sb_ssl) < 0)
209 {
210 RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
211 ret = HTTPRES_LOST_CONNECTION;
212 goto leave;
213 }
214 #endif
215 }
216 #endif
217 RTMPSockBuf_Send(&sb, sb.sb_buf, i);
218
219 /* set timeout */
220 #define HTTP_TIMEOUT 5
221 {
222 SET_RCVTIMEO(tv, HTTP_TIMEOUT);
223 if (setsockopt
224 (sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
225 {
226 RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
227 __FUNCTION__, HTTP_TIMEOUT);
228 }
229 }
230
231 sb.sb_size = 0;
232 sb.sb_timedout = FALSE;
233 if (RTMPSockBuf_Fill(&sb) < 1)
234 {
235 ret = HTTPRES_LOST_CONNECTION;
236 goto leave;
237 }
238 if (strncmp(sb.sb_buf, "HTTP/1", 6))
239 {
240 ret = HTTPRES_BAD_REQUEST;
241 goto leave;
242 }
243
244 p1 = strchr(sb.sb_buf, ' ');
245 rc = atoi(p1 + 1);
246 http->status = rc;
247
248 if (rc >= 300)
249 {
250 if (rc == 304)
251 {
252 ret = HTTPRES_OK_NOT_MODIFIED;
253 goto leave;
254 }
255 else if (rc == 404)
256 ret = HTTPRES_NOT_FOUND;
257 else if (rc >= 500)
258 ret = HTTPRES_SERVER_ERROR;
259 else if (rc >= 400)
260 ret = HTTPRES_BAD_REQUEST;
261 else
262 ret = HTTPRES_REDIRECTED;
263 }
264
265 p1 = memchr(sb.sb_buf, '\n', sb.sb_size);
266 if (!p1)
267 {
268 ret = HTTPRES_BAD_REQUEST;
269 goto leave;
270 }
271 sb.sb_start = p1 + 1;
272 sb.sb_size -= sb.sb_start - sb.sb_buf;
273
274 while ((p2 = memchr(sb.sb_start, '\r', sb.sb_size)))
275 {
276 if (*sb.sb_start == '\r')
277 {
278 sb.sb_start += 2;
279 sb.sb_size -= 2;
280 break;
281 }
282 else
283 if (!strncasecmp
284 (sb.sb_start, "Content-Length: ", sizeof("Content-Length: ") - 1))
285 {
286 flen = strtol(sb.sb_start + sizeof("Content-Length: ") - 1, NULL, 10);
287 if (flen < 1 || flen > INT_MAX)
288 {
289 ret = HTTPRES_BAD_REQUEST;
290 goto leave;
291 }
292 }
293 else
294 if (!strncasecmp
295 (sb.sb_start, "Last-Modified: ", sizeof("Last-Modified: ") - 1))
296 {
297 *p2 = '\0';
298 strncpy(http->date, sb.sb_start + sizeof("Last-Modified: ") - 1, DATELEN-1);
299 http->date[DATELEN-1] = '\0';
300 }
301 p2 += 2;
302 sb.sb_size -= p2 - sb.sb_start;
303 sb.sb_start = p2;
304 if (sb.sb_size < 1)
305 {
306 if (RTMPSockBuf_Fill(&sb) < 1)
307 {
308 ret = HTTPRES_LOST_CONNECTION;
309 goto leave;
310 }
311 }
312 }
313
314 len_known = flen > 0;
315 while ((!len_known || flen > 0) &&
316 (sb.sb_size > 0 || RTMPSockBuf_Fill(&sb) > 0))
317 {
318 cb(sb.sb_start, 1, sb.sb_size, http->data);
319 if (len_known)
320 flen -= sb.sb_size;
321 http->size += sb.sb_size;
322 sb.sb_size = 0;
323 }
324
325 if (flen > 0)
326 ret = HTTPRES_LOST_CONNECTION;
327
328 leave:
329 RTMPSockBuf_Close(&sb);
330 return ret;
331 }
332
333 #ifdef CRYPTO
334
335 #define CHUNK 16384
336
337 struct info
338 {
339 z_stream *zs;
340 HMAC_CTX *ctx;
341 int first;
342 int zlib;
343 int size;
344 };
345
346 static size_t
swfcrunch(void * ptr,size_t size,size_t nmemb,void * stream)347 swfcrunch(void *ptr, size_t size, size_t nmemb, void *stream)
348 {
349 struct info *i = stream;
350 char *p = ptr;
351 size_t len = size * nmemb;
352
353 if (i->first)
354 {
355 i->first = 0;
356 /* compressed? */
357 if (!strncmp(p, "CWS", 3))
358 {
359 *p = 'F';
360 i->zlib = 1;
361 }
362 HMAC_crunch(i->ctx, (unsigned char *)p, 8);
363 p += 8;
364 len -= 8;
365 i->size = 8;
366 }
367
368 if (i->zlib)
369 {
370 unsigned char out[CHUNK];
371 i->zs->next_in = (unsigned char *)p;
372 i->zs->avail_in = len;
373 do
374 {
375 i->zs->avail_out = CHUNK;
376 i->zs->next_out = out;
377 inflate(i->zs, Z_NO_FLUSH);
378 len = CHUNK - i->zs->avail_out;
379 i->size += len;
380 HMAC_crunch(i->ctx, out, len);
381 }
382 while (i->zs->avail_out == 0);
383 }
384 else
385 {
386 i->size += len;
387 HMAC_crunch(i->ctx, (unsigned char *)p, len);
388 }
389 return size * nmemb;
390 }
391
392 static int tzoff;
393 static int tzchecked;
394
395 #define JAN02_1980 318340800
396
397 static const char *monthtab[12] = { "Jan", "Feb", "Mar",
398 "Apr", "May", "Jun",
399 "Jul", "Aug", "Sep",
400 "Oct", "Nov", "Dec"
401 };
402 static const char *days[] =
403 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
404
405 /* Parse an HTTP datestamp into Unix time */
406 static time_t
make_unix_time(char * s)407 make_unix_time(char *s)
408 {
409 struct tm time;
410 int i, ysub = 1900, fmt = 0;
411 char *month;
412 char *n;
413 time_t res;
414
415 if (s[3] != ' ')
416 {
417 fmt = 1;
418 if (s[3] != ',')
419 ysub = 0;
420 }
421 for (n = s; *n; ++n)
422 if (*n == '-' || *n == ':')
423 *n = ' ';
424
425 time.tm_mon = 0;
426 n = strchr(s, ' ');
427 if (fmt)
428 {
429 /* Day, DD-MMM-YYYY HH:MM:SS GMT */
430 time.tm_mday = strtol(n + 1, &n, 0);
431 month = n + 1;
432 n = strchr(month, ' ');
433 time.tm_year = strtol(n + 1, &n, 0);
434 time.tm_hour = strtol(n + 1, &n, 0);
435 time.tm_min = strtol(n + 1, &n, 0);
436 time.tm_sec = strtol(n + 1, NULL, 0);
437 }
438 else
439 {
440 /* Unix ctime() format. Does not conform to HTTP spec. */
441 /* Day MMM DD HH:MM:SS YYYY */
442 month = n + 1;
443 n = strchr(month, ' ');
444 while (isspace(*n))
445 n++;
446 time.tm_mday = strtol(n, &n, 0);
447 time.tm_hour = strtol(n + 1, &n, 0);
448 time.tm_min = strtol(n + 1, &n, 0);
449 time.tm_sec = strtol(n + 1, &n, 0);
450 time.tm_year = strtol(n + 1, NULL, 0);
451 }
452 if (time.tm_year > 100)
453 time.tm_year -= ysub;
454
455 for (i = 0; i < 12; i++)
456 if (!strncasecmp(month, monthtab[i], 3))
457 {
458 time.tm_mon = i;
459 break;
460 }
461 time.tm_isdst = 0; /* daylight saving is never in effect in GMT */
462
463 /* this is normally the value of extern int timezone, but some
464 * braindead C libraries don't provide it.
465 */
466 if (!tzchecked)
467 {
468 struct tm *tc;
469 time_t then = JAN02_1980;
470 tc = localtime(&then);
471 tzoff = (12 - tc->tm_hour) * 3600 + tc->tm_min * 60 + tc->tm_sec;
472 tzchecked = 1;
473 }
474 res = mktime(&time);
475 /* Unfortunately, mktime() assumes the input is in local time,
476 * not GMT, so we have to correct it here.
477 */
478 if (res != -1)
479 res += tzoff;
480 return res;
481 }
482
483 /* Convert a Unix time to a network time string
484 * Weekday, DD-MMM-YYYY HH:MM:SS GMT
485 */
486 static void
strtime(time_t * t,char * s)487 strtime(time_t * t, char *s)
488 {
489 struct tm *tm;
490
491 tm = gmtime((time_t *) t);
492 sprintf(s, "%s, %02d %s %d %02d:%02d:%02d GMT",
493 days[tm->tm_wday], tm->tm_mday, monthtab[tm->tm_mon],
494 tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec);
495 }
496
497 #define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
498
499 int
RTMP_HashSWF(const char * url,unsigned int * size,unsigned char * hash,int age)500 RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
501 int age)
502 {
503 FILE *f = NULL;
504 char *path, date[DATELEN], cctim[DATELEN];
505 long pos = 0;
506 time_t ctim = -1, cnow;
507 int i, got = 0, ret = 0;
508 unsigned int hlen;
509 struct info in = { 0 };
510 struct HTTP_ctx http = { 0 };
511 HTTPResult httpres;
512 z_stream zs = { 0 };
513 AVal home, hpre;
514
515 date[0] = '\0';
516 #ifdef _WIN32
517 #ifdef XBMC4XBOX
518 hpre.av_val = "Q:";
519 hpre.av_len = 2;
520 home.av_val = "\\UserData";
521 #else
522 hpre.av_val = getenv("HOMEDRIVE");
523 hpre.av_len = strlen(hpre.av_val);
524 home.av_val = getenv("HOMEPATH");
525 #endif
526 #define DIRSEP "\\"
527
528 #else /* !_WIN32 */
529 hpre.av_val = "";
530 hpre.av_len = 0;
531 home.av_val = getenv("HOME");
532 #define DIRSEP "/"
533 #endif
534 if (!home.av_val)
535 home.av_val = ".";
536 home.av_len = strlen(home.av_val);
537
538 /* SWF hash info is cached in a fixed-format file.
539 * url: <url of SWF file>
540 * ctim: HTTP datestamp of when we last checked it.
541 * date: HTTP datestamp of the SWF's last modification.
542 * size: SWF size in hex
543 * hash: SWF hash in hex
544 *
545 * These fields must be present in this order. All fields
546 * besides URL are fixed size.
547 */
548 path = malloc(hpre.av_len + home.av_len + sizeof(DIRSEP ".swfinfo"));
549 sprintf(path, "%s%s" DIRSEP ".swfinfo", hpre.av_val, home.av_val);
550
551 f = fopen(path, "r+");
552 while (f)
553 {
554 char buf[4096], *file, *p;
555
556 file = strchr(url, '/');
557 if (!file)
558 break;
559 file += 2;
560 file = strchr(file, '/');
561 if (!file)
562 break;
563 file++;
564 hlen = file - url;
565 p = strrchr(file, '/');
566 if (p)
567 file = p;
568 else
569 file--;
570
571 while (fgets(buf, sizeof(buf), f))
572 {
573 char *r1;
574
575 got = 0;
576
577 if (strncmp(buf, "url: ", 5))
578 continue;
579 if (strncmp(buf + 5, url, hlen))
580 continue;
581 r1 = strrchr(buf, '/');
582 i = strlen(r1);
583 r1[--i] = '\0';
584 if (strncmp(r1, file, i))
585 continue;
586 pos = ftell(f);
587 while (got < 4 && fgets(buf, sizeof(buf), f))
588 {
589 if (!strncmp(buf, "size: ", 6))
590 {
591 *size = strtol(buf + 6, NULL, 16);
592 got++;
593 }
594 else if (!strncmp(buf, "hash: ", 6))
595 {
596 unsigned char *ptr = hash, *in = (unsigned char *)buf + 6;
597 int l = strlen((char *)in) - 1;
598 for (i = 0; i < l; i += 2)
599 *ptr++ = (HEX2BIN(in[i]) << 4) | HEX2BIN(in[i + 1]);
600 got++;
601 }
602 else if (!strncmp(buf, "date: ", 6))
603 {
604 buf[strlen(buf) - 1] = '\0';
605 strncpy(date, buf + 6, sizeof(date)-1);
606 date[DATELEN-1] = '\0';
607 got++;
608 }
609 else if (!strncmp(buf, "ctim: ", 6))
610 {
611 buf[strlen(buf) - 1] = '\0';
612 ctim = make_unix_time(buf + 6);
613 got++;
614 }
615 else if (!strncmp(buf, "url: ", 5))
616 break;
617 }
618 break;
619 }
620 break;
621 }
622
623 cnow = time(NULL);
624 /* If we got a cache time, see if it's young enough to use directly */
625 if (age && ctim > 0)
626 {
627 ctim = cnow - ctim;
628 ctim /= 3600 * 24; /* seconds to days */
629 if (ctim < age) /* ok, it's new enough */
630 goto out;
631 }
632
633 in.first = 1;
634 HMAC_setup(in.ctx, "Genuine Adobe Flash Player 001", 30);
635 inflateInit(&zs);
636 in.zs = &zs;
637
638 http.date = date;
639 http.data = ∈
640
641 httpres = HTTP_get(&http, url, swfcrunch);
642
643 inflateEnd(&zs);
644
645 if (httpres != HTTPRES_OK && httpres != HTTPRES_OK_NOT_MODIFIED)
646 {
647 ret = -1;
648 if (httpres == HTTPRES_LOST_CONNECTION)
649 RTMP_Log(RTMP_LOGERROR, "%s: connection lost while downloading swfurl %s",
650 __FUNCTION__, url);
651 else if (httpres == HTTPRES_NOT_FOUND)
652 RTMP_Log(RTMP_LOGERROR, "%s: swfurl %s not found", __FUNCTION__, url);
653 else
654 RTMP_Log(RTMP_LOGERROR, "%s: couldn't contact swfurl %s (HTTP error %d)",
655 __FUNCTION__, url, http.status);
656 }
657 else
658 {
659 if (got && pos)
660 fseek(f, pos, SEEK_SET);
661 else
662 {
663 char *q;
664 if (!f)
665 f = fopen(path, "w");
666 if (!f)
667 {
668 int err = errno;
669 RTMP_Log(RTMP_LOGERROR,
670 "%s: couldn't open %s for writing, errno %d (%s)",
671 __FUNCTION__, path, err, strerror(err));
672 ret = -1;
673 goto out;
674 }
675 fseek(f, 0, SEEK_END);
676 q = strchr(url, '?');
677 if (q)
678 i = q - url;
679 else
680 i = strlen(url);
681
682 fprintf(f, "url: %.*s\n", i, url);
683 }
684 strtime(&cnow, cctim);
685 fprintf(f, "ctim: %s\n", cctim);
686
687 if (!in.first)
688 {
689 HMAC_finish(in.ctx, hash, hlen);
690 *size = in.size;
691
692 fprintf(f, "date: %s\n", date);
693 fprintf(f, "size: %08x\n", in.size);
694 fprintf(f, "hash: ");
695 for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
696 fprintf(f, "%02x", hash[i]);
697 fprintf(f, "\n");
698 }
699 }
700 HMAC_close(in.ctx);
701 out:
702 free(path);
703 if (f)
704 fclose(f);
705 return ret;
706 }
707 #else
708 int
RTMP_HashSWF(const char * url,unsigned int * size,unsigned char * hash,int age)709 RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
710 int age)
711 {
712 return -1;
713 }
714 #endif
715