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