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