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