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 = &in;
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