1 /*  HTTP-RTMP Stream Gateway
2  *  Copyright (C) 2009 Andrej Stepanchuk
3  *  Copyright (C) 2009-2010 Howard Chu
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with RTMPDump; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  *  Boston, MA  02110-1301, USA.
19  *  http://www.gnu.org/copyleft/gpl.html
20  *
21  */
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26 
27 #include <signal.h>
28 #include <getopt.h>
29 
30 #include <assert.h>
31 
32 #include "librtmp/rtmp_sys.h"
33 #include "librtmp/log.h"
34 
35 #include "thread.h"
36 
37 #define RD_SUCCESS		0
38 #define RD_FAILED		1
39 #define RD_INCOMPLETE		2
40 
41 #define PACKET_SIZE 1024*1024
42 
43 #ifdef WIN32
44 #define InitSockets()	{\
45         WORD version;			\
46         WSADATA wsaData;		\
47 					\
48         version = MAKEWORD(1,1);	\
49         WSAStartup(version, &wsaData);	}
50 
51 #define	CleanupSockets()	WSACleanup()
52 #else
53 #define InitSockets()
54 #define	CleanupSockets()
55 #endif
56 
57 enum
58 {
59   STREAMING_ACCEPTING,
60   STREAMING_IN_PROGRESS,
61   STREAMING_STOPPING,
62   STREAMING_STOPPED
63 };
64 
65 typedef struct
66 {
67   int socket;
68   int state;
69 
70 } STREAMING_SERVER;
71 
72 STREAMING_SERVER *httpServer = 0;	// server structure pointer
73 
74 STREAMING_SERVER *startStreaming(const char *address, int port);
75 void stopStreaming(STREAMING_SERVER * server);
76 
77 typedef struct
78 {
79   AVal hostname;
80   int rtmpport;
81   int protocol;
82   int bLiveStream;		// is it a live stream? then we can't seek/resume
83 
84   long int timeout;		// timeout connection after 120 seconds
85   uint32_t bufferTime;
86 
87   char *rtmpurl;
88   AVal fullUrl;
89   AVal playpath;
90   AVal swfUrl;
91   AVal tcUrl;
92   AVal pageUrl;
93   AVal app;
94   AVal auth;
95   AVal swfHash;
96   AVal flashVer;
97   AVal token;
98   AVal subscribepath;
99   AVal usherToken; //Justin.tv auth token
100   AVal sockshost;
101   AMFObject extras;
102   int edepth;
103   uint32_t swfSize;
104   int swfAge;
105   int swfVfy;
106 
107   uint32_t dStartOffset;
108   uint32_t dStopOffset;
109 
110 #ifdef CRYPTO
111   unsigned char hash[RTMP_SWF_HASHLEN];
112 #endif
113 } RTMP_REQUEST;
114 
115 #define STR2AVAL(av,str)	av.av_val = str; av.av_len = strlen(av.av_val)
116 
117 int
parseAMF(AMFObject * obj,const char * arg,int * depth)118 parseAMF(AMFObject *obj, const char *arg, int *depth)
119 {
120   AMFObjectProperty prop = {{0,0}};
121   int i;
122   char *p;
123 
124   if (arg[1] == ':')
125     {
126       p = (char *)arg+2;
127       switch(arg[0])
128         {
129         case 'B':
130           prop.p_type = AMF_BOOLEAN;
131           prop.p_vu.p_number = atoi(p);
132           break;
133         case 'S':
134           prop.p_type = AMF_STRING;
135           STR2AVAL(prop.p_vu.p_aval,p);
136           break;
137         case 'N':
138           prop.p_type = AMF_NUMBER;
139           prop.p_vu.p_number = strtod(p, NULL);
140           break;
141         case 'Z':
142           prop.p_type = AMF_NULL;
143           break;
144         case 'O':
145           i = atoi(p);
146           if (i)
147             {
148               prop.p_type = AMF_OBJECT;
149             }
150           else
151             {
152               (*depth)--;
153               return 0;
154             }
155           break;
156         default:
157           return -1;
158         }
159     }
160   else if (arg[2] == ':' && arg[0] == 'N')
161     {
162       p = strchr(arg+3, ':');
163       if (!p || !*depth)
164         return -1;
165       prop.p_name.av_val = (char *)arg+3;
166       prop.p_name.av_len = p - (arg+3);
167 
168       p++;
169       switch(arg[1])
170         {
171         case 'B':
172           prop.p_type = AMF_BOOLEAN;
173           prop.p_vu.p_number = atoi(p);
174           break;
175         case 'S':
176           prop.p_type = AMF_STRING;
177           STR2AVAL(prop.p_vu.p_aval,p);
178           break;
179         case 'N':
180           prop.p_type = AMF_NUMBER;
181           prop.p_vu.p_number = strtod(p, NULL);
182           break;
183         case 'O':
184           prop.p_type = AMF_OBJECT;
185           break;
186         default:
187           return -1;
188         }
189     }
190   else
191     return -1;
192 
193   if (*depth)
194     {
195       AMFObject *o2;
196       for (i=0; i<*depth; i++)
197         {
198           o2 = &obj->o_props[obj->o_num-1].p_vu.p_object;
199           obj = o2;
200         }
201     }
202   AMF_AddProp(obj, &prop);
203   if (prop.p_type == AMF_OBJECT)
204     (*depth)++;
205   return 0;
206 }
207 
208 /* this request is formed from the parameters and used to initialize a new request,
209  * thus it is a default settings list. All settings can be overriden by specifying the
210  * parameters in the GET request. */
211 RTMP_REQUEST defaultRTMPRequest;
212 
213 int ParseOption(char opt, char *arg, RTMP_REQUEST * req);
214 
215 #ifdef _DEBUG
216 uint32_t debugTS = 0;
217 
218 int pnum = 0;
219 
220 FILE *netstackdump = NULL;
221 FILE *netstackdump_read = NULL;
222 #endif
223 
224 /* inplace http unescape. This is possible .. strlen(unescaped_string)  <= strlen(esacped_string) */
225 void
http_unescape(char * data)226 http_unescape(char *data)
227 {
228   char hex[3];
229   char *stp;
230   int src_x = 0;
231   int dst_x = 0;
232 
233   int length = (int) strlen(data);
234   hex[2] = 0;
235 
236   while (src_x < length)
237     {
238       if (strncmp(data + src_x, "%", 1) == 0 && src_x + 2 < length)
239 	{
240 	  //
241 	  // Since we encountered a '%' we know this is an escaped character
242 	  //
243 	  hex[0] = data[src_x + 1];
244 	  hex[1] = data[src_x + 2];
245 	  data[dst_x] = (char) strtol(hex, &stp, 16);
246 	  dst_x += 1;
247 	  src_x += 3;
248 	}
249       else if (src_x != dst_x)
250 	{
251 	  //
252 	  // This doesn't need to be unescaped. If we didn't unescape anything previously
253 	  // there is no need to copy the string either
254 	  //
255 	  data[dst_x] = data[src_x];
256 	  src_x += 1;
257 	  dst_x += 1;
258 	}
259       else
260 	{
261 	  //
262 	  // This doesn't need to be unescaped, however we need to copy the string
263 	  //
264 	  src_x += 1;
265 	  dst_x += 1;
266 	}
267     }
268   data[dst_x] = '\0';
269 }
270 
271 TFTYPE
controlServerThread(void * unused)272 controlServerThread(void *unused)
273 {
274   char ich;
275   while (1)
276     {
277       ich = getchar();
278       switch (ich)
279 	{
280 	case 'q':
281 	  RTMP_LogPrintf("Exiting\n");
282 	  stopStreaming(httpServer);
283 	  exit(0);
284 	  break;
285 	default:
286 	  RTMP_LogPrintf("Unknown command \'%c\', ignoring\n", ich);
287 	}
288     }
289   TFRET();
290 }
291 
292 /*
293 ssize_t readHTTPLine(int sockfd, char *buffer, size_t length)
294 {
295 	size_t i=0;
296 
297 	while(i < length-1) {
298 		char c;
299 		int n = read(sockfd, &c, 1);
300 
301 		if(n == 0)
302 			break;
303 
304 		buffer[i] = c;
305 		i++;
306 
307 		if(c == '\n')
308 			break;
309 	}
310 	buffer[i]='\0';
311 	i++;
312 
313 	return i;
314 }
315 
316 int isHTTPRequestEOF(char *line, size_t length)
317 {
318 	if(length < 2)
319 		return TRUE;
320 
321 	if(line[0]=='\r' && line[1]=='\n')
322 		return TRUE;
323 
324 	return FALSE;
325 }
326 */
327 
processTCPrequest(STREAMING_SERVER * server,int sockfd)328 void processTCPrequest(STREAMING_SERVER * server,	// server socket and state (our listening socket)
329 		       int sockfd	// client connection socket
330   )
331 {
332   char buf[512] = { 0 };	// answer buffer
333   char header[2048] = { 0 };	// request header
334   char *filename = NULL;	// GET request: file name //512 not enuf
335   char *buffer = NULL;		// stream buffer
336   char *ptr = NULL;		// header pointer
337   int len;
338 
339   size_t nRead = 0;
340 
341   char srvhead[] = "\r\nServer: HTTP-RTMP Stream Server " RTMPDUMP_VERSION "\r\n";
342 
343   char *status = "404 Not Found";
344 
345   server->state = STREAMING_IN_PROGRESS;
346 
347   RTMP rtmp = { 0 };
348   uint32_t dSeek = 0;		// can be used to start from a later point in the stream
349 
350   // reset RTMP options to defaults specified upon invokation of streams
351   RTMP_REQUEST req;
352   memcpy(&req, &defaultRTMPRequest, sizeof(RTMP_REQUEST));
353 
354   // timeout for http requests
355   fd_set fds;
356   struct timeval tv;
357 
358   memset(&tv, 0, sizeof(struct timeval));
359   tv.tv_sec = 5;
360 
361   // go through request lines
362   //do {
363   FD_ZERO(&fds);
364   FD_SET(sockfd, &fds);
365 
366   if (select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0)
367     {
368       RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request");
369       goto quit;
370     }
371   else
372     {
373       nRead = recv(sockfd, header, 2047, 0);
374       header[2047] = '\0';
375 
376       RTMP_Log(RTMP_LOGDEBUG, "%s: header: %s", __FUNCTION__, header);
377 
378       if (strstr(header, "Range: bytes=") != 0)
379 	{
380 	  // TODO check range starts from 0 and asking till the end.
381 	  RTMP_LogPrintf("%s, Range request not supported\n", __FUNCTION__);
382 	  len = sprintf(buf, "HTTP/1.0 416 Requested Range Not Satisfiable%s\r\n",
383 		  srvhead);
384 	  send(sockfd, buf, len, 0);
385 	  goto quit;
386 	}
387 
388       if (strncmp(header, "GET", 3) == 0 && nRead > 4)
389 	{
390 	  filename = header + 4;
391 
392 	  // filter " HTTP/..." from end of request
393 	  char *p = filename;
394 	  while (*p != '\0')
395 	    {
396 	      if (*p == ' ')
397 		{
398 		  *p = '\0';
399 		  break;
400 		}
401 	      p++;
402 	    }
403 	}
404     }
405   //} while(!isHTTPRequestEOF(header, nRead));
406 
407   // if we got a filename from the GET method
408   if (filename != NULL)
409     {
410       RTMP_Log(RTMP_LOGDEBUG, "%s: Request header: %s", __FUNCTION__, filename);
411       if (filename[0] == '/')
412 	{			// if its not empty, is it /?
413 	  ptr = filename + 1;
414 
415 	  // parse parameters
416 	  if (*ptr == '?')
417 	    {
418 	      ptr++;
419 	      int len = strlen(ptr);
420 
421 	      while (len >= 2)
422 		{
423 		  char ich = *ptr;
424 		  ptr++;
425 		  if (*ptr != '=')
426 		    goto filenotfound;	// long parameters not (yet) supported
427 
428 		  ptr++;
429 		  len -= 2;
430 
431 		  // get position of the next '&'
432 		  char *temp;
433 
434 		  unsigned int nArgLen = len;
435 		  if ((temp = strstr(ptr, "&")) != 0)
436 		    {
437 		      nArgLen = temp - ptr;
438 		    }
439 
440 		  char *arg = (char *) malloc((nArgLen + 1) * sizeof(char));
441 		  memcpy(arg, ptr, nArgLen * sizeof(char));
442 		  arg[nArgLen] = '\0';
443 
444 		  //RTMP_Log(RTMP_LOGDEBUG, "%s: unescaping parameter: %s", __FUNCTION__, arg);
445 		  http_unescape(arg);
446 
447 		  RTMP_Log(RTMP_LOGDEBUG, "%s: parameter: %c, arg: %s", __FUNCTION__,
448 		      ich, arg);
449 
450 		  ptr += nArgLen + 1;
451 		  len -= nArgLen + 1;
452 
453 		  if (!ParseOption(ich, arg, &req))
454 		    {
455 		      status = "400 unknown option";
456 		      goto filenotfound;
457 		    }
458 		}
459 	    }
460 	}
461       else
462 	{
463 	  goto filenotfound;
464 	}
465     }
466   else
467     {
468       RTMP_LogPrintf("%s: No request header received/unsupported method\n",
469 		__FUNCTION__);
470     }
471 
472   // do necessary checks right here to make sure the combined request of default values and GET parameters is correct
473   if (!req.hostname.av_len && !req.fullUrl.av_len)
474     {
475       RTMP_Log(RTMP_LOGERROR,
476 	  "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
477       status = "400 Missing Hostname";
478       goto filenotfound;
479     }
480   if (req.playpath.av_len == 0 && !req.fullUrl.av_len)
481     {
482       RTMP_Log(RTMP_LOGERROR,
483 	  "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
484       status = "400 Missing Playpath";
485       goto filenotfound;;
486     }
487 
488   if (req.protocol == RTMP_PROTOCOL_UNDEFINED && !req.fullUrl.av_len)
489     {
490       RTMP_Log(RTMP_LOGWARNING,
491 	  "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
492       req.protocol = RTMP_PROTOCOL_RTMP;
493     }
494   if (req.rtmpport == -1 && !req.fullUrl.av_len)
495     {
496       RTMP_Log(RTMP_LOGWARNING,
497 	  "You haven't specified a port (--port) or rtmp url (-r), using default port");
498       req.rtmpport = 0;
499     }
500   if (req.rtmpport == 0 && !req.fullUrl.av_len)
501     {
502       if (req.protocol & RTMP_FEATURE_SSL)
503 	req.rtmpport = 443;
504       else if (req.protocol & RTMP_FEATURE_HTTP)
505 	req.rtmpport = 80;
506       else
507 	req.rtmpport = 1935;
508     }
509 
510   if (req.tcUrl.av_len == 0)
511     {
512       char str[512] = { 0 };
513       req.tcUrl.av_len = snprintf(str, 511, "%s://%.*s:%d/%.*s",
514 	RTMPProtocolStringsLower[req.protocol], req.hostname.av_len,
515 	req.hostname.av_val, req.rtmpport, req.app.av_len, req.app.av_val);
516       req.tcUrl.av_val = (char *) malloc(req.tcUrl.av_len + 1);
517       strcpy(req.tcUrl.av_val, str);
518     }
519 
520   if (req.swfVfy)
521     {
522 #ifdef CRYPTO
523         if (RTMP_HashSWF(req.swfUrl.av_val, &req.swfSize, req.hash, req.swfAge) == 0)
524           {
525             req.swfHash.av_val = (char *)req.hash;
526             req.swfHash.av_len = RTMP_SWF_HASHLEN;
527           }
528 #endif
529     }
530 
531   // after validation of the http request send response header
532   len = sprintf(buf, "HTTP/1.0 200 OK%sContent-Type: video/flv\r\n\r\n", srvhead);
533   send(sockfd, buf, len, 0);
534 
535   // send the packets
536   buffer = (char *) calloc(PACKET_SIZE, 1);
537 
538   // User defined seek offset
539   if (req.dStartOffset > 0)
540     {
541       if (req.bLiveStream)
542 	RTMP_Log(RTMP_LOGWARNING,
543 	    "Can't seek in a live stream, ignoring --seek option");
544       else
545 	dSeek += req.dStartOffset;
546     }
547 
548   if (dSeek != 0)
549     {
550       RTMP_LogPrintf("Starting at TS: %d ms\n", dSeek);
551     }
552 
553   RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", req.bufferTime);
554   RTMP_Init(&rtmp);
555   RTMP_SetBufferMS(&rtmp, req.bufferTime);
556   if (!req.fullUrl.av_len)
557     {
558       RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost,
559 		       &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, dSeek, req.dStopOffset,
560 		       req.bLiveStream, req.timeout);
561     }
562   else
563     {
564       if (RTMP_SetupURL(&rtmp, req.fullUrl.av_val) == FALSE)
565         {
566           RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", req.fullUrl.av_val);
567           return;
568         }
569     }
570   /* backward compatibility, we always sent this as true before */
571   if (req.auth.av_len)
572     rtmp.Link.lFlags |= RTMP_LF_AUTH;
573 
574   rtmp.Link.extras = req.extras;
575   rtmp.Link.token = req.token;
576   rtmp.m_read.timestamp = dSeek;
577 
578   RTMP_LogPrintf("Connecting ... port: %d, app: %s\n", req.rtmpport, req.app.av_val);
579   if (!RTMP_Connect(&rtmp, NULL))
580     {
581       RTMP_LogPrintf("%s, failed to connect!\n", __FUNCTION__);
582     }
583   else
584     {
585       unsigned long size = 0;
586       double percent = 0;
587       double duration = 0.0;
588 
589       int nWritten = 0;
590       int nRead = 0;
591 
592       do
593 	{
594 	  nRead = RTMP_Read(&rtmp, buffer, PACKET_SIZE);
595 
596 	  if (nRead > 0)
597 	    {
598 	      if ((nWritten = send(sockfd, buffer, nRead, 0)) < 0)
599 		{
600 		  RTMP_Log(RTMP_LOGERROR, "%s, sending failed, error: %d", __FUNCTION__,
601 		      GetSockError());
602 		  goto cleanup;	// we are in STREAMING_IN_PROGRESS, so we'll go to STREAMING_ACCEPTING
603 		}
604 
605 	      size += nRead;
606 
607 	      //RTMP_LogPrintf("write %dbytes (%.1f KB)\n", nRead, nRead/1024.0);
608 	      if (duration <= 0)	// if duration unknown try to get it from the stream (onMetaData)
609 		duration = RTMP_GetDuration(&rtmp);
610 
611 	      if (duration > 0)
612 		{
613 		  percent =
614 		    ((double) (dSeek + rtmp.m_read.timestamp)) / (duration *
615 							   1000.0) * 100.0;
616 		  percent = ((double) (int) (percent * 10.0)) / 10.0;
617 		  RTMP_LogStatus("\r%.3f KB / %.2f sec (%.1f%%)",
618 			    (double) size / 1024.0,
619 			    (double) (rtmp.m_read.timestamp) / 1000.0, percent);
620 		}
621 	      else
622 		{
623 		  RTMP_LogStatus("\r%.3f KB / %.2f sec", (double) size / 1024.0,
624 			    (double) (rtmp.m_read.timestamp) / 1000.0);
625 		}
626 	    }
627 #ifdef _DEBUG
628 	  else
629 	    {
630 	      RTMP_Log(RTMP_LOGDEBUG, "zero read!");
631 	    }
632 #endif
633 	}
634       while (server->state == STREAMING_IN_PROGRESS && nRead > -1
635 	     && RTMP_IsConnected(&rtmp) && nWritten >= 0);
636     }
637 cleanup:
638   RTMP_LogPrintf("Closing connection... ");
639   RTMP_Close(&rtmp);
640   RTMP_LogPrintf("done!\n\n");
641 
642 quit:
643   if (buffer)
644     {
645       free(buffer);
646       buffer = NULL;
647     }
648 
649   if (sockfd)
650     closesocket(sockfd);
651 
652   if (server->state == STREAMING_IN_PROGRESS)
653     server->state = STREAMING_ACCEPTING;
654 
655   return;
656 
657 filenotfound:
658   RTMP_LogPrintf("%s, %s, %s\n", __FUNCTION__, status, filename);
659   len = sprintf(buf, "HTTP/1.0 %s%s\r\n", status, srvhead);
660   send(sockfd, buf, len, 0);
661   goto quit;
662 }
663 
664 TFTYPE
serverThread(void * arg)665 serverThread(void *arg)
666 {
667   STREAMING_SERVER *server = arg;
668   server->state = STREAMING_ACCEPTING;
669 
670   while (server->state == STREAMING_ACCEPTING)
671     {
672       struct sockaddr_in addr;
673       socklen_t addrlen = sizeof(struct sockaddr_in);
674       int sockfd =
675 	accept(server->socket, (struct sockaddr *) &addr, &addrlen);
676 
677       if (sockfd > 0)
678 	{
679 	  // Create a new process and transfer the control to that
680 	  RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__,
681 	      inet_ntoa(addr.sin_addr));
682 	  processTCPrequest(server, sockfd);
683 	  RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__);
684 	}
685       else
686 	{
687 	  RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
688 	}
689     }
690   server->state = STREAMING_STOPPED;
691   TFRET();
692 }
693 
694 STREAMING_SERVER *
startStreaming(const char * address,int port)695 startStreaming(const char *address, int port)
696 {
697   struct sockaddr_in addr;
698   int sockfd;
699   STREAMING_SERVER *server;
700 
701   sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
702   if (sockfd == -1)
703     {
704       RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__);
705       return 0;
706     }
707 
708   addr.sin_family = AF_INET;
709   addr.sin_addr.s_addr = inet_addr(address);	//htonl(INADDR_ANY);
710   addr.sin_port = htons(port);
711 
712   if (bind(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) ==
713       -1)
714     {
715       RTMP_Log(RTMP_LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__,
716 	  port);
717       return 0;
718     }
719 
720   if (listen(sockfd, 10) == -1)
721     {
722       RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__);
723       closesocket(sockfd);
724       return 0;
725     }
726 
727   server = (STREAMING_SERVER *) calloc(1, sizeof(STREAMING_SERVER));
728   server->socket = sockfd;
729 
730   ThreadCreate(serverThread, server);
731 
732   return server;
733 }
734 
735 void
stopStreaming(STREAMING_SERVER * server)736 stopStreaming(STREAMING_SERVER * server)
737 {
738   assert(server);
739 
740   if (server->state != STREAMING_STOPPED)
741     {
742       if (server->state == STREAMING_IN_PROGRESS)
743 	{
744 	  server->state = STREAMING_STOPPING;
745 
746 	  // wait for streaming threads to exit
747 	  while (server->state != STREAMING_STOPPED)
748 	    msleep(1);
749 	}
750 
751       if (closesocket(server->socket))
752 	RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d",
753 	    __FUNCTION__, GetSockError());
754 
755       server->state = STREAMING_STOPPED;
756     }
757 }
758 
759 
760 void
sigIntHandler(int sig)761 sigIntHandler(int sig)
762 {
763   RTMP_ctrlC = TRUE;
764   RTMP_LogPrintf("Caught signal: %d, cleaning up, just a second...\n", sig);
765   if (httpServer)
766     stopStreaming(httpServer);
767   signal(SIGINT, SIG_DFL);
768 }
769 
770 #define HEX2BIN(a)      (((a)&0x40)?((a)&0xf)+9:((a)&0xf))
hex2bin(char * str,char ** hex)771 int hex2bin(char *str, char **hex)
772 {
773   char *ptr;
774   int i, l = strlen(str);
775 
776   if (l & 1)
777   	return 0;
778 
779   *hex = malloc(l/2);
780   ptr = *hex;
781   if (!ptr)
782     return 0;
783 
784   for (i=0; i<l; i+=2)
785     *ptr++ = (HEX2BIN(str[i]) << 4) | HEX2BIN(str[i+1]);
786   return l/2;
787 }
788 
789 // this will parse RTMP related options as needed
790 // excludes the following options: h, d, g
791 
792 // Return values: true (option parsing ok)
793 //                false (option not parsed/invalid)
794 int
ParseOption(char opt,char * arg,RTMP_REQUEST * req)795 ParseOption(char opt, char *arg, RTMP_REQUEST * req)
796 {
797   switch (opt)
798     {
799 #ifdef CRYPTO
800     case 'w':
801       {
802 	int res = hex2bin(arg, &req->swfHash.av_val);
803 	if (!res || res != RTMP_SWF_HASHLEN)
804 	  {
805 	    req->swfHash.av_val = NULL;
806 	    RTMP_Log(RTMP_LOGWARNING,
807 		"Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
808 	  }
809 	req->swfHash.av_len = RTMP_SWF_HASHLEN;
810 	break;
811       }
812     case 'x':
813       {
814 	int size = atoi(arg);
815 	if (size <= 0)
816 	  {
817 	    RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
818 	  }
819 	else
820 	  {
821 	    req->swfSize = size;
822 	  }
823 	break;
824       }
825     case 'W':
826       {
827         STR2AVAL(req->swfUrl, arg);
828         req->swfVfy = 1;
829       }
830       break;
831     case 'X':
832       {
833 	int num = atoi(arg);
834 	if (num < 0)
835 	  {
836 	    RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
837 	  }
838 	else
839 	  {
840 	    req->swfAge = num;
841 	  }
842 	break;
843       }
844 #endif
845     case 'b':
846       {
847 	int32_t bt = atol(arg);
848 	if (bt < 0)
849 	  {
850 	    RTMP_Log(RTMP_LOGERROR,
851 		"Buffer time must be greater than zero, ignoring the specified value %d!",
852 		bt);
853 	  }
854 	else
855 	  {
856 	    req->bufferTime = bt;
857 	  }
858 	break;
859       }
860     case 'v':
861       req->bLiveStream = TRUE;	// no seeking or resuming possible!
862       break;
863     case 'd':
864       STR2AVAL(req->subscribepath, arg);
865       break;
866     case 'n':
867       STR2AVAL(req->hostname, arg);
868       break;
869     case 'c':
870       req->rtmpport = atoi(arg);
871       break;
872     case 'l':
873       {
874 	int protocol = atoi(arg);
875 	if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
876 	  {
877 	    RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d, using default",
878 		protocol);
879 	    return FALSE;
880 	  }
881 	else
882 	  {
883 	    req->protocol = protocol;
884 	  }
885 	break;
886       }
887     case 'y':
888       STR2AVAL(req->playpath, arg);
889       break;
890     case 'r':
891       {
892 	req->rtmpurl = arg;
893 
894 	AVal parsedHost, parsedPlaypath, parsedApp;
895 	unsigned int parsedPort = 0;
896 	int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
897 
898 	if (!RTMP_ParseURL
899 	    (req->rtmpurl, &parsedProtocol, &parsedHost, &parsedPort,
900 	     &parsedPlaypath, &parsedApp))
901 	  {
902 	    RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!", arg);
903 	  }
904 	else
905 	  {
906 	    if (!req->hostname.av_len)
907 	      req->hostname = parsedHost;
908 	    if (req->rtmpport == -1)
909 	      req->rtmpport = parsedPort;
910 	    if (req->playpath.av_len == 0 && parsedPlaypath.av_len)
911 	      {
912 		    req->playpath = parsedPlaypath;
913 	      }
914 	    if (req->protocol == RTMP_PROTOCOL_UNDEFINED)
915 	      req->protocol = parsedProtocol;
916 	    if (req->app.av_len == 0 && parsedApp.av_len)
917 	      {
918 		    req->app = parsedApp;
919 	      }
920 	  }
921 	break;
922       }
923     case 'i':
924       STR2AVAL(req->fullUrl, arg);
925       break;
926     case 's':
927       STR2AVAL(req->swfUrl, arg);
928       break;
929     case 't':
930       STR2AVAL(req->tcUrl, arg);
931       break;
932     case 'p':
933       STR2AVAL(req->pageUrl, arg);
934       break;
935     case 'a':
936       STR2AVAL(req->app, arg);
937       break;
938     case 'f':
939       STR2AVAL(req->flashVer, arg);
940       break;
941     case 'u':
942       STR2AVAL(req->auth, arg);
943       break;
944     case 'C':
945       parseAMF(&req->extras, arg, &req->edepth);
946       break;
947     case 'm':
948       req->timeout = atoi(arg);
949       break;
950     case 'A':
951       req->dStartOffset = (int)(atof(arg) * 1000.0);
952       //printf("dStartOffset = %d\n", dStartOffset);
953       break;
954     case 'B':
955       req->dStopOffset = (int)(atof(arg) * 1000.0);
956       //printf("dStartOffset = %d\n", dStartOffset);
957       break;
958     case 'T':
959       STR2AVAL(req->token, arg);
960       break;
961     case 'S':
962       STR2AVAL(req->sockshost, arg);
963     case 'q':
964       RTMP_debuglevel = RTMP_LOGCRIT;
965       break;
966     case 'V':
967       RTMP_debuglevel = RTMP_LOGDEBUG;
968       break;
969     case 'z':
970       RTMP_debuglevel = RTMP_LOGALL;
971       break;
972     case 'j':
973       STR2AVAL(req->usherToken, arg);
974       break;
975     default:
976       RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg);
977       return FALSE;
978     }
979   return TRUE;
980 }
981 
982 int
main(int argc,char ** argv)983 main(int argc, char **argv)
984 {
985   int nStatus = RD_SUCCESS;
986 
987   // http streaming server
988   char DEFAULT_HTTP_STREAMING_DEVICE[] = "0.0.0.0";	// 0.0.0.0 is any device
989 
990   char *httpStreamingDevice = DEFAULT_HTTP_STREAMING_DEVICE;	// streaming device, default 0.0.0.0
991   int nHttpStreamingPort = 80;	// port
992 
993   RTMP_LogPrintf("HTTP-RTMP Stream Gateway %s\n", RTMPDUMP_VERSION);
994   RTMP_LogPrintf("(c) 2010 Andrej Stepanchuk, Howard Chu; license: GPL\n\n");
995 
996   // init request
997   memset(&defaultRTMPRequest, 0, sizeof(RTMP_REQUEST));
998 
999   defaultRTMPRequest.rtmpport = -1;
1000   defaultRTMPRequest.protocol = RTMP_PROTOCOL_UNDEFINED;
1001   defaultRTMPRequest.bLiveStream = FALSE;	// is it a live stream? then we can't seek/resume
1002 
1003   defaultRTMPRequest.timeout = 120;	// timeout connection after 120 seconds
1004   defaultRTMPRequest.bufferTime = 20 * 1000;
1005 
1006   defaultRTMPRequest.swfAge = 30;
1007 
1008   int opt;
1009   struct option longopts[] = {
1010     {"help", 0, NULL, 'h'},
1011     {"url", 1, NULL, 'i'},
1012     {"host", 1, NULL, 'n'},
1013     {"port", 1, NULL, 'c'},
1014     {"socks", 1, NULL, 'S'},
1015     {"protocol", 1, NULL, 'l'},
1016     {"playpath", 1, NULL, 'y'},
1017     {"rtmp", 1, NULL, 'r'},
1018     {"swfUrl", 1, NULL, 's'},
1019     {"tcUrl", 1, NULL, 't'},
1020     {"pageUrl", 1, NULL, 'p'},
1021     {"app", 1, NULL, 'a'},
1022 #ifdef CRYPTO
1023     {"swfhash", 1, NULL, 'w'},
1024     {"swfsize", 1, NULL, 'x'},
1025     {"swfVfy", 1, NULL, 'W'},
1026     {"swfAge", 1, NULL, 'X'},
1027 #endif
1028     {"auth", 1, NULL, 'u'},
1029     {"conn", 1, NULL, 'C'},
1030     {"flashVer", 1, NULL, 'f'},
1031     {"live", 0, NULL, 'v'},
1032     //{"flv",     1, NULL, 'o'},
1033     //{"resume",  0, NULL, 'e'},
1034     {"timeout", 1, NULL, 'm'},
1035     {"buffer", 1, NULL, 'b'},
1036     //{"skip",    1, NULL, 'k'},
1037     {"device", 1, NULL, 'D'},
1038     {"sport", 1, NULL, 'g'},
1039     {"subscribe", 1, NULL, 'd'},
1040     {"start", 1, NULL, 'A'},
1041     {"stop", 1, NULL, 'B'},
1042     {"token", 1, NULL, 'T'},
1043     {"debug", 0, NULL, 'z'},
1044     {"quiet", 0, NULL, 'q'},
1045     {"verbose", 0, NULL, 'V'},
1046     {"jtv", 1, NULL, 'j'},
1047     {0, 0, 0, 0}
1048   };
1049 
1050   signal(SIGINT, sigIntHandler);
1051 #ifndef WIN32
1052   signal(SIGPIPE, SIG_IGN);
1053 #endif
1054 
1055   InitSockets();
1056 
1057   while ((opt =
1058 	  getopt_long(argc, argv,
1059 		      "hvqVzr:s:t:i:p:a:f:u:n:c:l:y:m:d:D:A:B:T:g:w:x:W:X:S:j:", longopts,
1060 		      NULL)) != -1)
1061     {
1062       switch (opt)
1063 	{
1064 	case 'h':
1065 	  RTMP_LogPrintf
1066 	    ("\nThis program serves media content streamed from RTMP onto HTTP.\n\n");
1067 	  RTMP_LogPrintf("--help|-h               Prints this help screen.\n");
1068 	  RTMP_LogPrintf
1069 	    ("--url|-i url            URL with options included (e.g. rtmp://host[:port]/path swfUrl=url tcUrl=url)\n");
1070 	  RTMP_LogPrintf
1071 	    ("--rtmp|-r url           URL (e.g. rtmp://host[:port]/path)\n");
1072 	  RTMP_LogPrintf
1073 	    ("--host|-n hostname      Overrides the hostname in the rtmp url\n");
1074 	  RTMP_LogPrintf
1075 	    ("--port|-c port          Overrides the port in the rtmp url\n");
1076 	  RTMP_LogPrintf
1077 	    ("--socks|-S host:port    Use the specified SOCKS proxy\n");
1078 	  RTMP_LogPrintf
1079 	    ("--protocol|-l           Overrides the protocol in the rtmp url (0 - RTMP, 2 - RTMPE)\n");
1080 	  RTMP_LogPrintf
1081 	    ("--playpath|-y           Overrides the playpath parsed from rtmp url\n");
1082 	  RTMP_LogPrintf("--swfUrl|-s url         URL to player swf file\n");
1083 	  RTMP_LogPrintf
1084 	    ("--tcUrl|-t url          URL to played stream (default: \"rtmp://host[:port]/app\")\n");
1085 	  RTMP_LogPrintf("--pageUrl|-p url        Web URL of played programme\n");
1086 	  RTMP_LogPrintf("--app|-a app            Name of target app in server\n");
1087 #ifdef CRYPTO
1088 	  RTMP_LogPrintf
1089 	    ("--swfhash|-w hexstring  SHA256 hash of the decompressed SWF file (32 bytes)\n");
1090 	  RTMP_LogPrintf
1091 	    ("--swfsize|-x num        Size of the decompressed SWF file, required for SWFVerification\n");
1092 	  RTMP_LogPrintf
1093 	    ("--swfVfy|-W url         URL to player swf file, compute hash/size automatically\n");
1094 	  RTMP_LogPrintf
1095 	    ("--swfAge|-X days        Number of days to use cached SWF hash before refreshing\n");
1096 #endif
1097 	  RTMP_LogPrintf
1098 	    ("--auth|-u string        Authentication string to be appended to the connect string\n");
1099 	  RTMP_LogPrintf
1100 	    ("--conn|-C type:data     Arbitrary AMF data to be appended to the connect string\n");
1101 	  RTMP_LogPrintf
1102 	    ("                        B:boolean(0|1), S:string, N:number, O:object-flag(0|1),\n");
1103 	  RTMP_LogPrintf
1104 	    ("                        Z:(null), NB:name:boolean, NS:name:string, NN:name:number\n");
1105 	  RTMP_LogPrintf
1106 	    ("--flashVer|-f string    Flash version string (default: \"%s\")\n",
1107 	     RTMP_DefaultFlashVer.av_val);
1108 	  RTMP_LogPrintf
1109 	    ("--live|-v               Get a live stream, no --resume (seeking) of live streams possible\n");
1110 	  RTMP_LogPrintf
1111 	    ("--subscribe|-d string   Stream name to subscribe to (otherwise defaults to playpath if live is specified)\n");
1112 	  RTMP_LogPrintf
1113 	    ("--timeout|-m num        Timeout connection num seconds (default: %lu)\n",
1114 	     defaultRTMPRequest.timeout);
1115 	  RTMP_LogPrintf
1116 	    ("--start|-A num          Start at num seconds into stream (not valid when using --live)\n");
1117 	  RTMP_LogPrintf
1118 	    ("--stop|-B num           Stop at num seconds into stream\n");
1119 	  RTMP_LogPrintf
1120 	    ("--token|-T key          Key for SecureToken response\n");
1121 	  RTMP_LogPrintf
1122 	    ("--jtv|-j JSON           Authentication token for Justin.tv legacy servers\n");
1123 	  RTMP_LogPrintf
1124 	    ("--buffer|-b             Buffer time in milliseconds (default: %u)\n\n",
1125 	     defaultRTMPRequest.bufferTime);
1126 
1127 	  RTMP_LogPrintf
1128 	    ("--device|-D             Streaming device ip address (default: %s)\n",
1129 	     DEFAULT_HTTP_STREAMING_DEVICE);
1130 	  RTMP_LogPrintf
1131 	    ("--sport|-g              Streaming port (default: %d)\n\n",
1132 	     nHttpStreamingPort);
1133 	  RTMP_LogPrintf
1134 	    ("--quiet|-q              Suppresses all command output.\n");
1135 	  RTMP_LogPrintf("--verbose|-V            Verbose command output.\n");
1136 	  RTMP_LogPrintf("--debug|-z              Debug level command output.\n");
1137 	  RTMP_LogPrintf
1138 	    ("If you don't pass parameters for swfUrl, pageUrl, or auth these properties will not be included in the connect ");
1139 	  RTMP_LogPrintf("packet.\n\n");
1140 	  return RD_SUCCESS;
1141 	  break;
1142 	  // streaming server specific options
1143 	case 'D':
1144 	  if (inet_addr(optarg) == INADDR_NONE)
1145 	    {
1146 	      RTMP_Log(RTMP_LOGERROR,
1147 		  "Invalid binding address (requested address %s), ignoring",
1148 		  optarg);
1149 	    }
1150 	  else
1151 	    {
1152 	      httpStreamingDevice = optarg;
1153 	    }
1154 	  break;
1155 	case 'g':
1156 	  {
1157 	    int port = atoi(optarg);
1158 	    if (port < 0 || port > 65535)
1159 	      {
1160 		RTMP_Log(RTMP_LOGERROR,
1161 		    "Streaming port out of range (requested port %d), ignoring\n",
1162 		    port);
1163 	      }
1164 	    else
1165 	      {
1166 		nHttpStreamingPort = port;
1167 	      }
1168 	    break;
1169 	  }
1170 	default:
1171 	  //RTMP_LogPrintf("unknown option: %c\n", opt);
1172 	  if (!ParseOption(opt, optarg, &defaultRTMPRequest))
1173 	    return RD_FAILED;
1174 	  break;
1175 	}
1176     }
1177 
1178 #ifdef _DEBUG
1179   netstackdump = fopen("netstackdump", "wb");
1180   netstackdump_read = fopen("netstackdump_read", "wb");
1181 #endif
1182 
1183   // start text UI
1184   ThreadCreate(controlServerThread, 0);
1185 
1186   // start http streaming
1187   if ((httpServer =
1188        startStreaming(httpStreamingDevice, nHttpStreamingPort)) == 0)
1189     {
1190       RTMP_Log(RTMP_LOGERROR, "Failed to start HTTP server, exiting!");
1191       return RD_FAILED;
1192     }
1193   RTMP_LogPrintf("Streaming on http://%s:%d\n", httpStreamingDevice,
1194 	    nHttpStreamingPort);
1195 
1196   while (httpServer->state != STREAMING_STOPPED)
1197     {
1198       sleep(1);
1199     }
1200   RTMP_Log(RTMP_LOGDEBUG, "Done, exiting...");
1201 
1202   CleanupSockets();
1203 
1204 #ifdef _DEBUG
1205   if (netstackdump != 0)
1206     fclose(netstackdump);
1207   if (netstackdump_read != 0)
1208     fclose(netstackdump_read);
1209 #endif
1210   return nStatus;
1211 }
1212