From f801beab75c25ceb408acceb7b183a63e23b269f Mon Sep 17 00:00:00 2001 From: Thulinma Date: Mon, 11 Apr 2011 17:05:36 +0200 Subject: [PATCH] Even more documentation, RTMP Connector compiles again, but still extremely buggy. Will create RTMP debugging tool soon. Something wrong with RTMP Connector reading FLV::Tags... needs more investigation. --- Doxyfile | 2 +- util/amf.cpp | 3 + util/amf.h | 3 + util/crypto.cpp | 509 ++++++++++++++++++++++++++++++++++++++++++ util/crypto.h | 56 +++++ util/ddv_socket.cpp | 18 ++ util/ddv_socket.h | 4 + util/flv_tag.cpp | 22 +- util/flv_tag.h | 6 +- util/http_parser.cpp | 132 ++++++++--- util/http_parser.h | 74 +++--- util/rtmpchunks.cpp | 442 ++++++++++++++++++++++++++++++++++++ util/rtmpchunks.h | 65 ++++++ util/server_setup.cpp | 40 +++- 14 files changed, 1287 insertions(+), 89 deletions(-) create mode 100644 util/crypto.cpp create mode 100644 util/crypto.h create mode 100644 util/rtmpchunks.cpp create mode 100644 util/rtmpchunks.h diff --git a/Doxyfile b/Doxyfile index dd8ba205..b6edff54 100644 --- a/Doxyfile +++ b/Doxyfile @@ -43,7 +43,7 @@ SYMBOL_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO +EXTRACT_PRIVATE = YES EXTRACT_STATIC = YES EXTRACT_LOCAL_CLASSES = YES EXTRACT_LOCAL_METHODS = NO diff --git a/util/amf.cpp b/util/amf.cpp index fdff660f..97d97ef5 100644 --- a/util/amf.cpp +++ b/util/amf.cpp @@ -1,3 +1,6 @@ +/// \file amf.cpp +/// Holds all code for the AMF namespace. + #include "amf.h" /// Returns the std::string Indice for the current object, if available. diff --git a/util/amf.h b/util/amf.h index 81ab9881..8ae95cd4 100644 --- a/util/amf.h +++ b/util/amf.h @@ -1,3 +1,6 @@ +/// \file amf.h +/// Holds all headers for the AMF namespace. + #pragma once #include #include diff --git a/util/crypto.cpp b/util/crypto.cpp new file mode 100644 index 00000000..c523c680 --- /dev/null +++ b/util/crypto.cpp @@ -0,0 +1,509 @@ +/// \file crypto.cpp +/// Holds all code needed for RTMP cryptography. + +#define STR(x) (((std::string)(x)).c_str()) + +#include "crypto.h" + +#define P768 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF" + +#define P1024 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \ +"FFFFFFFFFFFFFFFF" + +#define Q1024 \ +"7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \ +"948127044533E63A0105DF531D89CD9128A5043CC71A026E" \ +"F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \ +"F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \ +"F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \ +"FFFFFFFFFFFFFFFF" + +#define P1536 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF" + +#define P2048 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \ +"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \ +"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \ +"15728E5A8AACAA68FFFFFFFFFFFFFFFF" + +#define P3072 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \ +"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \ +"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \ +"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \ +"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \ +"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \ +"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \ +"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \ +"43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF" + +#define P4096 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \ +"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \ +"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \ +"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \ +"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \ +"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \ +"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \ +"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \ +"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \ +"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \ +"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \ +"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \ +"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \ +"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" \ +"FFFFFFFFFFFFFFFF" + +#define P6144 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \ +"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \ +"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \ +"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \ +"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \ +"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \ +"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \ +"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \ +"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \ +"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \ +"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \ +"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \ +"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \ +"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \ +"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \ +"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \ +"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \ +"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \ +"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \ +"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \ +"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \ +"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \ +"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \ +"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \ +"12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF" + +#define P8192 \ +"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ +"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ +"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ +"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ +"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" \ +"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" \ +"83655D23DCA3AD961C62F356208552BB9ED529077096966D" \ +"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" \ +"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" \ +"DE2BCBF6955817183995497CEA956AE515D2261898FA0510" \ +"15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" \ +"ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" \ +"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" \ +"F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" \ +"BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" \ +"43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" \ +"88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" \ +"2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" \ +"287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" \ +"1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" \ +"93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" \ +"36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" \ +"F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" \ +"179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" \ +"DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" \ +"5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" \ +"D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" \ +"23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" \ +"CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" \ +"06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" \ +"DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" \ +"12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" \ +"38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" \ +"741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" \ +"3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" \ +"22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" \ +"4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" \ +"062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" \ +"4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" \ +"B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" \ +"4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" \ +"9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" \ +"60C980DD98EDD3DFFFFFFFFFFFFFFFFF" + + +uint8_t genuineFMSKey[] = { + 0x47, 0x65, 0x6e, 0x75, 0x69, 0x6e, 0x65, 0x20, + 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x46, 0x6c, + 0x61, 0x73, 0x68, 0x20, 0x4d, 0x65, 0x64, 0x69, + 0x61, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001 + 0xf0, 0xee, 0xc2, 0x4a, 0x80, 0x68, 0xbe, 0xe8, + 0x2e, 0x00, 0xd0, 0xd1, 0x02, 0x9e, 0x7e, 0x57, + 0x6e, 0xec, 0x5d, 0x2d, 0x29, 0x80, 0x6f, 0xab, + 0x93, 0xb8, 0xe6, 0x36, 0xcf, 0xeb, 0x31, 0xae +}; // 68 + +uint8_t genuineFPKey[] = { + 0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20, + 0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C, + 0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79, + 0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001 + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, + 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, + 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; // 62 + + +void replace(std::string &target, std::string search, std::string replacement) { + if (search == replacement) + return; + if (search == "") + return; + std::string::size_type i = std::string::npos; + while ((i = target.find(search)) != std::string::npos) { + target.replace(i, search.length(), replacement); + } +} + + +DHWrapper::DHWrapper(int32_t bitsCount) { + _bitsCount = bitsCount; + _pDH = NULL; + _pSharedKey = NULL; + _sharedKeyLength = 0; + _peerPublickey = NULL; +} + +DHWrapper::~DHWrapper() { + Cleanup(); +} + +bool DHWrapper::Initialize() { + Cleanup(); + + //1. Create the DH + _pDH = DH_new(); + if (_pDH == NULL) { + Cleanup(); + return false; + } + + //2. Create his internal p and g + _pDH->p = BN_new(); + if (_pDH->p == NULL) { + Cleanup(); + return false; + } + _pDH->g = BN_new(); + if (_pDH->g == NULL) { + Cleanup(); + return false; + } + + //3. initialize p, g and key length + if (BN_hex2bn(&_pDH->p, P1024) == 0) { + Cleanup(); + return false; + } + if (BN_set_word(_pDH->g, 2) != 1) { + Cleanup(); + return false; + } + + //4. Set the key length + _pDH->length = _bitsCount; + + //5. Generate private and public key + if (DH_generate_key(_pDH) != 1) { + Cleanup(); + return false; + } + + return true; +} + +bool DHWrapper::CopyPublicKey(uint8_t *pDst, int32_t dstLength) { + if (_pDH == NULL) { + return false; + } + + return CopyKey(_pDH->pub_key, pDst, dstLength); +} + +bool DHWrapper::CopyPrivateKey(uint8_t *pDst, int32_t dstLength) { + if (_pDH == NULL) { + return false; + } + + return CopyKey(_pDH->priv_key, pDst, dstLength); +} + +bool DHWrapper::CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length) { + if (_pDH == NULL) { + return false; + } + + if (_sharedKeyLength != 0 || _pSharedKey != NULL) { + return false; + } + + _sharedKeyLength = DH_size(_pDH); + if (_sharedKeyLength <= 0 || _sharedKeyLength > 1024) { + return false; + } + _pSharedKey = new uint8_t[_sharedKeyLength]; + + _peerPublickey = BN_bin2bn(pPeerPublicKey, length, 0); + if (_peerPublickey == NULL) { + return false; + } + + if (DH_compute_key(_pSharedKey, _peerPublickey, _pDH) != _sharedKeyLength) { + return false; + } + + return true; +} + +bool DHWrapper::CopySharedKey(uint8_t *pDst, int32_t dstLength) { + if (_pDH == NULL) { + return false; + } + + if (dstLength != _sharedKeyLength) { + return false; + } + + memcpy(pDst, _pSharedKey, _sharedKeyLength); + + return true; +} + +void DHWrapper::Cleanup() { + if (_pDH != NULL) { + if (_pDH->p != NULL) { + BN_free(_pDH->p); + _pDH->p = NULL; + } + if (_pDH->g != NULL) { + BN_free(_pDH->g); + _pDH->g = NULL; + } + DH_free(_pDH); + _pDH = NULL; + } + + if (_pSharedKey != NULL) { + delete[] _pSharedKey; + _pSharedKey = NULL; + } + _sharedKeyLength = 0; + + if (_peerPublickey != NULL) { + BN_free(_peerPublickey); + _peerPublickey = NULL; + } +} + +bool DHWrapper::CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength) { + int32_t keySize = BN_num_bytes(pNum); + if ((keySize <= 0) || (dstLength <= 0) || (keySize > dstLength)) { + return false; + } + + if (BN_bn2bin(pNum, pDst) != keySize) { + return false; + } + + return true; +} + +void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut) { + uint8_t digest[SHA256_DIGEST_LENGTH]; + unsigned int digestLen = 0; + + HMAC_CTX ctx; + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0); + HMAC_Update(&ctx, pubKeyIn, 128); + HMAC_Final(&ctx, digest, &digestLen); + HMAC_CTX_cleanup(&ctx); + + RC4_set_key(rc4keyOut, 16, digest); + + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, secretKey, 128, EVP_sha256(), 0); + HMAC_Update(&ctx, pubKeyOut, 128); + HMAC_Final(&ctx, digest, &digestLen); + HMAC_CTX_cleanup(&ctx); + + RC4_set_key(rc4keyIn, 16, digest); +} + +std::string md5(std::string source, bool textResult) { + EVP_MD_CTX mdctx; + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + + EVP_DigestInit(&mdctx, EVP_md5()); + EVP_DigestUpdate(&mdctx, STR(source), source.length()); + EVP_DigestFinal_ex(&mdctx, md_value, &md_len); + EVP_MD_CTX_cleanup(&mdctx); + + if (textResult) { + std::string result = ""; + char tmp[3]; + for (uint32_t i = 0; i < md_len; i++) { + sprintf(tmp, "%02x", md_value[i]); + result += tmp; + } + return result; + } else { + return std::string((char *) md_value, md_len); + } +} + +std::string b64(std::string source) { + return b64((uint8_t *) STR(source), source.size()); +} + +std::string b64(uint8_t *pBuffer, uint32_t length) { + BIO *bmem; + BIO *b64; + BUF_MEM *bptr; + + b64 = BIO_new(BIO_f_base64()); + bmem = BIO_new(BIO_s_mem()); + + b64 = BIO_push(b64, bmem); + BIO_write(b64, pBuffer, length); + std::string result = ""; + if (BIO_flush(b64) == 1) { + BIO_get_mem_ptr(b64, &bptr); + result = std::string(bptr->data, bptr->length); + } + + BIO_free_all(b64); + + + replace(result, "\n", ""); + replace(result, "\r", ""); + + return result; +} + +std::string unb64(std::string source) { + return unb64((uint8_t *)STR(source),source.length()); +} + +std::string unb64(uint8_t *pBuffer, uint32_t length){ + // create a memory buffer containing base64 encoded data + //BIO* bmem = BIO_new_mem_buf((void*) STR(source), source.length()); + BIO* bmem = BIO_new_mem_buf((void *)pBuffer, length); + + // push a Base64 filter so that reading from buffer decodes it + BIO *bioCmd = BIO_new(BIO_f_base64()); + // we don't want newlines + BIO_set_flags(bioCmd, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_push(bioCmd, bmem); + + char *pOut = new char[length]; + + int finalLen = BIO_read(bmem, (void*) pOut, length); + BIO_free_all(bmem); + std::string result(pOut, finalLen); + delete[] pOut; + return result; +} + +void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult) { + unsigned int digestLen; + HMAC_CTX ctx; + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, (unsigned char*) pKey, keyLength, EVP_sha256(), NULL); + HMAC_Update(&ctx, (unsigned char *) pData, dataLength); + HMAC_Final(&ctx, (unsigned char *) pResult, &digestLen); + HMAC_CTX_cleanup(&ctx); +} + +uint32_t GetDigestOffset0(uint8_t *pBuffer) { + uint32_t offset = pBuffer[8] + pBuffer[9] + pBuffer[10] + pBuffer[11]; + return (offset % 728) + 12; +} +uint32_t GetDigestOffset1(uint8_t *pBuffer) { + uint32_t offset = pBuffer[772] + pBuffer[773] + pBuffer[774] + pBuffer[775]; + return (offset % 728) + 776; +} +uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme){ + if (scheme == 0){return GetDigestOffset0(pBuffer);}else{return GetDigestOffset1(pBuffer);} +} +uint32_t GetDHOffset0(uint8_t *pBuffer) { + uint32_t offset = pBuffer[1532] + pBuffer[1533] + pBuffer[1534] + pBuffer[1535]; + return (offset % 632) + 772; +} +uint32_t GetDHOffset1(uint8_t *pBuffer) { + uint32_t offset = pBuffer[768] + pBuffer[769] + pBuffer[770] + pBuffer[771]; + return (offset % 632) + 8; +} +uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme){ + if (scheme == 0){return GetDHOffset0(pBuffer);}else{return GetDHOffset1(pBuffer);} +} + + +bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme) { + uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme); + uint8_t *pTempBuffer = new uint8_t[1536 - 32]; + memcpy(pTempBuffer, pBuffer, clientDigestOffset); + memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32, 1536 - clientDigestOffset - 32); + uint8_t *pTempHash = new uint8_t[512]; + HMACsha256(pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash); + bool result = (memcmp(pBuffer+clientDigestOffset, pTempHash, 32) == 0); + #if DEBUG >= 4 + fprintf(stderr, "Client scheme validation %hhi %s\n", scheme, result?"success":"failed"); + #endif + delete[] pTempBuffer; + delete[] pTempHash; + return result; +} diff --git a/util/crypto.h b/util/crypto.h new file mode 100644 index 00000000..f4daa4bb --- /dev/null +++ b/util/crypto.h @@ -0,0 +1,56 @@ +/// \file crypto.h +/// Holds all headers needed for RTMP cryptography functions. + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class DHWrapper { +private: + int32_t _bitsCount; + DH *_pDH; + uint8_t *_pSharedKey; + int32_t _sharedKeyLength; + BIGNUM *_peerPublickey; +public: + DHWrapper(int32_t bitsCount); + virtual ~DHWrapper(); + + bool Initialize(); + bool CopyPublicKey(uint8_t *pDst, int32_t dstLength); + bool CopyPrivateKey(uint8_t *pDst, int32_t dstLength); + bool CreateSharedKey(uint8_t *pPeerPublicKey, int32_t length); + bool CopySharedKey(uint8_t *pDst, int32_t dstLength); +private: + void Cleanup(); + bool CopyKey(BIGNUM *pNum, uint8_t *pDst, int32_t dstLength); +}; + + +void InitRC4Encryption(uint8_t *secretKey, uint8_t *pubKeyIn, uint8_t *pubKeyOut, RC4_KEY *rc4keyIn, RC4_KEY *rc4keyOut); +std::string md5(std::string source, bool textResult); +std::string b64(std::string source); +std::string b64(uint8_t *pBuffer, uint32_t length); +std::string unb64(std::string source); +std::string unb64(uint8_t *pBuffer, uint32_t length); + +void HMACsha256(const void *pData, uint32_t dataLength, const void *pKey, uint32_t keyLength, void *pResult); + +uint32_t GetDigestOffset0(uint8_t *pBuffer); +uint32_t GetDigestOffset1(uint8_t *pBuffer); +uint32_t GetDigestOffset(uint8_t *pBuffer, uint8_t scheme); +uint32_t GetDHOffset0(uint8_t *pBuffer); +uint32_t GetDHOffset1(uint8_t *pBuffer); +uint32_t GetDHOffset(uint8_t *pBuffer, uint8_t scheme); + +extern uint8_t genuineFMSKey[]; + +bool ValidateClientScheme(uint8_t * pBuffer, uint8_t scheme); diff --git a/util/ddv_socket.cpp b/util/ddv_socket.cpp index 76c2ee8a..e82ea8ae 100644 --- a/util/ddv_socket.cpp +++ b/util/ddv_socket.cpp @@ -1,3 +1,6 @@ +/// \file ddv_socket.cpp +/// Holds all code for the DDV namespace. + #include "ddv_socket.h" /// Create a new base socket. This is a basic constructor for converting any valid socket to a DDV::Socket. @@ -207,6 +210,21 @@ int DDV::Socket::iread(void * buffer, int len){ return r; }//DDV::Socket::iread +/// Read call that is compatible with std::string. +/// Data is read using iread (which is nonblocking if the DDV::Socket itself is), +/// then appended to end of buffer. +/// \param buffer std::string to append data to. +/// \return True if new data arrived, false otherwise. +bool DDV::Socket::read(std::string & buffer){ + char cbuffer[5000]; + int num = iread(cbuffer, 5000); + if (num > 0){ + buffer.append(cbuffer, num); + return true; + } + return false; +}//read + /// Create a new base ServerSocket. The socket is never connected, and a placeholder for later connections. DDV::ServerSocket::ServerSocket(){ sock = -1; diff --git a/util/ddv_socket.h b/util/ddv_socket.h index 298b5954..5d58e0bd 100644 --- a/util/ddv_socket.h +++ b/util/ddv_socket.h @@ -1,3 +1,6 @@ +/// \file ddv_socket.h +/// Holds all headers for the DDV namespace. + #pragma once #include #include @@ -33,6 +36,7 @@ namespace DDV{ bool write(const std::string data); ///< Write call that is compatible with std::string. int iwrite(void * buffer, int len); ///< Incremental write call. int iread(void * buffer, int len); ///< Incremental read call. + bool read(std::string & buffer); ///< Read call that is compatible with std::string. void close(); ///< Close connection. int getSocket(); ///< Returns internal socket number. }; diff --git a/util/flv_tag.cpp b/util/flv_tag.cpp index 70321b17..08fed05b 100644 --- a/util/flv_tag.cpp +++ b/util/flv_tag.cpp @@ -1,3 +1,6 @@ +/// \file flv_tag.cpp +/// Holds all code for the FLV namespace. + #include "flv_tag.h" #include //for Tag::FileLoader #include //for Tag::FileLoader @@ -8,6 +11,7 @@ char FLV::Header[13]; ///< Holds the last FLV header parsed. bool FLV::Parse_Error = false; ///< This variable is set to true if a problem is encountered while parsing the FLV. +std::string FLV::Error_Str = ""; /// Checks a FLV Header for validness. Returns true if the header is valid, false /// if the header is not. Not valid can mean: @@ -219,7 +223,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true; return false;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -227,7 +231,7 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } @@ -259,7 +263,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s if (r < 0){ if (errno != EWOULDBLOCK){ FLV::Parse_Error = true; - fprintf(stderr, "ReadUntil fail: %s. All Hell Broke Loose!\n", strerror(errno)); + Error_Str = "Error reading from socket."; } return false; } @@ -267,7 +271,7 @@ bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & s if (sofar == count){return true;} if (sofar > count){ FLV::Parse_Error = true; - fprintf(stderr, "ReadUntil fail: %s. Read too much. All Hell Broke Loose!\n", strerror(errno)); + Error_Str = "Socket buffer overflow."; } return false; }//Tag::SockReadUntil @@ -289,7 +293,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true; return false;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -297,7 +301,7 @@ bool FLV::Tag::SockLoader(DDV::Socket sock){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true; return false;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } @@ -335,7 +339,7 @@ bool FLV::Tag::FileReadUntil(char * buffer, unsigned int count, unsigned int & s if (sofar >= count){return true;} int r = 0; r = fread(buffer + sofar,1,count-sofar,f); - if (r < 0){FLV::Parse_Error = true; return false;} + if (r < 0){FLV::Parse_Error = true; Error_Str = "File reading error."; return false;} sofar += r; if (sofar >= count){return true;} return false; @@ -363,7 +367,7 @@ bool FLV::Tag::FileLoader(FILE * f){ if (FLV::check_header(data)){ sofar = 0; memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true;} + }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} } }else{ //if a tag header, calculate length and read tag body @@ -371,7 +375,7 @@ bool FLV::Tag::FileLoader(FILE * f){ len += (data[2] << 8); len += (data[1] << 16); if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){FLV::Parse_Error = true;} + if (data[0] > 0x12){FLV::Parse_Error = true; Error_Str = "Invalid Tag received."; return false;} done = false; } } diff --git a/util/flv_tag.h b/util/flv_tag.h index d91cabb5..e824228c 100644 --- a/util/flv_tag.h +++ b/util/flv_tag.h @@ -1,3 +1,6 @@ +/// \file flv_tag.h +/// Holds all headers for the FLV namespace. + #pragma once #include "ddv_socket.h" #include @@ -7,7 +10,8 @@ namespace FLV { //variables extern char Header[13]; ///< Holds the last FLV header parsed. extern bool Parse_Error; ///< This variable is set to true if a problem is encountered while parsing the FLV. - + extern std::string Error_Str; ///< This variable is set if a problem is encountered while parsing the FLV. + //functions bool check_header(char * header); ///< Checks a FLV Header for validness. bool is_header(char * header); ///< Checks the first 3 bytes for the string "FLV". diff --git a/util/http_parser.cpp b/util/http_parser.cpp index dd01e9b2..60d370e7 100644 --- a/util/http_parser.cpp +++ b/util/http_parser.cpp @@ -1,8 +1,14 @@ -#include "http_parser.h" -#include "ddv_socket.h" +/// \file http_parser.cpp +/// Holds all code for the HTTP namespace. -HTTPReader::HTTPReader(){Clean();} -void HTTPReader::Clean(){ +#include "http_parser.h" + +/// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. +/// All this constructor does is call HTTP::Parser::Clean(). +HTTP::Parser::Parser(){Clean();} + +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. +void HTTP::Parser::Clean(){ seenHeaders = false; seenReq = false; method = "GET"; @@ -11,11 +17,14 @@ void HTTPReader::Clean(){ body = ""; length = 0; HTTPbuffer = ""; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + vars.clear(); } -bool HTTPReader::CleanForNext(){ +/// Re-initializes the HTTP::Parser, leaving the internal data buffer alone, then tries to parse a new request or response. +/// First does the same as HTTP::Parser::Clean(), but does not clear the internal data buffer. +/// This function then calls the HTTP::Parser::parse() function, and returns that functions return value. +bool HTTP::Parser::CleanForNext(){ seenHeaders = false; seenReq = false; method = "GET"; @@ -23,12 +32,19 @@ bool HTTPReader::CleanForNext(){ protocol = "HTTP/1.1"; body = ""; length = 0; - headers.erase(headers.begin(), headers.end()); - vars.erase(vars.begin(), vars.end()); + headers.clear(); + vars.clear(); return parse(); } -std::string HTTPReader::BuildRequest(){ +/// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending. +/// The request is build from internal variables set before this call is made. +/// To be precise, method, url, protocol, headers and the internal data buffer are used, +/// where the internal data buffer is used as the body of the request. +/// This means you cannot mix receiving and sending, because the body would get corrupted. +/// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending. +std::string HTTP::Parser::BuildRequest(){ + /// \todo Include GET/POST variable parsing? std::map::iterator it; std::string tmp = method+" "+url+" "+protocol+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -39,7 +55,16 @@ std::string HTTPReader::BuildRequest(){ return tmp; } -std::string HTTPReader::BuildResponse(std::string code, std::string message){ +/// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending. +/// The response is partly build from internal variables set before this call is made. +/// To be precise, protocol, headers and the internal data buffer are used, +/// where the internal data buffer is used as the body of the response. +/// This means you cannot mix receiving and sending, because the body would get corrupted. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +/// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending. +std::string HTTP::Parser::BuildResponse(std::string code, std::string message){ + /// \todo Include GET/POST variable parsing? std::map::iterator it; std::string tmp = protocol+" "+code+" "+message+"\n"; for (it=headers.begin(); it != headers.end(); it++){ @@ -50,47 +75,61 @@ std::string HTTPReader::BuildResponse(std::string code, std::string message){ return tmp; } -void HTTPReader::Trim(std::string & s){ +/// Trims any whitespace at the front or back of the string. +/// Used when getting/setting headers. +/// \param s The string to trim. The string itself will be changed, not returned. +void HTTP::Parser::Trim(std::string & s){ size_t startpos = s.find_first_not_of(" \t"); size_t endpos = s.find_last_not_of(" \t"); if ((std::string::npos == startpos) || (std::string::npos == endpos)){s = "";}else{s = s.substr(startpos, endpos-startpos+1);} } -void HTTPReader::SetBody(std::string s){ +/// Function that sets the body of a response or request, along with the correct Content-Length header. +/// \param s The string to set the body to. +void HTTP::Parser::SetBody(std::string s){ HTTPbuffer = s; SetHeader("Content-Length", s.length()); } -void HTTPReader::SetBody(char * buffer, int len){ +/// Function that sets the body of a response or request, along with the correct Content-Length header. +/// \param buffer The buffer data to set the body to. +/// \param len Length of the buffer data. +void HTTP::Parser::SetBody(char * buffer, int len){ HTTPbuffer = ""; HTTPbuffer.append(buffer, len); SetHeader("Content-Length", len); } +/// Returns header i, if set. +std::string HTTP::Parser::GetHeader(std::string i){return headers[i];} +/// Returns POST variable i, if set. +std::string HTTP::Parser::GetVar(std::string i){return vars[i];} -std::string HTTPReader::GetHeader(std::string i){return headers[i];} -std::string HTTPReader::GetVar(std::string i){return vars[i];} - -void HTTPReader::SetHeader(std::string i, std::string v){ +/// Sets header i to string value v. +void HTTP::Parser::SetHeader(std::string i, std::string v){ Trim(i); Trim(v); headers[i] = v; } -void HTTPReader::SetHeader(std::string i, int v){ +/// Sets header i to integer value v. +void HTTP::Parser::SetHeader(std::string i, int v){ Trim(i); char val[128]; sprintf(val, "%i", v); headers[i] = val; } -void HTTPReader::SetVar(std::string i, std::string v){ +/// Sets POST variable i to string value v. +void HTTP::Parser::SetVar(std::string i, std::string v){ Trim(i); Trim(v); vars[i] = v; } -bool HTTPReader::Read(DDV::Socket & sock){ +/// Attempt to read a whole HTTP request or response from DDV::Socket sock. +/// \return True of a whole request or response was read, false otherwise. +bool HTTP::Parser::Read(DDV::Socket & sock){ //returned true als hele http packet gelezen is int r = 0; int b = 0; @@ -111,7 +150,9 @@ bool HTTPReader::Read(DDV::Socket & sock){ return false; }//HTTPReader::ReadSocket -bool HTTPReader::Read(FILE * F){ +/// Reads a full set of HTTP responses/requests from file F. +/// \return Always false. Use HTTP::Parser::CleanForNext() to parse the contents of the file. +bool HTTP::Parser::Read(FILE * F){ //returned true als hele http packet gelezen is int b = 1; char buffer[500]; @@ -122,7 +163,11 @@ bool HTTPReader::Read(FILE * F){ return false; }//HTTPReader::ReadSocket -bool HTTPReader::parse(){ +/// Attempt to read a whole HTTP response or request from the internal data buffer. +/// If succesful, fills its own fields with the proper data and removes the response/request +/// from the internal data buffer. +/// \return True on success, false otherwise. +bool HTTP::Parser::parse(){ size_t f; std::string tmpA, tmpB, tmpC; while (HTTPbuffer != ""){ @@ -140,7 +185,7 @@ bool HTTPReader::parse(){ if (f != std::string::npos){url = tmpA.substr(0, f); tmpA.erase(0, f+1);} f = tmpA.find(' '); if (f != std::string::npos){protocol = tmpA.substr(0, f); tmpA.erase(0, f+1);} - //TODO: GET variable parsing? + /// \todo Include GET variable parsing? }else{ if (tmpA.size() == 0){ seenHeaders = true; @@ -156,7 +201,7 @@ bool HTTPReader::parse(){ } if (seenHeaders){ if (length > 0){ - //TODO: POST variable parsing? + /// \todo Include POST variable parsing? if (HTTPbuffer.length() >= length){ body = HTTPbuffer.substr(0, length); HTTPbuffer.erase(0, length); @@ -172,23 +217,40 @@ bool HTTPReader::parse(){ return false; //we should never get here... }//HTTPReader::parse -void HTTPReader::SendResponse(DDV::Socket & conn, std::string code, std::string message){ +/// Sends data as response to conn. +/// The response is automatically first build using HTTP::Parser::BuildResponse(). +/// \param conn The DDV::Socket to send the response over. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +void HTTP::Parser::SendResponse(DDV::Socket & conn, std::string code, std::string message){ std::string tmp = BuildResponse(code, message); conn.write(tmp); } -void HTTPReader::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ +/// Sends data as HTTP/1.1 bodypart to conn. +/// HTTP/1.1 chunked encoding is automatically applied if needed. +/// \param conn The DDV::Socket to send the part over. +/// \param buffer The buffer to send. +/// \param len The length of the buffer. +void HTTP::Parser::SendBodyPart(DDV::Socket & conn, char * buffer, int len){ std::string tmp; tmp.append(buffer, len); SendBodyPart(conn, tmp); } -void HTTPReader::SendBodyPart(DDV::Socket & conn, std::string bodypart){ - static char len[10]; - int sizelen; - sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); - conn.write(len, sizelen); - conn.write(bodypart); - conn.write(len+sizelen-2, 2); +/// Sends data as HTTP/1.1 bodypart to conn. +/// HTTP/1.1 chunked encoding is automatically applied if needed. +/// \param conn The DDV::Socket to send the part over. +/// \param bodypart The data to send. +void HTTP::Parser::SendBodyPart(DDV::Socket & conn, std::string bodypart){ + if (protocol == "HTTP/1.1"){ + static char len[10]; + int sizelen; + sizelen = snprintf(len, 10, "%x\r\n", (unsigned int)bodypart.size()); + conn.write(len, sizelen); + conn.write(bodypart); + conn.write(len+sizelen-2, 2); + }else{ + conn.write(bodypart); + } } - diff --git a/util/http_parser.h b/util/http_parser.h index 2741f0c4..d08cadef 100644 --- a/util/http_parser.h +++ b/util/http_parser.h @@ -1,3 +1,6 @@ +/// \file http_parser.h +/// Holds all headers for the HTTP namespace. + #pragma once #include #include @@ -5,37 +8,40 @@ #include #include "ddv_socket.h" -class HTTPReader{ - public: - HTTPReader(); - bool Read(DDV::Socket & sock); - bool Read(FILE * F); - std::string GetHeader(std::string i); - std::string GetVar(std::string i); - void SetHeader(std::string i, std::string v); - void SetHeader(std::string i, int v); - void SetVar(std::string i, std::string v); - void SetBody(std::string s); - void SetBody(char * buffer, int len); - std::string BuildRequest(); - std::string BuildResponse(std::string code, std::string message); - void SendResponse(DDV::Socket & conn, std::string code, std::string message); - void SendBodyPart(DDV::Socket & conn, char * buffer, int len); - void SendBodyPart(DDV::Socket & conn, std::string bodypart); - void Clean(); - bool CleanForNext(); - std::string body; - std::string method; - std::string url; - std::string protocol; - unsigned int length; - private: - bool seenHeaders; - bool seenReq; - bool parse(); - std::string HTTPbuffer; - std::map headers; - std::map vars; - void Trim(std::string & s); -};//HTTPReader - +/// Holds all HTTP processing related code. +namespace HTTP{ + /// Simple class for reading and writing HTTP 1.0 and 1.1. + class Parser{ + public: + Parser(); + bool Read(DDV::Socket & sock); + bool Read(FILE * F); + std::string GetHeader(std::string i); + std::string GetVar(std::string i); + void SetHeader(std::string i, std::string v); + void SetHeader(std::string i, int v); + void SetVar(std::string i, std::string v); + void SetBody(std::string s); + void SetBody(char * buffer, int len); + std::string BuildRequest(); + std::string BuildResponse(std::string code, std::string message); + void SendResponse(DDV::Socket & conn, std::string code, std::string message); + void SendBodyPart(DDV::Socket & conn, char * buffer, int len); + void SendBodyPart(DDV::Socket & conn, std::string bodypart); + void Clean(); + bool CleanForNext(); + std::string body; + std::string method; + std::string url; + std::string protocol; + unsigned int length; + private: + bool seenHeaders; + bool seenReq; + bool parse(); + std::string HTTPbuffer; + std::map headers; + std::map vars; + void Trim(std::string & s); + };//HTTP::Parser class +};//HTTP namespace diff --git a/util/rtmpchunks.cpp b/util/rtmpchunks.cpp new file mode 100644 index 00000000..d297b5e5 --- /dev/null +++ b/util/rtmpchunks.cpp @@ -0,0 +1,442 @@ +/// \file rtmpchunks.cpp +/// Holds all code for the RTMPStream namespace. + +#include "rtmpchunks.h" +#include "crypto.h" + +char versionstring[] = "WWW.DDVTECH.COM "; ///< String that is repeated in the RTMP handshake +std::string RTMPStream::handshake_in; ///< Input for the handshake. +std::string RTMPStream::handshake_out;///< Output for the handshake. + +/// Gets the current system time in milliseconds. +unsigned int RTMPStream::getNowMS(){ + timeval t; + gettimeofday(&t, 0); + return t.tv_sec + t.tv_usec/1000; +}//RTMPStream::getNowMS + + +unsigned int RTMPStream::chunk_rec_max = 128; +unsigned int RTMPStream::chunk_snd_max = 128; +unsigned int RTMPStream::rec_window_size = 0xFA00; +unsigned int RTMPStream::snd_window_size = 1024*500; +unsigned int RTMPStream::rec_window_at = 0; +unsigned int RTMPStream::snd_window_at = 0; +unsigned int RTMPStream::rec_cnt = 0; +unsigned int RTMPStream::snd_cnt = 0; + +timeval RTMPStream::lastrec; +unsigned int RTMPStream::firsttime; + +/// Holds the last sent chunk for every msg_id. +std::map RTMPStream::Chunk::lastsend; +/// Holds the last received chunk for every msg_id. +std::map RTMPStream::Chunk::lastrecv; + +/// Packs up the chunk for sending over the network. +/// \warning Do not call if you are not actually sending the resulting data! +/// \returns A std::string ready to be sent. +std::string RTMPStream::Chunk::Pack(){ + std::string output = ""; + RTMPStream::Chunk prev = lastsend[cs_id]; + unsigned int tmpi; + unsigned char chtype = 0x00; + timestamp -= firsttime; + if (prev.cs_id == cs_id){ + if (msg_stream_id == prev.msg_stream_id){ + chtype = 0x40;//do not send msg_stream_id + if (len == prev.len){ + if (msg_type_id == prev.msg_type_id){ + chtype = 0x80;//do not send len and msg_type_id + if (timestamp == prev.timestamp){ + chtype = 0xC0;//do not send timestamp + } + } + } + } + } + if (cs_id <= 63){ + output += (unsigned char)(chtype | cs_id); + }else{ + if (cs_id <= 255+64){ + output += (unsigned char)(chtype | 0); + output += (unsigned char)(cs_id - 64); + }else{ + output += (unsigned char)(chtype | 1); + output += (unsigned char)((cs_id - 64) % 256); + output += (unsigned char)((cs_id - 64) / 256); + } + } + unsigned int ntime = 0; + if (chtype != 0xC0){ + //timestamp or timestamp diff + if (chtype == 0x00){ + tmpi = timestamp; + }else{ + tmpi = timestamp - prev.timestamp; + } + if (tmpi >= 0x00ffffff){ntime = tmpi; tmpi = 0x00ffffff;} + output += (unsigned char)(tmpi / (256*256)); + output += (unsigned char)(tmpi / 256); + output += (unsigned char)(tmpi % 256); + if (chtype != 0x80){ + //len + tmpi = len; + output += (unsigned char)(tmpi / (256*256)); + output += (unsigned char)(tmpi / 256); + output += (unsigned char)(tmpi % 256); + //msg type id + output += (unsigned char)msg_type_id; + if (chtype != 0x40){ + //msg stream id + output += (unsigned char)(msg_stream_id % 256); + output += (unsigned char)(msg_stream_id / 256); + output += (unsigned char)(msg_stream_id / (256*256)); + output += (unsigned char)(msg_stream_id / (256*256*256)); + } + } + } + //support for 0x00ffffff timestamps + if (ntime){ + output += (unsigned char)(ntime % 256); + output += (unsigned char)(ntime / 256); + output += (unsigned char)(ntime / (256*256)); + output += (unsigned char)(ntime / (256*256*256)); + } + len_left = 0; + while (len_left < len){ + tmpi = len - len_left; + if (tmpi > RTMPStream::chunk_snd_max){tmpi = RTMPStream::chunk_snd_max;} + output.append(data, len_left, tmpi); + len_left += tmpi; + if (len_left < len){ + if (cs_id <= 63){ + output += (unsigned char)(0xC0 + cs_id); + }else{ + if (cs_id <= 255+64){ + output += (unsigned char)(0xC0); + output += (unsigned char)(cs_id - 64); + }else{ + output += (unsigned char)(0xC1); + output += (unsigned char)((cs_id - 64) % 256); + output += (unsigned char)((cs_id - 64) / 256); + } + } + } + } + lastsend[cs_id] = *this; + RTMPStream::snd_cnt += output.size(); + return output; +}//SendChunk + +/// Default contructor, creates an empty chunk with all values initialized to zero. +RTMPStream::Chunk::Chunk(){ + cs_id = 0; + timestamp = 0; + len = 0; + real_len = 0; + len_left = 0; + msg_type_id = 0; + msg_stream_id = 0; + data = ""; +}//constructor + +/// Packs up a chunk with the given arguments as properties. +std::string RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data){ + RTMPStream::Chunk ch; + ch.cs_id = cs_id; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = data.size(); + ch.real_len = data.size(); + ch.len_left = 0; + ch.msg_type_id = msg_type_id; + ch.msg_stream_id = msg_stream_id; + ch.data = data; + return ch.Pack(); +}//constructor + +/// Packs up a chunk with media contents. +/// \param msg_type_id Type number of the media, as per FLV standard. +/// \param data Contents of the media data. +/// \param len Length of the media data, in bytes. +/// \param ts Timestamp of the media data, relative to current system time. +std::string RTMPStream::SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts){ + RTMPStream::Chunk ch; + ch.cs_id = msg_type_id; + ch.timestamp = ts; + ch.len = len; + ch.real_len = len; + ch.len_left = 0; + ch.msg_type_id = msg_type_id; + ch.msg_stream_id = 1; + ch.data.append((char*)data, (size_t)len); + return ch.Pack(); +}//SendMedia + +/// Packs up a chunk for a control message with 1 argument. +std::string RTMPStream::SendCTL(unsigned char type, unsigned int data){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 4; + ch.real_len = 4; + ch.len_left = 0; + ch.msg_type_id = type; + ch.msg_stream_id = 0; + ch.data.resize(4); + *(int*)((char*)ch.data.c_str()) = htonl(data); + return ch.Pack(); +}//SendCTL + +/// Packs up a chunk for a control message with 2 arguments. +std::string RTMPStream::SendCTL(unsigned char type, unsigned int data, unsigned char data2){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 5; + ch.real_len = 5; + ch.len_left = 0; + ch.msg_type_id = type; + ch.msg_stream_id = 0; + ch.data.resize(5); + *(int*)((char*)ch.data.c_str()) = htonl(data); + ch.data[4] = data2; + return ch.Pack(); +}//SendCTL + +/// Packs up a chunk for a user control message with 1 argument. +std::string RTMPStream::SendUSR(unsigned char type, unsigned int data){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 6; + ch.real_len = 6; + ch.len_left = 0; + ch.msg_type_id = 4; + ch.msg_stream_id = 0; + ch.data.resize(6); + *(int*)((char*)ch.data.c_str()+2) = htonl(data); + ch.data[0] = 0; + ch.data[1] = type; + return ch.Pack(); +}//SendUSR + +/// Packs up a chunk for a user control message with 2 arguments. +std::string RTMPStream::SendUSR(unsigned char type, unsigned int data, unsigned int data2){ + RTMPStream::Chunk ch; + ch.cs_id = 2; + ch.timestamp = RTMPStream::getNowMS(); + ch.len = 10; + ch.real_len = 10; + ch.len_left = 0; + ch.msg_type_id = 4; + ch.msg_stream_id = 0; + ch.data.resize(10); + *(int*)((char*)ch.data.c_str()+2) = htonl(data); + *(int*)((char*)ch.data.c_str()+6) = htonl(data2); + ch.data[0] = 0; + ch.data[1] = type; + return ch.Pack(); +}//SendUSR + + +/// Parses the argument string into the current chunk. +/// Tries to read a whole chunk, if successful it will remove +/// the corresponding data from the input string. +/// \param indata The input string to parse and update. +/// \warning This function will destroy the current data in this chunk! +/// \returns True if a whole chunk could be read, false otherwise. +bool RTMPStream::Chunk::Parse(std::string & indata){ + gettimeofday(&RTMPStream::lastrec, 0); + unsigned int i = 0; + if (indata.size() < 3) return false;//need at least 3 bytes to continue + + unsigned char chunktype = indata[i++]; + //read the chunkstream ID properly + switch (chunktype & 0x3F){ + case 0: + cs_id = indata[i++] + 64; + break; + case 1: + cs_id = indata[i++] + 64; + cs_id += indata[i++] * 256; + break; + default: + cs_id = chunktype & 0x3F; + break; + } + + RTMPStream::Chunk prev = lastrecv[cs_id]; + + //process the rest of the header, for each chunk type + switch (chunktype & 0xC0){ + case 0x00: + if (indata.size() < i+11) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + len = indata[i++]*256*256; + len += indata[i++]*256; + len += indata[i++]; + len_left = 0; + msg_type_id = indata[i++]; + msg_stream_id = indata[i++]; + msg_stream_id += indata[i++]*256; + msg_stream_id += indata[i++]*256*256; + msg_stream_id += indata[i++]*256*256*256; + break; + case 0x40: + if (indata.size() < i+7) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + timestamp += prev.timestamp; + len = indata[i++]*256*256; + len += indata[i++]*256; + len += indata[i++]; + len_left = 0; + msg_type_id = indata[i++]; + msg_stream_id = prev.msg_stream_id; + break; + case 0x80: + if (indata.size() < i+3) return false; //can't read whole header + timestamp = indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + timestamp += prev.timestamp; + len = prev.len; + len_left = prev.len_left; + msg_type_id = prev.msg_type_id; + msg_stream_id = prev.msg_stream_id; + break; + case 0xC0: + timestamp = prev.timestamp; + len = prev.len; + len_left = prev.len_left; + msg_type_id = prev.msg_type_id; + msg_stream_id = prev.msg_stream_id; + break; + } + //calculate chunk length, real length, and length left till complete + if (len_left > 0){ + real_len = len_left; + len_left -= real_len; + }else{ + real_len = len; + } + if (real_len > RTMPStream::chunk_rec_max){ + len_left += real_len - RTMPStream::chunk_rec_max; + real_len = RTMPStream::chunk_rec_max; + } + //read extended timestamp, if neccesary + if (timestamp == 0x00ffffff){ + if (indata.size() < i+4) return false; //can't read whole header + timestamp = indata[i++]*256*256*256; + timestamp += indata[i++]*256*256; + timestamp += indata[i++]*256; + timestamp += indata[i++]; + } + + //read data if length > 0, and allocate it + if (real_len > 0){ + if (prev.len_left > 0){ + data = prev.data; + }else{ + data = ""; + } + if (indata.size() < i+real_len) return false;//can't read all data (yet) + data.append(indata, i, real_len); + indata = indata.substr(i+real_len); + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i+real_len; + if (len_left == 0){ + return true; + }else{ + return false; + } + }else{ + data = ""; + indata = indata.substr(i+real_len); + lastrecv[cs_id] = *this; + RTMPStream::rec_cnt += i+real_len; + return true; + } +}//Parse + + +/// Does the handshake. Expects handshake_in to be filled, and fills handshake_out. +/// After calling this function, don't forget to read and ignore 1536 extra bytes, +/// this is the handshake response and not interesting for us because we don't do client +/// verification. +bool RTMPStream::doHandshake(){ + char Version; + //Read C0 + Version = RTMPStream::handshake_in[0]; + uint8_t * Client = (uint8_t *)RTMPStream::handshake_in.c_str() + 1; + RTMPStream::handshake_out.resize(3073); + uint8_t * Server = (uint8_t *)RTMPStream::handshake_out.c_str() + 1; + RTMPStream::rec_cnt += 1537; + + //Build S1 Packet + *((uint32_t*)Server) = 0;//time zero + *(((uint32_t*)(Server+4))) = htonl(0x01020304);//version 1 2 3 4 + for (int i = 8; i < 3072; ++i){Server[i] = versionstring[i%16];}//"random" data + + bool encrypted = (Version == 6); + #if DEBUG >= 4 + fprintf(stderr, "Handshake version is %hhi\n", Version); + #endif + uint8_t _validationScheme = 5; + if (ValidateClientScheme(Client, 0)) _validationScheme = 0; + if (ValidateClientScheme(Client, 1)) _validationScheme = 1; + + #if DEBUG >= 4 + fprintf(stderr, "Handshake type is %hhi, encryption is %s\n", _validationScheme, encrypted?"on":"off"); + #endif + + //FIRST 1536 bytes from server response + //compute DH key position + uint32_t serverDHOffset = GetDHOffset(Server, _validationScheme); + uint32_t clientDHOffset = GetDHOffset(Client, _validationScheme); + + //generate DH key + DHWrapper dhWrapper(1024); + if (!dhWrapper.Initialize()) return false; + if (!dhWrapper.CreateSharedKey(Client + clientDHOffset, 128)) return false; + if (!dhWrapper.CopyPublicKey(Server + serverDHOffset, 128)) return false; + + if (encrypted) { + uint8_t secretKey[128]; + if (!dhWrapper.CopySharedKey(secretKey, sizeof (secretKey))) return false; + RC4_KEY _pKeyIn; + RC4_KEY _pKeyOut; + InitRC4Encryption(secretKey, (uint8_t*) & Client[clientDHOffset], (uint8_t*) & Server[serverDHOffset], &_pKeyIn, &_pKeyOut); + uint8_t data[1536]; + RC4(&_pKeyIn, 1536, data, data); + RC4(&_pKeyOut, 1536, data, data); + } + //generate the digest + uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme); + uint8_t *pTempBuffer = new uint8_t[1536 - 32]; + memcpy(pTempBuffer, Server, serverDigestOffset); + memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1536 - serverDigestOffset - 32); + uint8_t *pTempHash = new uint8_t[512]; + HMACsha256(pTempBuffer, 1536 - 32, genuineFMSKey, 36, pTempHash); + memcpy(Server + serverDigestOffset, pTempHash, 32); + delete[] pTempBuffer; + delete[] pTempHash; + + //SECOND 1536 bytes from server response + uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme); + pTempHash = new uint8_t[512]; + HMACsha256(Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash); + uint8_t *pLastHash = new uint8_t[512]; + HMACsha256(Server + 1536, 1536 - 32, pTempHash, 32, pLastHash); + memcpy(Server + 1536 * 2 - 32, pLastHash, 32); + delete[] pTempHash; + delete[] pLastHash; + //DONE BUILDING THE RESPONSE ***// + Server[-1] = Version; + RTMPStream::snd_cnt += 3073; + return true; +} diff --git a/util/rtmpchunks.h b/util/rtmpchunks.h new file mode 100644 index 00000000..b11142c8 --- /dev/null +++ b/util/rtmpchunks.h @@ -0,0 +1,65 @@ +/// \file rtmpchunks.h +/// Holds all headers for the RTMPStream namespace. + +#pragma once +#include +#include +#include +#include +#include +#include +#define DEBUG 4 + +/// Contains all functions and classes needed for RTMP connections. +namespace RTMPStream{ + + /// Gets the current system time in milliseconds. + unsigned int getNowMS(); + + extern unsigned int chunk_rec_max; ///< Maximum size for a received chunk. + extern unsigned int chunk_snd_max; ///< Maximum size for a sent chunk. + extern unsigned int rec_window_size; ///< Window size for receiving. + extern unsigned int snd_window_size; ///< Window size for sending. + extern unsigned int rec_window_at; ///< Current position of the receiving window. + extern unsigned int snd_window_at; ///< Current position of the sending window. + extern unsigned int rec_cnt; ///< Counter for total data received, in bytes. + extern unsigned int snd_cnt; ///< Counter for total data sent, in bytes. + + extern timeval lastrec; ///< Timestamp of last time data was received. + extern unsigned int firsttime; ///< Timestamp of first time a chunk was sent. + + /// Holds a single RTMP chunk, either send or receive direction. + class Chunk{ + public: + unsigned int cs_id; ///< ContentStream ID + unsigned int timestamp; ///< Timestamp of this chunk. + unsigned int len; ///< Length of the complete chunk. + unsigned int real_len; ///< Length of this particular part of it. + unsigned int len_left; ///< Length not yet received, out of complete chunk. + unsigned char msg_type_id; ///< Message Type ID + unsigned int msg_stream_id; ///< Message Stream ID + std::string data; ///< Payload of chunk. + + Chunk(); + bool Parse(std::string & data); + std::string Pack(); + + private: + static std::map lastsend; + static std::map lastrecv; + };//RTMPStream::Chunk + + std::string SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data); + std::string SendMedia(unsigned char msg_type_id, unsigned char * data, int len, unsigned int ts); + std::string SendCTL(unsigned char type, unsigned int data); + std::string SendCTL(unsigned char type, unsigned int data, unsigned char data2); + std::string SendUSR(unsigned char type, unsigned int data); + std::string SendUSR(unsigned char type, unsigned int data, unsigned int data2); + + /// This value should be set to the first 1537 bytes received. + extern std::string handshake_in; + /// This value is the handshake response that is to be sent out. + extern std::string handshake_out; + /// Does the handshake. Expects handshake_in to be filled, and fills handshake_out. + bool doHandshake(); +};//RTMPStream namespace diff --git a/util/server_setup.cpp b/util/server_setup.cpp index daa9e6c6..36e09c47 100644 --- a/util/server_setup.cpp +++ b/util/server_setup.cpp @@ -1,12 +1,34 @@ -#include +/// \file server_setup.cpp +/// Contains generic functions for setting up a DDVTECH Connector. + +#ifndef MAINHANDLER + /// Handler that is called for accepted incoming connections. + #define MAINHANDLER NoHandler + #error "No handler was set!" +#endif + + +#ifndef DEFAULT_PORT + /// Default port for this server. + #define DEFAULT_PORT 0 + #error "No default port was set!" +#endif + + +#ifndef CONFIGSECT + /// Configuration file section for this server. + #define CONFIGSECT None + #error "No configuration file section was set!" +#endif + #include "ddv_socket.h" //DDVTech Socket wrapper -#include "flv_tag.h" //FLV parsing with DDVTech Socket wrapper +#include #include #include #include -#define defstr(x) #x //converts a define name to string -#define defstrh(x) "[" defstr(x) "]" //converts define name to [string] -DDV::ServerSocket server_socket(-1); +#define defstr(x) #x ///< converts a define name to string +#define defstrh(x) "[" defstr(x) "]" ///< converts define name to [string] +DDV::ServerSocket server_socket(-1); ///< Placeholder for the server socket /// Basic signal handler. Disconnects the server_socket if it receives /// a SIGINT, SIGHUP or SIGTERM signal, but does nothing for SIGPIPE. @@ -26,10 +48,10 @@ void signal_handler (int signum){ /// Generic main entry point and loop for DDV Connectors. /// This sets up the proper termination handler, checks commandline options, /// parses config files and opens a listening socket on the requested port. -/// Any incoming connections will be accepted and start up the function MAINHANDLER, -/// which should be #defined before including server_setup.cpp. -/// The default port is set by #define DEFAULT_PORT. -/// The configuration file section is set by #define CONFIGSECT. +/// Any incoming connections will be accepted and start up the function #MAINHANDLER, +/// which should be defined before including server_setup.cpp. +/// The default port is set by define #DEFAULT_PORT. +/// The configuration file section is set by define #CONFIGSECT. int main(int argc, char ** argv){ DDV::Socket S;//placeholder for incoming connections