diff --git Makefile Makefile index a1595a8..9fe7584 100644 --- Makefile +++ Makefile @@ -32,7 +32,7 @@ BINDIR=$(DESTDIR)$(bindir) SBINDIR=$(DESTDIR)$(sbindir) MANDIR=$(DESTDIR)$(mandir) -LIBS_posix= +LIBS_posix=-lm LIBS_darwin= LIBS_mingw=-lws2_32 -lwinmm -lgdi32 LIB_RTMP=-Llibrtmp -lrtmp diff --git librtmp/Makefile librtmp/Makefile index 96c076b..835d4ed 100644 --- librtmp/Makefile +++ librtmp/Makefile @@ -25,7 +25,7 @@ DEF_=-DNO_CRYPTO REQ_GNUTLS=gnutls REQ_OPENSSL=libssl,libcrypto LIBZ=-lz -LIBS_posix= +LIBS_posix=-lm LIBS_darwin= LIBS_mingw=-lws2_32 -lwinmm -lgdi32 LIB_GNUTLS=-lgnutls -lhogweed -lnettle -lgmp $(LIBZ) diff --git librtmp/amf.c librtmp/amf.c index 563486c..fb6b149 100644 --- librtmp/amf.c +++ librtmp/amf.c @@ -618,6 +618,9 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, return -1; } + if (*pBuffer == AMF_NULL) + bDecodeName = 0; + if (bDecodeName && nSize < 4) { /* at least name (length + at least 1 byte) and 1 byte of data */ RTMP_Log(RTMP_LOGDEBUG, @@ -708,6 +711,7 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, if (nRes == -1) return -1; nSize -= nRes; + prop->p_type = AMF_OBJECT; break; } case AMF_OBJECT_END: @@ -725,6 +729,7 @@ AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, if (nRes == -1) return -1; nSize -= nRes; + prop->p_type = AMF_OBJECT; break; } case AMF_DATE: @@ -807,8 +812,8 @@ AMFProp_Dump(AMFObjectProperty *prop) } else { - name.av_val = "no-name."; - name.av_len = sizeof("no-name.") - 1; + name.av_val = "no-name"; + name.av_len = sizeof("no-name") - 1; } if (name.av_len > 18) name.av_len = 18; diff --git librtmp/handshake.h librtmp/handshake.h index 0438486..102ba82 100644 --- librtmp/handshake.h +++ librtmp/handshake.h @@ -965,8 +965,18 @@ HandShake(RTMP * r, int FP9HandShake) __FUNCTION__); RTMP_LogHex(RTMP_LOGDEBUG, reply, RTMP_SIG_SIZE); #endif - if (!WriteN(r, (char *)reply, RTMP_SIG_SIZE)) - return FALSE; + if (r->Link.CombineConnectPacket) + { + char *HandshakeResponse = malloc(RTMP_SIG_SIZE); + memcpy(HandshakeResponse, (char *) reply, RTMP_SIG_SIZE); + r->Link.HandshakeResponse.av_val = HandshakeResponse; + r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE; + } + else + { + if (!WriteN(r, (char *) reply, RTMP_SIG_SIZE)) + return FALSE; + } /* 2nd part of handshake */ if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) diff --git librtmp/hashswf.c librtmp/hashswf.c index 9f4e2c0..710859f 100644 --- librtmp/hashswf.c +++ librtmp/hashswf.c @@ -70,7 +70,7 @@ extern TLS_CTX RTMP_TLS_ctx; #endif /* CRYPTO */ -#define AGENT "Mozilla/5.0" +#define AGENT "Mozilla/5.0 (Windows NT 5.1; rv:15.0) Gecko/20100101 Firefox/15.0.1" HTTPResult HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) @@ -528,7 +528,7 @@ RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash, if (strncmp(buf, "url: ", 5)) continue; - if (strncmp(buf + 5, url, hlen)) + if (strncmp(buf + 5, url, strlen(buf + 5) - 1)) continue; r1 = strrchr(buf, '/'); i = strlen(r1); diff --git librtmp/log.c librtmp/log.c index 0012985..856e3e4 100644 --- librtmp/log.c +++ librtmp/log.c @@ -52,8 +52,8 @@ static void rtmp_log_default(int level, const char *format, va_list vl) vsnprintf(str, MAX_PRINT_LEN-1, format, vl); /* Filter out 'no-name' */ - if ( RTMP_debuglevel #include #include +#include #include "rtmp_sys.h" #include "log.h" @@ -68,6 +69,7 @@ TLS_CTX RTMP_TLS_ctx; #define RTMP_SIG_SIZE 1536 #define RTMP_LARGE_HEADER_SIZE 12 +#define HEX2BIN(a) (((a)&0x40)?((a)&0xf)+9:((a)&0xf)) static const int packetSize[] = { 12, 8, 4, 1 }; @@ -120,6 +122,10 @@ static int SendFCSubscribe(RTMP *r, AVal *subscribepath); static int SendPlay(RTMP *r); static int SendBytesReceived(RTMP *r); static int SendUsherToken(RTMP *r, AVal *usherToken); +static int SendInvoke(RTMP *r, AVal *Command, int queue); +static int SendGetStreamLength(RTMP *r); +static int strsplit(char *src, int srclen, char delim, char ***params); +static int ConnectSocket(RTMP *r); #if 0 /* unused */ static int SendBGHasStream(RTMP *r, double dId, AVal *playpath); @@ -336,10 +342,12 @@ RTMP_Init(RTMP *r) r->m_nClientBW = 2500000; r->m_nClientBW2 = 2; r->m_nServerBW = 2500000; - r->m_fAudioCodecs = 3191.0; + r->m_fAudioCodecs = 3575.0; r->m_fVideoCodecs = 252.0; r->Link.timeout = 30; r->Link.swfAge = 30; + r->Link.CombineConnectPacket = TRUE; + r->Link.ConnectPacket = FALSE; } void @@ -443,6 +451,7 @@ RTMP_SetupStream(RTMP *r, AVal *flashVer, AVal *subscribepath, AVal *usherToken, + AVal *WeebToken, int dStart, int dStop, int bLiveStream, long int timeout) { @@ -465,6 +474,8 @@ RTMP_SetupStream(RTMP *r, RTMP_Log(RTMP_LOGDEBUG, "subscribepath : %s", subscribepath->av_val); if (usherToken && usherToken->av_val) RTMP_Log(RTMP_LOGDEBUG, "NetStream.Authenticate.UsherToken : %s", usherToken->av_val); + if (WeebToken && WeebToken->av_val) + RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", WeebToken->av_val); if (flashVer && flashVer->av_val) RTMP_Log(RTMP_LOGDEBUG, "flashVer : %s", flashVer->av_val); if (dStart > 0) @@ -513,6 +524,8 @@ RTMP_SetupStream(RTMP *r, r->Link.subscribepath = *subscribepath; if (usherToken && usherToken->av_len) r->Link.usherToken = *usherToken; + if (WeebToken && WeebToken->av_len) + r->Link.WeebToken = *WeebToken; r->Link.seekTime = dStart; r->Link.stopTime = dStop; if (bLiveStream) @@ -570,14 +583,22 @@ static struct urlopt { "Stream is live, no seeking possible" }, { AVC("subscribe"), OFF(Link.subscribepath), OPT_STR, 0, "Stream to subscribe to" }, - { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0, - "Justin.tv authentication token" }, - { AVC("token"), OFF(Link.token), OPT_STR, 0, + { AVC("jtv"), OFF(Link.usherToken), OPT_STR, 0, + "Justin.tv authentication token"}, + { AVC("weeb"), OFF(Link.WeebToken), OPT_STR, 0, + "Weeb.tv authentication token"}, + { AVC("token"), OFF(Link.token), OPT_STR, 0, "Key for SecureToken response" }, { AVC("swfVfy"), OFF(Link.lFlags), OPT_BOOL, RTMP_LF_SWFV, "Perform SWF Verification" }, { AVC("swfAge"), OFF(Link.swfAge), OPT_INT, 0, "Number of days to use cached SWF hash" }, +#ifdef CRYPTO + { AVC("swfsize"), OFF(Link.swfSize), OPT_INT, 0, + "Size of the decompressed SWF file"}, + { AVC("swfhash"), OFF(Link.swfHash), OPT_STR, 0, + "SHA256 hash of the decompressed SWF file"}, +#endif { AVC("start"), OFF(Link.seekTime), OPT_INT, 0, "Stream start position in milliseconds" }, { AVC("stop"), OFF(Link.stopTime), OPT_INT, 0, @@ -842,9 +863,16 @@ int RTMP_SetupURL(RTMP *r, char *url) } #ifdef CRYPTO - if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len) - RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, - (unsigned char *)r->Link.SWFHash, r->Link.swfAge); + RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %d %d %s", r->Link.swfSize, r->Link.swfHash.av_len, r->Link.swfHash.av_val); + if (r->Link.swfSize && r->Link.swfHash.av_len) + { + int i, j = 0; + for (i = 0; i < r->Link.swfHash.av_len; i += 2) + r->Link.SWFHash[j++] = (HEX2BIN(r->Link.swfHash.av_val[i]) << 4) | HEX2BIN(r->Link.swfHash.av_val[i + 1]); + r->Link.SWFSize = (uint32_t) r->Link.swfSize; + } + else if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len) + RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, (unsigned char *) r->Link.SWFHash, r->Link.swfAge); #endif SocksSetup(r, &r->Link.sockshost); @@ -947,6 +975,8 @@ RTMP_Connect0(RTMP *r, struct sockaddr * service) } setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on)); + if (r->Link.protocol & RTMP_FEATURE_HTTP) + setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on)); return TRUE; } @@ -1397,41 +1427,90 @@ ReadN(RTMP *r, char *buffer, int n) ptr = buffer; while (n > 0) { - int nBytes = 0, nRead; + int nBytes = 0, nRead, status = 0, retries = 0; if (r->Link.protocol & RTMP_FEATURE_HTTP) { - int refill = 0; - while (!r->m_resplen) - { - int ret; - if (r->m_sb.sb_size < 13 || refill) - { - if (!r->m_unackd) - HTTP_Post(r, RTMPT_IDLE, "", 1); - if (RTMPSockBuf_Fill(&r->m_sb) < 1) - { - if (!r->m_sb.sb_timedout) - RTMP_Close(r); - return 0; - } - } - if ((ret = HTTP_read(r, 0)) == -1) - { - RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__); - RTMP_Close(r); - return 0; - } - else if (ret == -2) + while (!r->m_resplen) + { + /* Refill if socket buffer is empty */ + if (!r->m_sb.sb_size) { - refill = 1; + if (!r->m_unackd) + { + retries++; + if (retries > 30) + { + RTMP_Close(r); + return 0; + } + HTTP_Post(r, RTMPT_IDLE, "", 1); + r->m_unackd = TRUE; + if (!r->m_bPlaying) + sleep(.25); + } + + RTMP_Log(RTMP_LOGDEBUG, "Trying to fill HTTP buffer, Retries: %d", retries); + status = RTMPSockBuf_Fill(&r->m_sb); + /* Reconnect socket when closed by some moronic servers after + * every HTTP data packet */ + if (status < 1) + { + /* Close connection on connection reset */ + if (status == -1) + { + RTMP_Close(r); + return 0; + } + + RTMP_Log(RTMP_LOGDEBUG, "Reconnecting socket, Status: %d", status); + if (ConnectSocket(r)) + { + HTTP_Post(r, RTMPT_IDLE, "", 1); + r->m_unackd = TRUE; + } + else + { + RTMP_Close(r); + return 0; + } + } } - else + + RTMP_Log(RTMP_LOGDEBUG, "Trying to read HTTP response, Bytes Available: %d", r->m_sb.sb_size); + status = HTTP_read(r, 0); + if (status == -1) { - refill = 0; + RTMP_Log(RTMP_LOGDEBUG, "%s, No valid HTTP response found", __FUNCTION__); + RTMP_Close(r); + return 0; } - } - if (r->m_resplen && !r->m_sb.sb_size) - RTMPSockBuf_Fill(&r->m_sb); + else if (status == -2) + { + if (RTMPSockBuf_Fill(&r->m_sb) < 1) + if (!r->m_sb.sb_timedout) + { + RTMP_Close(r); + return 0; + } + } + else if (status == -3) + { + RTMP_Close(r); + return 0; + } + else + r->m_unackd = FALSE; + } + + /* Refill when there is still some data to be read and socket buffer + * is empty */ + if (r->m_resplen && (!r->m_sb.sb_size)) + { + if (RTMPSockBuf_Fill(&r->m_sb) < 1) + if (!r->m_sb.sb_timedout) + RTMP_Close(r); + } + avail = r->m_sb.sb_size; if (avail > r->m_resplen) avail = r->m_resplen; @@ -1458,10 +1537,9 @@ ReadN(RTMP *r, char *buffer, int n) r->m_sb.sb_size -= nRead; nBytes = nRead; r->m_nBytesIn += nRead; - if (r->m_bSendCounter - && r->m_nBytesIn > ( r->m_nBytesInSent + r->m_nClientBW / 10)) - if (!SendBytesReceived(r)) - return FALSE; + if (r->m_bSendCounter && r->m_nBytesIn > (r->m_nBytesInSent + r->m_nClientBW / 10)) + if (!SendBytesReceived(r)) + return FALSE; } /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */ #ifdef _DEBUG @@ -1472,7 +1550,8 @@ ReadN(RTMP *r, char *buffer, int n) { RTMP_Log(RTMP_LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__); /*goto again; */ - RTMP_Close(r); + if (!r->m_sb.sb_timedout) + RTMP_Close(r); break; } @@ -1512,6 +1591,16 @@ WriteN(RTMP *r, const char *buffer, int n) } #endif + if (r->Link.ConnectPacket) + { + char *ConnectPacket = malloc(r->Link.HandshakeResponse.av_len + n); + memcpy(ConnectPacket, r->Link.HandshakeResponse.av_val, r->Link.HandshakeResponse.av_len); + memcpy(ConnectPacket + r->Link.HandshakeResponse.av_len, ptr, n); + ptr = ConnectPacket; + n += r->Link.HandshakeResponse.av_len; + r->Link.ConnectPacket = FALSE; + } + while (n > 0) { int nBytes; @@ -1577,6 +1666,9 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp) char pbuf[4096], *pend = pbuf + sizeof(pbuf); char *enc; + if (r->Link.CombineConnectPacket) + r->Link.ConnectPacket = TRUE; + if (cp) return RTMP_SendPacket(r, cp, TRUE); @@ -1625,7 +1717,7 @@ SendConnectPacket(RTMP *r, RTMPPacket *cp) enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE); if (!enc) return FALSE; - enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0); + enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 239.0); if (!enc) return FALSE; enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs); @@ -1789,7 +1881,7 @@ SendUsherToken(RTMP *r, AVal *usherToken) packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; - RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %s", usherToken->av_val); + RTMP_Log(RTMP_LOGDEBUG, "UsherToken: %.*s", usherToken->av_len, usherToken->av_val); enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_NetStream_Authenticate_UsherToken); enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); @@ -2219,10 +2311,8 @@ SendPlay(RTMP *r) enc = AMF_EncodeNumber(enc, pend, -1000.0); else { - if (r->Link.seekTime > 0.0) - enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */ - else - enc = AMF_EncodeNumber(enc, pend, 0.0); /*-2000.0);*/ /* recorded as default, -2000.0 is not reliable since that freezes the player if the stream is not found */ + if (r->Link.seekTime > 0.0 || r->Link.stopTime) + enc = AMF_EncodeNumber(enc, pend, r->Link.seekTime); /* resume from here */ } if (!enc) return FALSE; @@ -2338,7 +2428,7 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime) int nSize; char *buf; - RTMP_Log(RTMP_LOGDEBUG, "sending ctrl. type: 0x%04x", (unsigned short)nType); + RTMP_Log(RTMP_LOGDEBUG, "sending ctrl, type: 0x%04x", (unsigned short)nType); packet.m_nChannel = 0x02; /* control channel (ping) */ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; @@ -2370,8 +2460,8 @@ RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime) } else if (nType == 0x1A) { - *buf = nObject & 0xff; - } + *buf = nObject & 0xff; + } else { if (nSize > 2) @@ -2874,6 +2964,7 @@ PublisherAuth(RTMP *r, AVal *description) #endif +SAVC(onBWCheck); SAVC(onBWDone); SAVC(onFCSubscribe); SAVC(onFCUnsubscribe); @@ -2888,22 +2979,22 @@ SAVC(onStatus); SAVC(playlist_ready); static const AVal av_NetStream_Failed = AVC("NetStream.Failed"); static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed"); -static const AVal av_NetStream_Play_StreamNotFound = -AVC("NetStream.Play.StreamNotFound"); -static const AVal av_NetConnection_Connect_InvalidApp = -AVC("NetConnection.Connect.InvalidApp"); +static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound"); +static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp"); static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start"); static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete"); static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop"); static const AVal av_NetStream_Seek_Notify = AVC("NetStream.Seek.Notify"); static const AVal av_NetStream_Pause_Notify = AVC("NetStream.Pause.Notify"); -static const AVal av_NetStream_Play_PublishNotify = -AVC("NetStream.Play.PublishNotify"); -static const AVal av_NetStream_Play_UnpublishNotify = -AVC("NetStream.Play.UnpublishNotify"); +static const AVal av_NetStream_Play_PublishNotify = AVC("NetStream.Play.PublishNotify"); +static const AVal av_NetStream_Play_UnpublishNotify = AVC("NetStream.Play.UnpublishNotify"); static const AVal av_NetStream_Publish_Start = AVC("NetStream.Publish.Start"); -static const AVal av_NetConnection_Connect_Rejected = -AVC("NetConnection.Connect.Rejected"); +static const AVal av_NetConnection_Connect_Rejected = AVC("NetConnection.Connect.Rejected"); +static const AVal av_NetConnection_confStream = AVC("NetConnection.confStream"); +static const AVal av_verifyClient = AVC("verifyClient"); +static const AVal av_sendStatus = AVC("sendStatus"); +static const AVal av_getStreamLength = AVC("getStreamLength"); +static const AVal av_cps = AVC("cps"); /* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */ static int @@ -2913,6 +3004,11 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) AVal method; double txn; int ret = 0, nRes; + char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc, **params = NULL; + char *host = r->Link.hostname.av_len ? r->Link.hostname.av_val : ""; + char *pageUrl = r->Link.pageUrl.av_len ? r->Link.pageUrl.av_val : ""; + int param_count; + AVal av_Command, av_Response; if (body[0] != 0x02) /* make sure it is a string method name we start with */ { RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet", @@ -2974,23 +3070,148 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) RTMP_SendServerBW(r); RTMP_SendCtrl(r, 3, 0, 300); } - RTMP_SendCreateStream(r); + if (strstr(host, "tv-stream.to") || strstr(pageUrl, "tv-stream.to")) + { + static char auth[] = {'h', 0xC2, 0xA7, '4', 'j', 'h', 'H', '4', '3', 'd'}; + AVal av_auth; + SAVC(requestAccess); + av_auth.av_val = auth; + av_auth.av_len = sizeof (auth); + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_requestAccess); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + enc = AMF_EncodeString(enc, pend, &av_auth); + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + SendInvoke(r, &av_Command, FALSE); + + SAVC(getConnectionCount); + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_getConnectionCount); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + SendInvoke(r, &av_Command, FALSE); + + SendGetStreamLength(r); + } + else if (strstr(pageUrl, "dhmediahosting.com")) + { + SAVC(netStreamEnable); + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_netStreamEnable); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + SendInvoke(r, &av_Command, FALSE); + + RTMP_SendCreateStream(r); + } + else if (strstr(host, "streamscene.cc") || strstr(pageUrl, "streamscene.cc") + || strstr(host, "tsboard.tv") || strstr(pageUrl, "teamstream.in") + || strstr(host, "hdstreams.tv") || strstr(pageUrl, "teamstream.to") + || strstr(pageUrl, "istreams.to")) + { + SAVC(r); + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_r); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + SendInvoke(r, &av_Command, FALSE); + + SendGetStreamLength(r); + } + else if (strstr(host, "highwebmedia.com") || strstr(pageUrl, "chaturbate.com")) + { + AVal av_ModelName; + SAVC(CheckPublicStatus); - if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) - { - /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */ - if (r->Link.usherToken.av_len) - SendUsherToken(r, &r->Link.usherToken); - /* Send the FCSubscribe if live stream or if subscribepath is set */ - if (r->Link.subscribepath.av_len) - SendFCSubscribe(r, &r->Link.subscribepath); - else if (r->Link.lFlags & RTMP_LF_LIVE) - SendFCSubscribe(r, &r->Link.playpath); - } - } + if (strlen(pageUrl) > 7) + { + strsplit(pageUrl + 7, FALSE, '/', ¶ms); + av_ModelName.av_val = params[1]; + av_ModelName.av_len = strlen(params[1]); + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_CheckPublicStatus); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + enc = AMF_EncodeString(enc, pend, &av_ModelName); + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + + SendInvoke(r, &av_Command, FALSE); + } + else + { + RTMP_Log(RTMP_LOGERROR, "you must specify the pageUrl"); + RTMP_Close(r); + } + } + /* Weeb.tv specific authentication */ + else if (r->Link.WeebToken.av_len) + { + AVal av_Token, av_Username, av_Password; + SAVC(determineAccess); + + param_count = strsplit(r->Link.WeebToken.av_val, FALSE, ';', ¶ms); + if (param_count >= 1) + { + av_Token.av_val = params[0]; + av_Token.av_len = strlen(params[0]); + } + if (param_count >= 2) + { + av_Username.av_val = params[1]; + av_Username.av_len = strlen(params[1]); + } + if (param_count >= 3) + { + av_Password.av_val = params[2]; + av_Password.av_len = strlen(params[2]); + } + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_determineAccess); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + enc = AMF_EncodeString(enc, pend, &av_Token); + enc = AMF_EncodeString(enc, pend, &av_Username); + enc = AMF_EncodeString(enc, pend, &av_Password); + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + + RTMP_Log(RTMP_LOGDEBUG, "WeebToken: %s", r->Link.WeebToken.av_val); + SendInvoke(r, &av_Command, FALSE); + } + else + RTMP_SendCreateStream(r); + } + else if (AVMATCH(&methodInvoked, &av_getStreamLength)) + { + RTMP_SendCreateStream(r); + } else if (AVMATCH(&methodInvoked, &av_createStream)) - { - r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); + { + r->m_stream_id = (int) AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); + + if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) + { + /* Authenticate on Justin.tv legacy servers before sending FCSubscribe */ + if (r->Link.usherToken.av_len) + SendUsherToken(r, &r->Link.usherToken); + /* Send the FCSubscribe if live stream or if subscribepath is set */ + if (r->Link.subscribepath.av_len) + SendFCSubscribe(r, &r->Link.subscribepath); + else if ((r->Link.lFlags & RTMP_LF_LIVE) && (!r->Link.WeebToken.av_len)) + SendFCSubscribe(r, &r->Link.playpath); + } if (r->Link.protocol & RTMP_FEATURE_WRITE) { @@ -3013,7 +3234,7 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) } else if (AVMATCH(&method, &av_onBWDone)) { - if (!r->m_nBWCheckCounter) + if (!r->m_nBWCheckCounter) SendCheckBW(r); } else if (AVMATCH(&method, &av_onFCSubscribe)) @@ -3029,7 +3250,7 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) { SendPong(r, txn); } - else if (AVMATCH(&method, &av__onbwcheck)) + else if (AVMATCH(&method, &av__onbwcheck) || AVMATCH(&method, &av_onBWCheck)) { SendCheckBWResult(r, txn); } @@ -3045,6 +3266,7 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) } else if (AVMATCH(&method, &av__error)) { + int handled = FALSE; #ifdef CRYPTO AVal methodInvoked = {0}; int i; @@ -3082,20 +3304,63 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) /* if PublisherAuth returns 1, then reconnect */ PublisherAuth(r, &description); } - } - else - { - RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); + handled = TRUE; } free(methodInvoked.av_val); -#else - RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); #endif + double code = 0; + unsigned int parsedPort; + AMFObject obj2; + AMFObjectProperty p; + AVal redirect; + SAVC(ex); + SAVC(redirect); + + AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); + if (RTMP_FindFirstMatchingProperty(&obj2, &av_ex, &p)) + { + AMFProp_GetObject(&p, &obj2); + if (RTMP_FindFirstMatchingProperty(&obj2, &av_code, &p)) + code = AMFProp_GetNumber(&p); + if (code == 302 && RTMP_FindFirstMatchingProperty(&obj2, &av_redirect, &p)) + { + AMFProp_GetString(&p, &redirect); + r->Link.redirected = TRUE; + + char *url = malloc(redirect.av_len + sizeof ("/playpath")); + strncpy(url, redirect.av_val, redirect.av_len); + url[redirect.av_len] = '\0'; + r->Link.tcUrl.av_val = url; + r->Link.tcUrl.av_len = redirect.av_len; + strcat(url, "//playpath"); + RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname, &parsedPort, &r->Link.playpath0, &r->Link.app); + if (parsedPort) + r->Link.port = parsedPort; + } + } + if (r->Link.redirected) + { + handled = TRUE; + RTMP_Log(RTMP_LOGINFO, "rtmp server sent redirect"); + } + + if (!handled) + RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); } else if (AVMATCH(&method, &av_close)) { - RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); - RTMP_Close(r); + if (r->Link.redirected) + { + r->Link.redirected = FALSE; + RTMP_Close(r); + RTMP_Log(RTMP_LOGINFO, "trying to connect with redirected url"); + RTMP_Connect(r, NULL); + } + else + { + RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); + RTMP_Close(r); + } #ifdef CRYPTO if ((r->Link.protocol & RTMP_FEATURE_WRITE) && !(r->Link.pFlags & RTMP_PUB_CLEAN) && @@ -3116,10 +3381,11 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) else if (AVMATCH(&method, &av_onStatus)) { AMFObject obj2; - AVal code, level; + AVal code, level, description; AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code); AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level); + AMFProp_GetString(AMF_GetProp(&obj2, &av_description, -1), &description); RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val); if (AVMATCH(&code, &av_NetStream_Failed) @@ -3183,6 +3449,46 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) r->m_pausing = 3; } } + + else if (AVMATCH(&code, &av_NetConnection_confStream)) + { +#ifdef CRYPTO + static const char hexdig[] = "0123456789abcdef"; + AVal auth; + SAVC(cf_stream); + int i; + char hash_hex[33] = {0}; + unsigned char hash[16]; + + param_count = strsplit(description.av_val, description.av_len, ':', ¶ms); + if (param_count >= 3) + { + char *buf = malloc(strlen(params[0]) + r->Link.playpath.av_len + 1); + strcpy(buf, params[0]); + strncat(buf, r->Link.playpath.av_val, r->Link.playpath.av_len); + md5_hash((unsigned char *) buf, strlen(buf), hash); + for (i = 0; i < 16; i++) + { + hash_hex[i * 2] = hexdig[0x0f & (hash[i] >> 4)]; + hash_hex[i * 2 + 1] = hexdig[0x0f & (hash[i])]; + } + auth.av_val = &hash_hex[atoi(params[1]) - 1]; + auth.av_len = atoi(params[2]); + RTMP_Log(RTMP_LOGDEBUG, "Khalsa: %.*s", auth.av_len, auth.av_val); + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_cf_stream); + enc = AMF_EncodeNumber(enc, pend, txn); + *enc++ = AMF_NULL; + enc = AMF_EncodeString(enc, pend, &auth); + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + + SendInvoke(r, &av_Command, FALSE); + free(buf); + } +#endif + } } else if (AVMATCH(&method, &av_playlist_ready)) { @@ -3196,6 +3502,85 @@ HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) } } } + else if (AVMATCH(&method, &av_verifyClient)) + { + double VerificationNumber = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); + RTMP_Log(RTMP_LOGDEBUG, "VerificationNumber: %.2f", VerificationNumber); + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av__result); + enc = AMF_EncodeNumber(enc, pend, txn); + *enc++ = AMF_NULL; + enc = AMF_EncodeNumber(enc, pend, exp(atan(sqrt(VerificationNumber))) + 1); + av_Response.av_val = pbuf; + av_Response.av_len = enc - pbuf; + + AMF_Decode(&obj, av_Response.av_val, av_Response.av_len, FALSE); + AMF_Dump(&obj); + SendInvoke(r, &av_Response, FALSE); + } + else if (AVMATCH(&method, &av_sendStatus)) + { + if (r->Link.WeebToken.av_len) + { + AVal av_Authorized = AVC("User.hasAccess"); + AVal av_TransferLimit = AVC("User.noPremium.limited"); + AVal av_UserLimit = AVC("User.noPremium.tooManyUsers"); + AVal av_TimeLeft = AVC("timeLeft"); + AVal av_Status, av_ReconnectionTime; + + AMFObject Status; + AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &Status); + AMFProp_GetString(AMF_GetProp(&Status, &av_code, -1), &av_Status); + RTMP_Log(RTMP_LOGINFO, "%.*s", av_Status.av_len, av_Status.av_val); + if (AVMATCH(&av_Status, &av_Authorized)) + { + RTMP_Log(RTMP_LOGINFO, "Weeb.tv authentication successful"); + RTMP_SendCreateStream(r); + } + else if (AVMATCH(&av_Status, &av_UserLimit)) + { + RTMP_Log(RTMP_LOGINFO, "No free slots available"); + RTMP_Close(r); + } + else if (AVMATCH(&av_Status, &av_TransferLimit)) + { + AMFProp_GetString(AMF_GetProp(&Status, &av_TimeLeft, -1), &av_ReconnectionTime); + RTMP_Log(RTMP_LOGINFO, "Viewing limit exceeded. try again in %.*s minutes.", av_ReconnectionTime.av_len, av_ReconnectionTime.av_val); + RTMP_Close(r); + } + } + } + else if (AVMATCH(&method, &av_cps)) + { + int Status = AMFProp_GetBoolean(AMF_GetProp(&obj, NULL, 3)); + if (Status == FALSE) + { + AVal Message; + AMFProp_GetString(AMF_GetProp(&obj, NULL, 4), &Message); + RTMP_Log(RTMP_LOGINFO, "Model status is %.*s", Message.av_len, Message.av_val); + RTMP_Close(r); + } + else + { + AVal Playpath, Server; + AMFProp_GetString(AMF_GetProp(&obj, NULL, 5), &Playpath); + AMFProp_GetString(AMF_GetProp(&obj, NULL, 6), &Server); + if (strncasecmp(&Playpath.av_val[Playpath.av_len - 4], ".mp4", 4) != 0) + { + char *playpath = calloc(Server.av_len + Playpath.av_len + 25, sizeof (char)); + strcat(playpath, "rtmp://"); + strncat(playpath, Server.av_val, Server.av_len); + strcat(playpath, "/live-origin/"); + strncat(playpath, Playpath.av_val, Playpath.av_len); + strcat(playpath, ".mp4"); + Playpath.av_val = playpath; + Playpath.av_len = strlen(playpath); + } + RTMP_ParsePlaypath(&Playpath, &r->Link.playpath); + RTMP_SendCreateStream(r); + } + } else { @@ -3292,7 +3677,7 @@ DumpMetaData(AMFObject *obj) /* chomp */ if (strlen(str) >= 1 && str[strlen(str) - 1] == '\n') str[strlen(str) - 1] = '\0'; - RTMP_Log(RTMP_LOGINFO, " %-22.*s%s", prop->p_name.av_len, + RTMP_Log(RTMP_LOGINFO, " %-24.*s%s", prop->p_name.av_len, prop->p_name.av_val, str); } } @@ -3381,7 +3766,7 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet) unsigned int tmp; if (packet->m_body && packet->m_nBodySize >= 2) nType = AMF_DecodeInt16(packet->m_body); - RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl. type: %d, len: %d", __FUNCTION__, nType, + RTMP_Log(RTMP_LOGDEBUG, "%s, received ctrl, type: %d, len: %d", __FUNCTION__, nType, packet->m_nBodySize); /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ @@ -3490,15 +3875,15 @@ HandleCtrl(RTMP *r, const RTMPPacket *packet) RTMP_Log(RTMP_LOGDEBUG, "%s, SWFVerification ping received: ", __FUNCTION__); if (packet->m_nBodySize > 2 && packet->m_body[2] > 0x01) { - RTMP_Log(RTMP_LOGERROR, - "%s: SWFVerification Type %d request not supported! Patches welcome...", - __FUNCTION__, packet->m_body[2]); + RTMP_Log(RTMP_LOGERROR, + "%s: SWFVerification Type %d request not supported, attempting to use SWFVerification Type 1! Patches welcome...", + __FUNCTION__, packet->m_body[2]); } #ifdef CRYPTO /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ /* respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied */ - else if (r->Link.SWFSize) + if (r->Link.SWFSize) { RTMP_SendCtrl(r, 0x1B, 0, 0); } @@ -3796,8 +4181,18 @@ HandShake(RTMP *r, int FP9HandShake) serversig[4], serversig[5], serversig[6], serversig[7]); /* 2nd part of handshake */ - if (!WriteN(r, serversig, RTMP_SIG_SIZE)) - return FALSE; + if (r->Link.CombineConnectPacket) + { + char *HandshakeResponse = malloc(RTMP_SIG_SIZE); + memcpy(HandshakeResponse, (char *) serversig, RTMP_SIG_SIZE); + r->Link.HandshakeResponse.av_val = HandshakeResponse; + r->Link.HandshakeResponse.av_len = RTMP_SIG_SIZE; + } + else + { + if (!WriteN(r, (char *) serversig, RTMP_SIG_SIZE)) + return FALSE; + } if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) return FALSE; @@ -4248,8 +4643,13 @@ RTMPSockBuf_Fill(RTMPSockBuf *sb) { int nBytes; - if (!sb->sb_size) - sb->sb_start = sb->sb_buf; + /* Copy unprocessed bytes to the start of buffer to make optimum use of + * available buffer */ + if (sb->sb_start != sb->sb_buf) + { + memcpy(sb->sb_buf, sb->sb_start, sb->sb_size); + sb->sb_start = sb->sb_buf; + } while (1) { @@ -4263,6 +4663,8 @@ RTMPSockBuf_Fill(RTMPSockBuf *sb) #endif { nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0); + if (!nBytes) + RTMP_Log(RTMP_LOGDEBUG, "Socket closed by server, nBytes: %d", nBytes); } if (nBytes != -1) { @@ -4402,21 +4804,19 @@ static int HTTP_Post(RTMP *r, RTMPTCmd cmd, const char *buf, int len) { char hbuf[512]; - int hlen = snprintf(hbuf, sizeof(hbuf), "POST /%s%s/%d HTTP/1.1\r\n" - "Host: %.*s:%d\r\n" - "Accept: */*\r\n" - "User-Agent: Shockwave Flash\r\n" - "Connection: Keep-Alive\r\n" - "Cache-Control: no-cache\r\n" - "Content-type: application/x-fcs\r\n" - "Content-length: %d\r\n\r\n", RTMPT_cmds[cmd], - r->m_clientID.av_val ? r->m_clientID.av_val : "", - r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val, - r->Link.port, len); + int hlen = snprintf(hbuf, sizeof (hbuf), "POST /%s%s/%d HTTP/1.1\r\n" + "Content-Type: application/x-fcs\r\n" + "User-Agent: Shockwave Flash\r\n" + "Host: %.*s:%d\r\n" + "Content-Length: %d\r\n" + "Connection: Keep-Alive\r\n" + "Cache-Control: no-cache\r\n\r\n", RTMPT_cmds[cmd], + r->m_clientID.av_val ? r->m_clientID.av_val : "", + r->m_msgCounter, r->Link.hostname.av_len, r->Link.hostname.av_val, + r->Link.port, len); RTMPSockBuf_Send(&r->m_sb, hbuf, hlen); hlen = RTMPSockBuf_Send(&r->m_sb, buf, len); r->m_msgCounter++; - r->m_unackd++; return hlen; } @@ -4426,22 +4826,17 @@ HTTP_read(RTMP *r, int fill) char *ptr; int hlen; -restart: if (fill) RTMPSockBuf_Fill(&r->m_sb); - if (r->m_sb.sb_size < 13) { - if (fill) - goto restart; + + /* Check if socket buffer is empty or HTTP header isn't completely received */ + memset(r->m_sb.sb_start + r->m_sb.sb_size, '\0', 1); + if ((!r->m_sb.sb_size) || (!strstr(r->m_sb.sb_start, "\r\n\r\n"))) return -2; - } + if (strncmp(r->m_sb.sb_start, "HTTP/1.1 200 ", 13)) return -1; r->m_sb.sb_start[r->m_sb.sb_size] = '\0'; - if (!strstr(r->m_sb.sb_start, "\r\n\r\n")) { - if (fill) - goto restart; - return -2; - } ptr = r->m_sb.sb_start + sizeof("HTTP/1.1 200"); while ((ptr = strstr(ptr, "Content-"))) { @@ -4455,15 +4850,20 @@ restart: if (!ptr) return -1; ptr += 4; - if (ptr + (r->m_clientID.av_val ? 1 : hlen) > r->m_sb.sb_start + r->m_sb.sb_size) - { - if (fill) - goto restart; - return -2; - } r->m_sb.sb_size -= ptr - r->m_sb.sb_start; r->m_sb.sb_start = ptr; - r->m_unackd--; + + /* Stop processing if content length is 0 */ + if (!hlen) + return -3; + + /* Refill buffer if no payload is received */ + if (hlen && (!r->m_sb.sb_size)) + { + RTMPSockBuf_Fill(&r->m_sb); + ptr = r->m_sb.sb_buf; + r->m_sb.sb_start = ptr; + } if (!r->m_clientID.av_val) { @@ -4483,6 +4883,13 @@ restart: r->m_sb.sb_start++; r->m_sb.sb_size--; } + + /* Following values shouldn't be negative in any case */ + if (r->m_resplen < 0) + r->m_resplen = 0; + if (r->m_sb.sb_size < 0) + r->m_sb.sb_size = 0; + return 0; } @@ -5013,13 +5420,21 @@ fail: r->m_read.status = nRead; goto fail; } - /* buffer overflow, fix buffer and give up */ - if (r->m_read.buf < mybuf || r->m_read.buf > end) { - mybuf = realloc(mybuf, cnt + nRead); - memcpy(mybuf+cnt, r->m_read.buf, nRead); - r->m_read.buf = mybuf+cnt+nRead; - break; - } + /* buffer overflow, fix buffer and give up */ + if (r->m_read.buf < mybuf || r->m_read.buf > end) + { + if (!cnt) + { + mybuf = realloc(mybuf, sizeof (flvHeader) + cnt + nRead); + memcpy(mybuf, flvHeader, sizeof (flvHeader)); + cnt += sizeof (flvHeader); + } + else + mybuf = realloc(mybuf, cnt + nRead); + memcpy(mybuf + cnt, r->m_read.buf, nRead); + r->m_read.buf = mybuf + cnt + nRead; + break; + } cnt += nRead; r->m_read.buf += nRead; r->m_read.buflen -= nRead; @@ -5170,3 +5585,159 @@ RTMP_Write(RTMP *r, const char *buf, int size) } return size+s2; } + +static int +SendInvoke(RTMP *r, AVal *Command, int queue) +{ + RTMPPacket packet; + char pbuf[512], *enc; + + packet.m_nChannel = 0x03; /* control channel (invoke) */ + packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; + packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; + packet.m_nTimeStamp = 0; + packet.m_nInfoField2 = 0; + packet.m_hasAbsTimestamp = 0; + packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; + + enc = packet.m_body; + if (Command->av_len) + { + memcpy(enc, Command->av_val, Command->av_len); + enc += Command->av_len; + } + else + return FALSE; + packet.m_nBodySize = enc - packet.m_body; + + return RTMP_SendPacket(r, &packet, queue); +} + +static int +strsplit(char *src, int srclen, char delim, char ***params) +{ + char *sptr, *srcbeg, *srcend, *dstr; + int count = 1, i = 0, len = 0; + + if (src == NULL) + return 0; + if (!srclen) + srclen = strlen(src); + srcbeg = src; + srcend = srcbeg + srclen; + sptr = srcbeg; + + /* count the delimiters */ + while (sptr < srcend) + { + if (*sptr++ == delim) + count++; + } + sptr = srcbeg; + *params = calloc(count, sizeof (size_t)); + char **param = *params; + + for (i = 0; i < (count - 1); i++) + { + dstr = strchr(sptr, delim); + len = dstr - sptr; + param[i] = calloc(len + 1, sizeof (char)); + strncpy(param[i], sptr, len); + sptr += len + 1; + } + + /* copy the last string */ + if (sptr <= srcend) + { + len = srclen - (sptr - srcbeg); + param[i] = calloc(len + 1, sizeof (char)); + strncpy(param[i], sptr, len); + } + return count; +} + +static int +SendGetStreamLength(RTMP *r) +{ + char pbuf[256], *pend = pbuf + sizeof (pbuf), *enc; + AVal av_Command; + + enc = pbuf; + enc = AMF_EncodeString(enc, pend, &av_getStreamLength); + enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); + *enc++ = AMF_NULL; + enc = AMF_EncodeString(enc, pend, &r->Link.playpath); + av_Command.av_val = pbuf; + av_Command.av_len = enc - pbuf; + + return SendInvoke(r, &av_Command, TRUE); +} + +static int +ConnectSocket(RTMP *r) +{ + int on = 1; + struct sockaddr_in service; + if (!r->Link.hostname.av_len) + return FALSE; + + memset(&service, 0, sizeof (struct sockaddr_in)); + service.sin_family = AF_INET; + + if (r->Link.socksport) + { + /* Connect via SOCKS */ + if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport)) + return FALSE; + } + else + { + /* Connect directly */ + if (!add_addr_info(&service, &r->Link.hostname, r->Link.port)) + return FALSE; + } + + r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (r->m_sb.sb_socket != -1) + { + if (connect(r->m_sb.sb_socket, (struct sockaddr *) &service, sizeof (struct sockaddr)) < 0) + { + int err = GetSockError(); + RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)", + __FUNCTION__, err, strerror(err)); + RTMP_Close(r); + return FALSE; + } + + if (r->Link.socksport) + { + RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__); + if (!SocksNegotiate(r)) + { + RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__); + RTMP_Close(r); + return FALSE; + } + } + } + else + { + RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", + __FUNCTION__, GetSockError()); + return FALSE; + } + + /* set timeout */ + SET_RCVTIMEO(tv, r->Link.timeout); + if (setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof (tv))) + { + RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %d failed!", + __FUNCTION__, r->Link.timeout); + } + + setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof (on)); + if (r->Link.protocol & RTMP_FEATURE_HTTP) + setsockopt(r->m_sb.sb_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof (on)); + + return TRUE; +} diff --git librtmp/rtmp.h librtmp/rtmp.h index d723070..fa8f08d 100644 --- librtmp/rtmp.h +++ librtmp/rtmp.h @@ -150,12 +150,14 @@ extern "C" AVal playpath; /* passed in explicitly */ AVal tcUrl; AVal swfUrl; + AVal swfHash; AVal pageUrl; AVal app; AVal auth; AVal flashVer; AVal subscribepath; AVal usherToken; + AVal WeebToken; AVal token; AVal pubUser; AVal pubPasswd; @@ -174,9 +176,15 @@ extern "C" int lFlags; int swfAge; + int swfSize; int protocol; + int ConnectPacket; + int CombineConnectPacket; + int redirected; int timeout; /* connection timeout in seconds */ + AVal Extras; + AVal HandshakeResponse; #define RTMP_PUB_NAME 0x0001 /* send login to server */ #define RTMP_PUB_RESP 0x0002 /* send salted password hash */ @@ -310,6 +318,7 @@ extern "C" AVal *flashVer, AVal *subscribepath, AVal *usherToken, + AVal *WeebToken, int dStart, int dStop, int bLiveStream, long int timeout); diff --git librtmp/rtmp_sys.h librtmp/rtmp_sys.h index 85d7e53..b2a3438 100644 --- librtmp/rtmp_sys.h +++ librtmp/rtmp_sys.h @@ -65,6 +65,7 @@ #include #include #include +#include #if POLARSSL_VERSION_NUMBER < 0x01010000 #define havege_random havege_rand #endif @@ -105,6 +106,7 @@ typedef struct tls_server_ctx { #define TLS_write(s,b,l) ssl_write(s,(unsigned char *)b,l) #define TLS_shutdown(s) ssl_close_notify(s) #define TLS_close(s) ssl_free(s); free(s) +#define md5_hash(i, ilen, o) md5(i, ilen, o) #elif defined(USE_GNUTLS) #include @@ -122,6 +124,8 @@ typedef struct tls_ctx { #define TLS_write(s,b,l) gnutls_record_send(s,b,l) #define TLS_shutdown(s) gnutls_bye(s, GNUTLS_SHUT_RDWR) #define TLS_close(s) gnutls_deinit(s) +#define md5_hash(i, ilen, o) gnutls_digest_algorithm_t algorithm = GNUTLS_DIG_MD5;\ + gnutls_hash_fast(algorithm, i, ilen, o); #else /* USE_OPENSSL */ #define TLS_CTX SSL_CTX * @@ -134,6 +138,7 @@ typedef struct tls_ctx { #define TLS_write(s,b,l) SSL_write(s,b,l) #define TLS_shutdown(s) SSL_shutdown(s) #define TLS_close(s) SSL_free(s) +#define md5_hash(i, ilen, o) MD5(i, ilen, o) #endif #endif diff --git rtmpdump.c rtmpdump.c index 13741a7..cc7f056 100644 --- rtmpdump.c +++ rtmpdump.c @@ -705,6 +705,8 @@ void usage(char *prog) RTMP_LogPrintf ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n"); RTMP_LogPrintf + ("--weeb|-J string Authentication token for weeb.tv servers\n"); + RTMP_LogPrintf ("--hashes|-# Display progress with hashes, not with the byte counter\n"); RTMP_LogPrintf ("--buffer|-b Buffer time in milliseconds (default: %u)\n", @@ -751,7 +753,8 @@ main(int argc, char **argv) AVal hostname = { 0, 0 }; AVal playpath = { 0, 0 }; AVal subscribepath = { 0, 0 }; - AVal usherToken = { 0, 0 }; //Justin.tv auth token + AVal usherToken = { 0, 0 }; // Justin.tv auth token + AVal WeebToken = { 0, 0 }; // Weeb.tv auth token int port = -1; int protocol = RTMP_PROTOCOL_UNDEFINED; int retries = 0; @@ -858,12 +861,13 @@ main(int argc, char **argv) {"quiet", 0, NULL, 'q'}, {"verbose", 0, NULL, 'V'}, {"jtv", 1, NULL, 'j'}, + {"weeb", 1, NULL, 'J'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, - "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:", + "hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:J:", longopts, NULL)) != -1) { switch (opt) @@ -1079,6 +1083,9 @@ main(int argc, char **argv) case 'j': STR2AVAL(usherToken, optarg); break; + case 'J': + STR2AVAL(WeebToken, optarg); + break; default: RTMP_LogPrintf("unknown option: %c\n", opt); usage(argv[0]); @@ -1170,14 +1177,14 @@ main(int argc, char **argv) if (tcUrl.av_len == 0) { - tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) + - hostname.av_len + app.av_len + sizeof("://:65535/"); + tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) + + hostname.av_len + app.av_len + sizeof ("://:65535/"); tcUrl.av_val = (char *) malloc(tcUrl.av_len); - if (!tcUrl.av_val) - return RD_FAILED; + if (!tcUrl.av_val) + return RD_FAILED; tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s", - RTMPProtocolStringsLower[protocol], hostname.av_len, - hostname.av_val, port, app.av_len, app.av_val); + RTMPProtocolStringsLower[protocol], hostname.av_len, + hostname.av_val, port, app.av_len, app.av_val); } int first = 1; @@ -1197,8 +1204,8 @@ main(int argc, char **argv) if (!fullUrl.av_len) { RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath, - &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, - &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout); + &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, + &flashVer, &subscribepath, &usherToken, &WeebToken, dSeek, dStopOffset, bLiveStream, timeout); } else { diff --git rtmpgw.c rtmpgw.c index 3e47602..a82137f 100644 --- rtmpgw.c +++ rtmpgw.c @@ -96,7 +96,8 @@ typedef struct AVal flashVer; AVal token; AVal subscribepath; - AVal usherToken; //Justin.tv auth token + AVal usherToken; // Justin.tv auth token + AVal WeebToken; // Weeb.tv auth token AVal sockshost; AMFObject extras; int edepth; @@ -556,8 +557,8 @@ void processTCPrequest(STREAMING_SERVER * server, // server socket and state (ou if (!req.fullUrl.av_len) { RTMP_SetupStream(&rtmp, req.protocol, &req.hostname, req.rtmpport, &req.sockshost, - &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, - req.bLiveStream, req.timeout); + &req.playpath, &req.tcUrl, &req.swfUrl, &req.pageUrl, &req.app, &req.auth, &req.swfHash, req.swfSize, &req.flashVer, &req.subscribepath, &req.usherToken, &req.WeebToken, dSeek, req.dStopOffset, + req.bLiveStream, req.timeout); } else { @@ -972,6 +973,9 @@ ParseOption(char opt, char *arg, RTMP_REQUEST * req) case 'j': STR2AVAL(req->usherToken, arg); break; + case 'J': + STR2AVAL(req->WeebToken, arg); + break; default: RTMP_LogPrintf("unknown option: %c, arg: %s\n", opt, arg); return FALSE; @@ -1044,6 +1048,7 @@ main(int argc, char **argv) {"quiet", 0, NULL, 'q'}, {"verbose", 0, NULL, 'V'}, {"jtv", 1, NULL, 'j'}, + {"weeb", 1, NULL, 'J'}, {0, 0, 0, 0} }; @@ -1056,7 +1061,7 @@ main(int argc, char **argv) while ((opt = getopt_long(argc, argv, - "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, + "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:J:", longopts, NULL)) != -1) { switch (opt) @@ -1121,6 +1126,8 @@ main(int argc, char **argv) RTMP_LogPrintf ("--jtv|-j JSON Authentication token for Justin.tv legacy servers\n"); RTMP_LogPrintf + ("--weeb|-J string Authentication token for weeb.tv servers\n"); + RTMP_LogPrintf ("--buffer|-b Buffer time in milliseconds (default: %u)\n\n", defaultRTMPRequest.bufferTime); diff --git rtmpsrv.c rtmpsrv.c index a9e9045..4c1aaaa 100644 --- rtmpsrv.c +++ rtmpsrv.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -97,9 +98,20 @@ void *sslCtx = NULL; STREAMING_SERVER *startStreaming(const char *address, int port); void stopStreaming(STREAMING_SERVER * server); void AVreplace(AVal *src, const AVal *orig, const AVal *repl); +char *strreplace(char *srcstr, int srclen, char *orig, char *repl); +int file_exists(const char *fname); +int SendCheckBWResponse(RTMP *r, int oldMethodType, int onBWDoneInit); +AVal AVcopy(AVal src); +AVal StripParams(AVal *src); static const AVal av_dquote = AVC("\""); static const AVal av_escdquote = AVC("\\\""); +#ifdef WIN32 +static const AVal av_caret = AVC("^"); +static const AVal av_esccaret = AVC("^^"); +static const AVal av_pipe = AVC("|"); +static const AVal av_escpipe = AVC("^|"); +#endif typedef struct { @@ -168,6 +180,10 @@ SAVC(level); SAVC(code); SAVC(description); SAVC(secureToken); +SAVC(_checkbw); +SAVC(_onbwdone); +SAVC(checkBandwidth); +SAVC(onBWDone); static int SendConnectResult(RTMP *r, double txn) @@ -191,7 +207,7 @@ SendConnectResult(RTMP *r, double txn) enc = AMF_EncodeNumber(enc, pend, txn); *enc++ = AMF_OBJECT; - STR2AVAL(av, "FMS/3,5,1,525"); + STR2AVAL(av, "FMS/3,5,7,7009"); enc = AMF_EncodeNamedString(enc, pend, &av_fmsVer, &av); enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 31.0); enc = AMF_EncodeNamedNumber(enc, pend, &av_mode, 1.0); @@ -213,7 +229,7 @@ SendConnectResult(RTMP *r, double txn) enc = AMF_EncodeNamedString(enc, pend, &av_secureToken, &av); #endif STR2AVAL(p.p_name, "version"); - STR2AVAL(p.p_vu.p_aval, "3,5,1,525"); + STR2AVAL(p.p_vu.p_aval, "3,5,7,7009"); p.p_type = AMF_STRING; obj.o_num = 1; obj.o_props = &p; @@ -269,7 +285,7 @@ static int SendPlayStart(RTMP *r) { RTMPPacket packet; - char pbuf[512], *pend = pbuf+sizeof(pbuf); + char pbuf[1024], *pend = pbuf + sizeof (pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ @@ -301,7 +317,7 @@ static int SendPlayStop(RTMP *r) { RTMPPacket packet; - char pbuf[512], *pend = pbuf+sizeof(pbuf); + char pbuf[1024], *pend = pbuf + sizeof (pbuf); packet.m_nChannel = 0x03; // control channel (invoke) packet.m_headerType = 1; /* RTMP_PACKET_SIZE_MEDIUM; */ @@ -329,6 +345,49 @@ SendPlayStop(RTMP *r) return RTMP_SendPacket(r, &packet, FALSE); } +int +SendCheckBWResponse(RTMP *r, int oldMethodType, int onBWDoneInit) +{ + RTMPPacket packet; + char pbuf[256], *pend = pbuf + sizeof (pbuf); + char *enc; + + packet.m_nChannel = 0x03; /* control channel (invoke) */ + packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; + packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; + packet.m_nTimeStamp = 0; + packet.m_nInfoField2 = 0; + packet.m_hasAbsTimestamp = 0; + packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; + + enc = packet.m_body; + if (oldMethodType) + { + enc = AMF_EncodeString(enc, pend, &av__onbwdone); + enc = AMF_EncodeNumber(enc, pend, 0); + *enc++ = AMF_NULL; + enc = AMF_EncodeNumber(enc, pend, 10240); + enc = AMF_EncodeNumber(enc, pend, 10240); + } + else + { + enc = AMF_EncodeString(enc, pend, &av_onBWDone); + enc = AMF_EncodeNumber(enc, pend, 0); + *enc++ = AMF_NULL; + if (!onBWDoneInit) + { + enc = AMF_EncodeNumber(enc, pend, 10240); + enc = AMF_EncodeNumber(enc, pend, 10240); + enc = AMF_EncodeNumber(enc, pend, 0); + enc = AMF_EncodeNumber(enc, pend, 0); + } + } + + packet.m_nBodySize = enc - packet.m_body; + + return RTMP_SendPacket(r, &packet, FALSE); +} + static void spawn_dumper(int argc, AVal *av, char *cmd) { @@ -569,6 +628,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int server->arglen += countAMF(&r->Link.extras, &server->argc); } SendConnectResult(r, txn); + SendCheckBWResponse(r, FALSE, TRUE); } else if (AVMATCH(&method, &av_createStream)) { @@ -583,10 +643,22 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int AVal usherToken; AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken); AVreplace(&usherToken, &av_dquote, &av_escdquote); +#ifdef WIN32 + AVreplace(&usherToken, &av_caret, &av_esccaret); + AVreplace(&usherToken, &av_pipe, &av_escpipe); +#endif server->arglen += 6 + usherToken.av_len; server->argc += 2; r->Link.usherToken = usherToken; } + else if (AVMATCH(&method, &av__checkbw)) + { + SendCheckBWResponse(r, TRUE, FALSE); + } + else if (AVMATCH(&method, &av_checkBandwidth)) + { + SendCheckBWResponse(r, FALSE, FALSE); + } else if (AVMATCH(&method, &av_play)) { char *file, *p, *q, *cmd, *ptr; @@ -600,6 +672,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int if (obj.o_num > 5) r->Link.length = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 5)); */ + double StartFlag = 0; + AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4); + if (!(Start->p_type == AMF_INVALID)) + StartFlag = AMFProp_GetNumber(Start); + r->Link.app = AVcopy(r->Link.app); + if (StartFlag == -1000 || (r->Link.app.av_val && strstr(r->Link.app.av_val, "live"))) + { + StartFlag = -1000; + server->arglen += 7; + server->argc += 1; + } if (r->Link.tcUrl.av_len) { len = server->arglen + r->Link.playpath.av_len + 4 + @@ -617,6 +700,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int argv[argc].av_val = ptr + 1; argv[argc++].av_len = 2; argv[argc].av_val = ptr + 5; + r->Link.tcUrl = StripParams(&r->Link.tcUrl); ptr += sprintf(ptr," -r \"%s\"", r->Link.tcUrl.av_val); argv[argc++].av_len = r->Link.tcUrl.av_len; @@ -641,6 +725,7 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int argv[argc].av_val = ptr + 1; argv[argc++].av_len = 2; argv[argc].av_val = ptr + 5; + r->Link.swfUrl = StripParams(&r->Link.swfUrl); ptr += sprintf(ptr, " -W \"%s\"", r->Link.swfUrl.av_val); argv[argc++].av_len = r->Link.swfUrl.av_len; } @@ -663,10 +748,17 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int r->Link.usherToken.av_val = NULL; r->Link.usherToken.av_len = 0; } - if (r->Link.extras.o_num) { - ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc); - AMF_Reset(&r->Link.extras); - } + if (StartFlag == -1000) + { + argv[argc].av_val = ptr + 1; + argv[argc++].av_len = 6; + ptr += sprintf(ptr, " --live"); + } + if (r->Link.extras.o_num) + { + ptr = dumpAMF(&r->Link.extras, ptr, argv, &argc); + AMF_Reset(&r->Link.extras); + } argv[argc].av_val = ptr + 1; argv[argc++].av_len = 2; argv[argc].av_val = ptr + 5; @@ -674,7 +766,13 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int r->Link.playpath.av_len, r->Link.playpath.av_val); argv[argc++].av_len = r->Link.playpath.av_len; - av = r->Link.playpath; + if (r->Link.playpath.av_len) + av = r->Link.playpath; + else + { + av.av_val = "file"; + av.av_len = 4; + } /* strip trailing URL parameters */ q = memchr(av.av_val, '?', av.av_len); if (q) @@ -722,11 +820,52 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int { strcpy(file+av.av_len-4, ".flv"); } + /* Add timestamp and quotes to the filename */ + char *quoted, timestamp[21]; + int timestamp_len; + time_t current_time; + + time(¤t_time); + strftime(×tamp[0], 20, "%d-%m-%y_%I-%M-%S_", localtime(¤t_time)); + timestamp_len = strlen(timestamp); + quoted = malloc(timestamp_len + av.av_len + 3); + quoted[0] = '"'; + memcpy(quoted + 1, timestamp, timestamp_len); + memcpy(quoted + 1 + timestamp_len, file, av.av_len); + quoted[1 + timestamp_len + av.av_len] = '"'; + quoted[timestamp_len + av.av_len + 2] = '\0'; + av.av_len += timestamp_len + 2; + free(file); + file = quoted; + argv[argc].av_val = ptr + 1; argv[argc++].av_len = 2; argv[argc].av_val = file; argv[argc].av_len = av.av_len; - ptr += sprintf(ptr, " -o %s", file); +#ifdef VLC + char *vlc; + int didAlloc = FALSE; + + if (getenv("VLC")) + vlc = getenv("VLC"); + else if (getenv("ProgramFiles")) + { + vlc = malloc(512 * sizeof (char)); + didAlloc = TRUE; + char *ProgramFiles = getenv("ProgramFiles"); + sprintf(vlc, "%s%s", ProgramFiles, " (x86)\\VideoLAN\\VLC\\vlc.exe"); + if (!file_exists(vlc)) + sprintf(vlc, "%s%s", ProgramFiles, "\\VideoLAN\\VLC\\vlc.exe"); + } + else + vlc = "C:\\Program Files\\VideoLAN\\VLC\\vlc.exe"; + + ptr += sprintf(ptr, " | \"%s\" -", vlc); + if (didAlloc) + free(vlc); +#else + ptr += sprintf(ptr, " -o %s", file); +#endif now = RTMP_GetTime(); if (now - server->filetime < DUPTIME && AVMATCH(&argv[argc], &server->filename)) { @@ -740,7 +879,23 @@ ServeInvoke(STREAMING_SERVER *server, RTMP * r, RTMPPacket *packet, unsigned int server->filetime = now; free(server->filename.av_val); server->filename = argv[argc++]; - spawn_dumper(argc, argv, cmd); +#ifdef VLC + FILE *vlc_cmdfile = fopen("VLC.bat", "w"); + char *vlc_batchcmd = strreplace(cmd, 0, "%", "%%"); + fprintf(vlc_cmdfile, "%s\n", vlc_batchcmd); + fclose(vlc_cmdfile); + free(vlc_batchcmd); + spawn_dumper(argc, argv, "VLC.bat"); +#else + spawn_dumper(argc, argv, cmd); +#endif + +#ifdef WIN32 + // Dump command to batch file + FILE *cmdfile = fopen("Command.bat", "a"); + fprintf(cmdfile, "%s\n", cmd); + fclose(cmdfile); +#endif } free(cmd); @@ -1189,3 +1344,115 @@ AVreplace(AVal *src, const AVal *orig, const AVal *repl) src->av_val = dest; src->av_len = dptr - dest; } + +char * +strreplace(char *srcstr, int srclen, char *orig, char *repl) +{ + char *ptr = NULL, *sptr = srcstr; + int origlen = strlen(orig); + int repllen = strlen(repl); + if (!srclen) + srclen = strlen(srcstr); + char *srcend = srcstr + srclen; + int dstbuffer = srclen / origlen * repllen; + if (dstbuffer < srclen) + dstbuffer = srclen; + char *dststr = calloc(dstbuffer + 1, sizeof (char)); + char *dptr = dststr; + + if ((ptr = strstr(srcstr, orig))) + { + while (ptr < srcend && (ptr = strstr(sptr, orig))) + { + int len = ptr - sptr; + memcpy(dptr, sptr, len); + sptr += len + origlen; + dptr += len; + memcpy(dptr, repl, repllen); + dptr += repllen; + } + memcpy(dptr, sptr, srcend - sptr); + return dststr; + } + + memcpy(dststr, srcstr, srclen); + return dststr; +} + +AVal +StripParams(AVal *src) +{ + AVal str; + if (src->av_val) + { + str.av_val = calloc(src->av_len + 1, sizeof (char)); + strncpy(str.av_val, src->av_val, src->av_len); + str.av_len = src->av_len; + char *start = str.av_val; + char *end = start + str.av_len; + char *ptr = start; + + while (ptr < end) + { + if (*ptr == '?') + { + str.av_len = ptr - start; + break; + } + ptr++; + } + memset(start + str.av_len, 0, 1); + + char *dynamic = strstr(start, "[[DYNAMIC]]"); + if (dynamic) + { + dynamic -= 1; + memset(dynamic, 0, 1); + str.av_len = dynamic - start; + end = start + str.av_len; + } + + char *import = strstr(start, "[[IMPORT]]"); + if (import) + { + str.av_val = import + 11; + strcpy(start, "http://"); + str.av_val = strcat(start, str.av_val); + str.av_len = strlen(str.av_val); + } + return str; + } + str = *src; + return str; +} + +int +file_exists(const char *fname) +{ + FILE *file; + if ((file = fopen(fname, "r"))) + { + fclose(file); + return TRUE; + } + return FALSE; +} + +AVal +AVcopy(AVal src) +{ + AVal dst; + if (src.av_len) + { + dst.av_val = malloc(src.av_len + 1); + memcpy(dst.av_val, src.av_val, src.av_len); + dst.av_val[src.av_len] = '\0'; + dst.av_len = src.av_len; + } + else + { + dst.av_val = NULL; + dst.av_len = 0; + } + return dst; +} diff --git rtmpsuck.c rtmpsuck.c index e886179..b58a8ea 100644 --- rtmpsuck.c +++ rtmpsuck.c @@ -143,15 +143,18 @@ SAVC(onStatus); SAVC(close); static const AVal av_NetStream_Failed = AVC("NetStream.Failed"); static const AVal av_NetStream_Play_Failed = AVC("NetStream.Play.Failed"); -static const AVal av_NetStream_Play_StreamNotFound = -AVC("NetStream.Play.StreamNotFound"); -static const AVal av_NetConnection_Connect_InvalidApp = -AVC("NetConnection.Connect.InvalidApp"); +static const AVal av_NetStream_Play_StreamNotFound = AVC("NetStream.Play.StreamNotFound"); +static const AVal av_NetConnection_Connect_InvalidApp = AVC("NetConnection.Connect.InvalidApp"); static const AVal av_NetStream_Play_Start = AVC("NetStream.Play.Start"); static const AVal av_NetStream_Play_Complete = AVC("NetStream.Play.Complete"); static const AVal av_NetStream_Play_Stop = AVC("NetStream.Play.Stop"); +static const AVal av_NetStream_Authenticate_UsherToken = AVC("NetStream.Authenticate.UsherToken"); static const char *cst[] = { "client", "server" }; +char *dumpAMF(AMFObject *obj, char *ptr); +char *strreplace(char *srcstr, int srclen, char *orig, char *repl); +AVal AVcopy(AVal src); +AVal StripParams(AVal *src); // Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' int @@ -198,26 +201,28 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b if (cobj.o_props[i].p_type == AMF_STRING) { pval = cobj.o_props[i].p_vu.p_aval; - RTMP_LogPrintf("%.*s: %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val); + RTMP_LogPrintf("%10.*s : %.*s\n", pname.av_len, pname.av_val, pval.av_len, pval.av_val); } if (AVMATCH(&pname, &av_app)) { - server->rc.Link.app = pval; + server->rc.Link.app = AVcopy(pval); pval.av_val = NULL; } else if (AVMATCH(&pname, &av_flashVer)) { - server->rc.Link.flashVer = pval; + server->rc.Link.flashVer = AVcopy(pval); pval.av_val = NULL; } else if (AVMATCH(&pname, &av_swfUrl)) { #ifdef CRYPTO if (pval.av_val) - RTMP_HashSWF(pval.av_val, &server->rc.Link.SWFSize, - (unsigned char *)server->rc.Link.SWFHash, 30); + { + AVal swfUrl = StripParams(&pval); + RTMP_HashSWF(swfUrl.av_val, &server->rc.Link.SWFSize, (unsigned char *) server->rc.Link.SWFHash, 30); + } #endif - server->rc.Link.swfUrl = pval; + server->rc.Link.swfUrl = AVcopy(pval); pval.av_val = NULL; } else if (AVMATCH(&pname, &av_tcUrl)) @@ -225,7 +230,7 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b char *r1 = NULL, *r2; int len; - server->rc.Link.tcUrl = pval; + server->rc.Link.tcUrl = AVcopy(pval); if ((pval.av_val[0] | 0x40) == 'r' && (pval.av_val[1] | 0x40) == 't' && (pval.av_val[2] | 0x40) == 'm' && @@ -267,7 +272,7 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b } else if (AVMATCH(&pname, &av_pageUrl)) { - server->rc.Link.pageUrl = pval; + server->rc.Link.pageUrl = AVcopy(pval); pval.av_val = NULL; } else if (AVMATCH(&pname, &av_audioCodecs)) @@ -287,14 +292,21 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b if (pval.av_val) free(pval.av_val); } + if (obj.o_num > 3) { - if (AMFProp_GetBoolean(&obj.o_props[3])) - server->rc.Link.lFlags |= RTMP_LF_AUTH; - if (obj.o_num > 4) - { - AMFProp_GetString(&obj.o_props[4], &server->rc.Link.auth); - } + int i = obj.o_num - 3; + server->rc.Link.extras.o_num = i; + server->rc.Link.extras.o_props = malloc(i * sizeof (AMFObjectProperty)); + memcpy(server->rc.Link.extras.o_props, obj.o_props + 3, i * sizeof (AMFObjectProperty)); + obj.o_num = 3; + } + + if (server->rc.Link.extras.o_num) + { + server->rc.Link.Extras.av_val = calloc(1024, sizeof (char)); + dumpAMF(&server->rc.Link.extras, server->rc.Link.Extras.av_val); + server->rc.Link.Extras.av_len = strlen(server->rc.Link.Extras.av_val); } if (!RTMP_Connect(&server->rc, pack)) @@ -303,6 +315,18 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b return 1; } server->rc.m_bSendCounter = FALSE; + + if (server->rc.Link.extras.o_props) + { + AMF_Reset(&server->rc.Link.extras); + } + } + else if (AVMATCH(&method, &av_NetStream_Authenticate_UsherToken)) + { + AVal usherToken = {0}; + AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken); + server->rc.Link.usherToken = AVcopy(usherToken); + RTMP_LogPrintf("%10s : %.*s\n", "usherToken", server->rc.Link.usherToken.av_len, server->rc.Link.usherToken.av_val); } else if (AVMATCH(&method, &av_play)) { @@ -323,6 +347,14 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b if (!av.av_val) goto out; + double StartFlag = 0; + AMFObjectProperty *Start = AMF_GetProp(&obj, NULL, 4); + if (!(Start->p_type == AMF_INVALID)) + StartFlag = AMFProp_GetNumber(Start); + if (StartFlag == -1000 || (server->rc.Link.app.av_val && strstr(server->rc.Link.app.av_val, "live"))) + StartFlag = -1000; + RTMP_LogPrintf("%10s : %s\n", "live", (StartFlag == -1000) ? "yes" : "no"); + /* check for duplicates */ for (fl = server->f_head; fl; fl=fl->f_next) { @@ -372,9 +404,53 @@ ServeInvoke(STREAMING_SERVER *server, int which, RTMPPacket *pack, const char *b for (p=file; *p; p++) if (*p == ':') *p = '_'; - RTMP_LogPrintf("Playpath: %.*s\nSaving as: %s\n", - server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val, - file); + RTMP_LogPrintf("%10s : %.*s\n%10s : %s\n", "Playpath", server->rc.Link.playpath.av_len, + server->rc.Link.playpath.av_val, "Saving as", file); + +#ifdef WIN32 + // Dump command to batch file + char *cmd = NULL, *ptr = NULL; + AVal swfUrl, tcUrl; + + cmd = calloc(2048, sizeof (char)); + ptr = cmd; + tcUrl = StripParams(&server->rc.Link.tcUrl); + swfUrl = StripParams(&server->rc.Link.swfUrl); + ptr += sprintf(ptr, "rtmpdump -r \"%.*s\" -a \"%.*s\" -f \"%.*s\" -W \"%.*s\" -p \"%.*s\"", + tcUrl.av_len, tcUrl.av_val, + server->rc.Link.app.av_len, server->rc.Link.app.av_val, + server->rc.Link.flashVer.av_len, server->rc.Link.flashVer.av_val, + swfUrl.av_len, swfUrl.av_val, + server->rc.Link.pageUrl.av_len, server->rc.Link.pageUrl.av_val); + + if (server->rc.Link.usherToken.av_val) + { + char *usherToken = strreplace(server->rc.Link.usherToken.av_val, server->rc.Link.usherToken.av_len, "\"", "\\\""); +#ifdef WIN32 + usherToken = strreplace(usherToken, 0, "^", "^^"); + usherToken = strreplace(usherToken, 0, "|", "^|"); +#endif + ptr += sprintf(ptr, " --jtv \"%s\"", usherToken); + free(usherToken); + } + + if (server->rc.Link.Extras.av_len) + { + ptr += sprintf(ptr, "%.*s", server->rc.Link.Extras.av_len, server->rc.Link.Extras.av_val); + } + + if (StartFlag == -1000) + ptr += sprintf(ptr, "%s", " --live"); + ptr += sprintf(ptr, " -y \"%.*s\"", server->rc.Link.playpath.av_len, server->rc.Link.playpath.av_val); + ptr += sprintf(ptr, " -o \"%s.flv\"\n", file); + + FILE *cmdfile = fopen("Command.bat", "a"); + fprintf(cmdfile, "%s", cmd); + fclose(cmdfile); + + free(cmd); +#endif + out = fopen(file, "wb"); free(file); if (!out) @@ -1196,3 +1272,146 @@ main(int argc, char **argv) #endif return nStatus; } + +char * +dumpAMF(AMFObject *obj, char *ptr) +{ + int i; + const char opt[] = "NBSO Z"; + + for (i = 0; i < obj->o_num; i++) + { + AMFObjectProperty *p = &obj->o_props[i]; + if (p->p_type > 5) + continue; + ptr += sprintf(ptr, " -C "); + if (p->p_name.av_val) + *ptr++ = 'N'; + *ptr++ = opt[p->p_type]; + *ptr++ = ':'; + if (p->p_name.av_val) + ptr += sprintf(ptr, "%.*s:", p->p_name.av_len, p->p_name.av_val); + switch (p->p_type) + { + case AMF_BOOLEAN: + *ptr++ = p->p_vu.p_number != 0 ? '1' : '0'; + break; + case AMF_STRING: + memcpy(ptr, p->p_vu.p_aval.av_val, p->p_vu.p_aval.av_len); + ptr += p->p_vu.p_aval.av_len; + break; + case AMF_NUMBER: + ptr += sprintf(ptr, "%f", p->p_vu.p_number); + break; + case AMF_OBJECT: + *ptr++ = '1'; + ptr = dumpAMF(&p->p_vu.p_object, ptr); + ptr += sprintf(ptr, " -C O:0"); + break; + case AMF_NULL: + default: + break; + } + } + return ptr; +} + +char * +strreplace(char *srcstr, int srclen, char *orig, char *repl) +{ + char *ptr = NULL, *sptr = srcstr; + int origlen = strlen(orig); + int repllen = strlen(repl); + if (!srclen) + srclen = strlen(srcstr); + char *srcend = srcstr + srclen; + int dstbuffer = srclen / origlen * repllen; + if (dstbuffer < srclen) + dstbuffer = srclen; + char *dststr = calloc(dstbuffer + 1, sizeof (char)); + char *dptr = dststr; + + if ((ptr = strstr(srcstr, orig))) + { + while (ptr < srcend && (ptr = strstr(sptr, orig))) + { + int len = ptr - sptr; + memcpy(dptr, sptr, len); + sptr += len + origlen; + dptr += len; + memcpy(dptr, repl, repllen); + dptr += repllen; + } + memcpy(dptr, sptr, srcend - sptr); + return dststr; + } + + memcpy(dststr, srcstr, srclen); + return dststr; +} + +AVal +StripParams(AVal *src) +{ + AVal str; + if (src->av_val) + { + str.av_val = calloc(src->av_len + 1, sizeof (char)); + strncpy(str.av_val, src->av_val, src->av_len); + str.av_len = src->av_len; + char *start = str.av_val; + char *end = start + str.av_len; + char *ptr = start; + + while (ptr < end) + { + if (*ptr == '?') + { + str.av_len = ptr - start; + break; + } + ptr++; + } + memset(start + str.av_len, 0, 1); + + char *dynamic = strstr(start, "[[DYNAMIC]]"); + if (dynamic) + { + dynamic -= 1; + memset(dynamic, 0, 1); + str.av_len = dynamic - start; + end = start + str.av_len; + } + + char *import = strstr(start, "[[IMPORT]]"); + if (import) + { + str.av_val = import + 11; + strcpy(start, "http://"); + str.av_val = strcat(start, str.av_val); + str.av_len = strlen(str.av_val); + } + return str; + } + str = *src; + return str; +} + +AVal +AVcopy(AVal src) +{ + AVal dst; + if (src.av_len) + { + dst.av_val = malloc(src.av_len + 1); + memcpy(dst.av_val, src.av_val, src.av_len); + dst.av_val[src.av_len] = '\0'; + dst.av_len = src.av_len; + } + else + { + dst.av_val = NULL; + dst.av_len = 0; + } + return dst; +}