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