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
35 #include <polarssl/sha2.h>
36 #ifndef SHA256_DIGEST_LENGTH
37 #define SHA256_DIGEST_LENGTH 32
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>
47 #ifndef SHA256_DIGEST_LENGTH
48 #define SHA256_DIGEST_LENGTH 32
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
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)
77 extern void RTMP_TLS_Init();
78 extern TLS_CTX RTMP_TLS_ctx
;
84 #define AGENT "Mozilla/5.0"
87 HTTP_get(struct HTTP_ctx
*http
, const char *url
, HTTP_read_callback
*cb
)
99 HTTPResult ret
= HTTPRES_OK
;
100 struct sockaddr_in sa
;
101 RTMPSockBuf sb
= {0};
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
;
120 return HTTPRES_BAD_REQUEST
;
124 p1
= strchr(url
+ 4, ':');
125 if (!p1
|| strncmp(p1
, "://", 3))
126 return HTTPRES_BAD_REQUEST
;
129 path
= strchr(host
, '/');
131 strncpy(hbuf
, host
, hlen
);
134 p1
= strrchr(host
, ':');
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
;
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
);
158 i
+= sprintf(sb
.sb_buf
+ i
, "If-Modified-Since: %s\r\n", http
->date
);
159 i
+= sprintf(sb
.sb_buf
+ i
, "\r\n");
162 (sb
.sb_socket
, (struct sockaddr
*)&sa
, sizeof(struct sockaddr
)) < 0)
164 ret
= HTTPRES_LOST_CONNECTION
;
171 RTMP_Log(RTMP_LOGERROR
, "%s, No SSL/TLS support", __FUNCTION__
);
172 ret
= HTTPRES_BAD_REQUEST
;
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
;
186 RTMPSockBuf_Send(&sb
, sb
.sb_buf
, i
);
189 #define HTTP_TIMEOUT 5
191 SET_RCVTIMEO(tv
, HTTP_TIMEOUT
);
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
);
201 sb
.sb_timedout
= FALSE
;
202 if (RTMPSockBuf_Fill(&sb
) < 1)
204 ret
= HTTPRES_LOST_CONNECTION
;
207 if (strncmp(sb
.sb_buf
, "HTTP/1", 6))
209 ret
= HTTPRES_BAD_REQUEST
;
213 p1
= strchr(sb
.sb_buf
, ' ');
221 ret
= HTTPRES_OK_NOT_MODIFIED
;
225 ret
= HTTPRES_NOT_FOUND
;
227 ret
= HTTPRES_SERVER_ERROR
;
229 ret
= HTTPRES_BAD_REQUEST
;
231 ret
= HTTPRES_REDIRECTED
;
234 p1
= memchr(sb
.sb_buf
, '\n', sb
.sb_size
);
237 ret
= HTTPRES_BAD_REQUEST
;
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')
253 (sb
.sb_start
, "Content-Length: ", sizeof("Content-Length: ") - 1))
255 flen
= atoi(sb
.sb_start
+ sizeof("Content-Length: ") - 1);
259 (sb
.sb_start
, "Last-Modified: ", sizeof("Last-Modified: ") - 1))
262 strcpy(http
->date
, sb
.sb_start
+ sizeof("Last-Modified: ") - 1);
265 sb
.sb_size
-= p2
- sb
.sb_start
;
269 if (RTMPSockBuf_Fill(&sb
) < 1)
271 ret
= HTTPRES_LOST_CONNECTION
;
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
);
284 http
->size
+= sb
.sb_size
;
289 ret
= HTTPRES_LOST_CONNECTION
;
292 RTMPSockBuf_Close(&sb
);
310 swfcrunch(void *ptr
, size_t size
, size_t nmemb
, void *stream
)
312 struct info
*i
= stream
;
314 size_t len
= size
* nmemb
;
320 if (!strncmp(p
, "CWS", 3))
325 HMAC_crunch(i
->ctx
, (unsigned char *)p
, 8);
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
;
343 HMAC_crunch(i
->ctx
, out
, len
);
345 while (i
->zs
->avail_out
== 0);
350 HMAC_crunch(i
->ctx
, (unsigned char *)p
, len
);
356 static int tzchecked
;
358 #define JAN02_1980 318340800
360 static const char *monthtab
[12] = { "Jan", "Feb", "Mar",
365 static const char *days
[] =
366 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
368 /* Parse an HTTP datestamp into Unix time */
370 make_unix_time(char *s
)
373 int i
, ysub
= 1900, fmt
= 0;
385 if (*n
== '-' || *n
== ':')
392 /* Day, DD-MMM-YYYY HH:MM:SS GMT */
393 time
.tm_mday
= strtol(n
+ 1, &n
, 0);
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);
403 /* Unix ctime() format. Does not conform to HTTP spec. */
404 /* Day MMM DD HH:MM:SS YYYY */
406 n
= strchr(month
, ' ');
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))
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.
432 time_t then
= JAN02_1980
;
433 tc
= localtime(&then
);
434 tzoff
= (12 - tc
->tm_hour
) * 3600 + tc
->tm_min
* 60 + tc
->tm_sec
;
438 /* Unfortunately, mktime() assumes the input is in local time,
439 * not GMT, so we have to correct it here.
446 /* Convert a Unix time to a network time string
447 * Weekday, DD-MMM-YYYY HH:MM:SS GMT
450 strtime(time_t * t
, char *s
)
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
,
467 char *path
, date
[64], cctim
[64];
469 time_t ctim
= -1, cnow
;
470 int i
, got
= 0, ret
= 0;
472 struct info in
= { 0 };
473 struct HTTP_ctx http
= { 0 };
483 home
.av_val
= "\\UserData";
485 hpre
.av_val
= getenv("HOMEDRIVE");
486 hpre
.av_len
= strlen(hpre
.av_val
);
487 home
.av_val
= getenv("HOMEPATH");
494 home
.av_val
= getenv("HOME");
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+");
517 char buf
[4096], *file
, *p
;
519 file
= strchr(url
, '/');
523 file
= strchr(file
, '/');
528 p
= strrchr(file
, '/');
534 while (fgets(buf
, sizeof(buf
), f
))
540 if (strncmp(buf
, "url: ", 5))
542 if (strncmp(buf
+ 5, url
, hlen
))
544 r1
= strrchr(buf
, '/');
547 if (strncmp(r1
, file
, i
))
550 while (got
< 4 && fgets(buf
, sizeof(buf
), f
))
552 if (!strncmp(buf
, "size: ", 6))
554 *size
= strtol(buf
+ 6, NULL
, 16);
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]);
565 else if (!strncmp(buf
, "date: ", 6))
567 buf
[strlen(buf
) - 1] = '\0';
568 strncpy(date
, buf
+ 6, sizeof(date
));
571 else if (!strncmp(buf
, "ctim: ", 6))
573 buf
[strlen(buf
) - 1] = '\0';
574 ctim
= make_unix_time(buf
+ 6);
577 else if (!strncmp(buf
, "url: ", 5))
586 /* If we got a cache time, see if it's young enough to use directly */
590 ctim
/= 3600 * 24; /* seconds to days */
591 if (ctim
< age
) /* ok, it's new enough */
596 HMAC_setup(in
.ctx
, "Genuine Adobe Flash Player 001", 30);
603 httpres
= HTTP_get(&http
, url
, swfcrunch
);
607 if (httpres
!= HTTPRES_OK
&& httpres
!= HTTPRES_OK_NOT_MODIFIED
)
610 if (httpres
== HTTPRES_LOST_CONNECTION
)
611 RTMP_Log(RTMP_LOGERROR
, "%s: connection lost while downloading swfurl %s",
613 else if (httpres
== HTTPRES_NOT_FOUND
)
614 RTMP_Log(RTMP_LOGERROR
, "%s: swfurl %s not found", __FUNCTION__
, url
);
616 RTMP_Log(RTMP_LOGERROR
, "%s: couldn't contact swfurl %s (HTTP error %d)",
617 __FUNCTION__
, url
, http
.status
);
622 fseek(f
, pos
, SEEK_SET
);
627 f
= fopen(path
, "w");
631 RTMP_Log(RTMP_LOGERROR
,
632 "%s: couldn't open %s for writing, errno %d (%s)",
633 __FUNCTION__
, path
, err
, strerror(err
));
637 fseek(f
, 0, SEEK_END
);
638 q
= strchr(url
, '?');
644 fprintf(f
, "url: %.*s\n", i
, url
);
646 strtime(&cnow
, cctim
);
647 fprintf(f
, "ctim: %s\n", cctim
);
651 HMAC_finish(in
.ctx
, hash
, hlen
);
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
]);
671 RTMP_HashSWF(const char *url
, unsigned int *size
, unsigned char *hash
,