/********** This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. (See .) This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA **********/ // "liveMedia" // Copyright (c) 1996-2024 Live Networks, Inc. All rights reserved. // A RTSP server // C++ header #ifndef _RTSP_SERVER_HH #define _RTSP_SERVER_HH #ifndef _GENERIC_MEDIA_SERVER_HH #include "GenericMediaServer.hh" #endif #ifndef _DIGEST_AUTHENTICATION_HH #include "DigestAuthentication.hh" #endif class LIVEMEDIA_API RTSPServer: public GenericMediaServer { public: static RTSPServer* createNew(UsageEnvironment& env, Port ourPort = 554, UserAuthenticationDatabase* authDatabase = NULL, unsigned reclamationSeconds = 65); // If ourPort.num() == 0, we'll choose the port number // Note: The caller is responsible for reclaiming "authDatabase" // If "reclamationSeconds" > 0, then the "RTSPClientSession" state for // each client will get reclaimed (and the corresponding RTP stream(s) // torn down) if no RTSP commands - or RTCP "RR" packets - from the // client are received in at least "reclamationSeconds" seconds. static Boolean lookupByName(UsageEnvironment& env, char const* name, RTSPServer*& resultServer); typedef void (responseHandlerForREGISTER)(RTSPServer* rtspServer, unsigned requestId, int resultCode, char* resultString); unsigned registerStream(ServerMediaSession* serverMediaSession, char const* remoteClientNameOrAddress, portNumBits remoteClientPortNum, responseHandlerForREGISTER* responseHandler, char const* username = NULL, char const* password = NULL, Boolean receiveOurStreamViaTCP = False, char const* proxyURLSuffix = NULL); // 'Register' the stream represented by "serverMediaSession" with the given remote client (specifed by name and port number). // This is done using our custom "REGISTER" RTSP command. // The function returns a unique number that can be used to identify the request; this number is also passed to "responseHandler". // When a response is received from the remote client (or the "REGISTER" request fails), the specified response handler // (if non-NULL) is called. (Note that the "resultString" passed to the handler was dynamically allocated, // and should be delete[]d by the handler after use.) // If "receiveOurStreamViaTCP" is True, then we're requesting that the remote client access our stream using RTP/RTCP-over-TCP. // (Otherwise, the remote client may choose regular RTP/RTCP-over-UDP streaming.) // "proxyURLSuffix" (optional) is used only when the remote client is also a proxy server. // It tells the proxy server the suffix that it should use in its "rtsp://" URL (when front-end clients access the stream) typedef void (responseHandlerForDEREGISTER)(RTSPServer* rtspServer, unsigned requestId, int resultCode, char* resultString); unsigned deregisterStream(ServerMediaSession* serverMediaSession, char const* remoteClientNameOrAddress, portNumBits remoteClientPortNum, responseHandlerForDEREGISTER* responseHandler, char const* username = NULL, char const* password = NULL, char const* proxyURLSuffix = NULL); // Used to turn off a previous "registerStream()" - using our custom "DEREGISTER" RTSP command. char* rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket = -1, Boolean useIPv6 = False) const; // returns a "rtsp://" URL that could be used to access the // specified session (which must already have been added to // us using "addServerMediaSession()". // This string is dynamically allocated; caller should delete[] // (If "clientSocket" is non-negative, then it is used (by calling "getsockname()") to determine // the IP address to be used in the URL.) // Shortcuts: char* ipv4rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket = -1) { return rtspURL(serverMediaSession, clientSocket, False); } char* ipv6rtspURL(ServerMediaSession const* serverMediaSession, int clientSocket = -1) { return rtspURL(serverMediaSession, clientSocket, True); } char* rtspURLPrefix(int clientSocket = -1, Boolean useIPv6 = False) const; // like "rtspURL()", except that it returns just the common prefix used by // each session's "rtsp://" URL. // This string is dynamically allocated; caller should delete[] // Shortcuts: char* ipv4rtspURLPrefix(int clientSocket = -1) { return rtspURLPrefix(clientSocket, False); } char* ipv6rtspURLPrefix(int clientSocket = -1) { return rtspURLPrefix(clientSocket, True); } UserAuthenticationDatabase* setAuthenticationDatabase(UserAuthenticationDatabase* newDB); // Changes the server's authentication database to "newDB", returning a pointer to the old database (if there was one). // "newDB" may be NULL (you can use this to disable authentication at runtime, if desired). void disableStreamingRTPOverTCP() { fAllowStreamingRTPOverTCP = False; } Boolean setUpTunnelingOverHTTP(Port httpPort); // (Attempts to) enable RTSP-over-HTTP tunneling on the specified port. // Returns True iff the specified port can be used in this way (i.e., it's not already being used for a separate HTTP server). // Note: RTSP-over-HTTP tunneling is described in // http://mirror.informatimago.com/next/developer.apple.com/quicktime/icefloe/dispatch028.html // and http://images.apple.com/br/quicktime/pdf/QTSS_Modules.pdf portNumBits httpServerPortNum() const; // in host byte order. (Returns 0 if not present.) void setTLSState(char const* certFileName, char const* privKeyFileName, Boolean weServeSRTP = True, Boolean weEncryptSRTP = True); protected: RTSPServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort, UserAuthenticationDatabase* authDatabase, unsigned reclamationSeconds); // called only by createNew(); virtual ~RTSPServer(); virtual char const* allowedCommandNames(); // used to implement "RTSPClientConnection::handleCmd_OPTIONS()" virtual Boolean weImplementREGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, char const* proxyURLSuffix, char*& responseStr); // used to implement "RTSPClientConnection::handleCmd_REGISTER()" // Note: "responseStr" is dynamically allocated (or NULL), and should be delete[]d after the call virtual void implementCmd_REGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, char const* url, char const* urlSuffix, int socketToRemoteServer, Boolean deliverViaTCP, char const* proxyURLSuffix); // used to implement "RTSPClientConnection::handleCmd_REGISTER()" virtual UserAuthenticationDatabase* getAuthenticationDatabaseForCommand(char const* cmdName); virtual Boolean specialClientAccessCheck(int clientSocket, struct sockaddr_storage const& clientAddr, char const* urlSuffix); // a hook that allows subclassed servers to do server-specific access checking // on each client (e.g., based on client IP address), without using digest authentication. virtual Boolean specialClientUserAccessCheck(int clientSocket, struct sockaddr_storage const& clientAddr, char const* urlSuffix, char const *username); // another hook that allows subclassed servers to do server-specific access checking // - this time after normal digest authentication has already taken place (and would otherwise allow access). // (This test can only be used to further restrict access, not to grant additional access.) virtual void specialHandlingOfAuthenticationFailure(int clientSocket, struct sockaddr_storage const& clientAddr, char const* urlSuffix); // a hook that allows subclassed servers to take extra action whenevever an authentication failure occurs public: // redefined virtual functions virtual Boolean isRTSPServer() const; virtual void addServerMediaSession(ServerMediaSession* serverMediaSession); public: // should be protected, but some old compilers complain otherwise // The state of a TCP connection used by a RTSP client: class RTSPClientSession; // forward class RTSPClientConnection: public GenericMediaServer::ClientConnection { public: // A data structure that's used to implement the "REGISTER" command: class ParamsForREGISTER { public: ParamsForREGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, RTSPClientConnection* ourConnection, char const* url, char const* urlSuffix, Boolean reuseConnection, Boolean deliverViaTCP, char const* proxyURLSuffix); virtual ~ParamsForREGISTER(); private: friend class RTSPClientConnection; char const* fCmd; RTSPClientConnection* fOurConnection; char* fURL; char* fURLSuffix; Boolean fReuseConnection, fDeliverViaTCP; char* fProxyURLSuffix; }; protected: // redefined virtual functions: virtual void handleRequestBytes(int newBytesRead); protected: RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_storage const& clientAddr, Boolean useTLS = False); virtual ~RTSPClientConnection(); friend class RTSPServer; friend class RTSPClientSession; // Make the handler functions for each command virtual, to allow subclasses to reimplement them, if necessary: virtual void handleCmd_OPTIONS(); // You probably won't need to subclass/reimplement this function; reimplement "RTSPServer::allowedCommandNames()" instead. virtual void handleCmd_GET_PARAMETER(char const* fullRequestStr); // when operating on the entire server virtual void handleCmd_SET_PARAMETER(char const* fullRequestStr); // when operating on the entire server virtual void handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr); static void DESCRIBELookupCompletionFunction(void* clientData, ServerMediaSession* sessionLookedUp); virtual void handleCmd_DESCRIBE_afterLookup(ServerMediaSession* session); virtual void handleCmd_REGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, char const* url, char const* urlSuffix, char const* fullRequestStr, Boolean reuseConnection, Boolean deliverViaTCP, char const* proxyURLSuffix); // You probably won't need to subclass/reimplement this function; // reimplement "RTSPServer::weImplementREGISTER()" and "RTSPServer::implementCmd_REGISTER()" instead. virtual void handleCmd_bad(); virtual void handleCmd_notSupported(); virtual void handleCmd_redirect(char const* urlSuffix); virtual void handleCmd_notFound(); virtual void handleCmd_sessionNotFound(); virtual void handleCmd_unsupportedTransport(); // Support for optional RTSP-over-HTTP tunneling: virtual Boolean parseHTTPRequestString(char* resultCmdName, unsigned resultCmdNameMaxSize, char* urlSuffix, unsigned urlSuffixMaxSize, char* sessionCookie, unsigned sessionCookieMaxSize, char* acceptStr, unsigned acceptStrMaxSize); virtual void handleHTTPCmd_notSupported(); virtual void handleHTTPCmd_notFound(); virtual void handleHTTPCmd_OPTIONS(); virtual void handleHTTPCmd_TunnelingGET(char const* sessionCookie); virtual Boolean handleHTTPCmd_TunnelingPOST(char const* sessionCookie, unsigned char const* extraData, unsigned extraDataSize); virtual void handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* fullRequestStr); protected: void resetRequestBuffer(); void closeSocketsRTSP(); static void handleAlternativeRequestByte(void*, u_int8_t requestByte); void handleAlternativeRequestByte1(u_int8_t requestByte); Boolean authenticationOK(char const* cmdName, char const* urlSuffix, char const* fullRequestStr); void changeClientInputSocket(int newSocketNum, ServerTLSState const* newTLSState, unsigned char const* extraData, unsigned extraDataSize); // used to implement RTSP-over-HTTP tunneling static void continueHandlingREGISTER(ParamsForREGISTER* params); virtual void continueHandlingREGISTER1(ParamsForREGISTER* params); // Shortcuts for setting up a RTSP response (prior to sending it): void setRTSPResponse(char const* responseStr); void setRTSPResponse(char const* responseStr, u_int32_t sessionId); void setRTSPResponse(char const* responseStr, char const* contentStr); void setRTSPResponse(char const* responseStr, u_int32_t sessionId, char const* contentStr); RTSPServer& fOurRTSPServer; // same as ::fOurServer int& fClientInputSocket; // aliased to ::fOurSocket int fClientOutputSocket; ServerTLSState fPOSTSocketTLS; // used only for RTSP-over-HTTPS int fAddressFamily; Boolean fIsActive; unsigned char* fLastCRLF; unsigned fRecursionCount; char const* fCurrentCSeq; Authenticator fCurrentAuthenticator; // used if access control is needed char* fOurSessionCookie; // used for optional RTSP-over-HTTP tunneling unsigned fBase64RemainderCount; // used for optional RTSP-over-HTTP tunneling (possible values: 0,1,2,3) unsigned fScheduledDelayedTask; }; // The state of an individual client session (using one or more sequential TCP connections) handled by a RTSP server: class LIVEMEDIA_API RTSPClientSession: public GenericMediaServer::ClientSession { protected: RTSPClientSession(RTSPServer& ourServer, u_int32_t sessionId); virtual ~RTSPClientSession(); friend class RTSPServer; friend class RTSPClientConnection; // Make the handler functions for each command virtual, to allow subclasses to redefine them: virtual void handleCmd_SETUP(RTSPClientConnection* ourClientConnection, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr); static void SETUPLookupCompletionFunction1(void* clientData, ServerMediaSession* sessionLookedUp); virtual void handleCmd_SETUP_afterLookup1(ServerMediaSession* sms); static void SETUPLookupCompletionFunction2(void* clientData, ServerMediaSession* sessionLookedUp); virtual void handleCmd_SETUP_afterLookup2(ServerMediaSession* sms); virtual void handleCmd_withinSession(RTSPClientConnection* ourClientConnection, char const* cmdName, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr); virtual void handleCmd_TEARDOWN(RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession); virtual void handleCmd_PLAY(RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession, char const* fullRequestStr); virtual void handleCmd_PAUSE(RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession); virtual void handleCmd_GET_PARAMETER(RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession, char const* fullRequestStr); virtual void handleCmd_SET_PARAMETER(RTSPClientConnection* ourClientConnection, ServerMediaSubsession* subsession, char const* fullRequestStr); protected: void deleteStreamByTrack(unsigned trackNum); void reclaimStreamStates(); Boolean isMulticast() const { return fIsMulticast; } // Shortcuts for setting up a RTSP response (prior to sending it): void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr) { ourClientConnection->setRTSPResponse(responseStr); } void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, u_int32_t sessionId) { ourClientConnection->setRTSPResponse(responseStr, sessionId); } void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, char const* contentStr) { ourClientConnection->setRTSPResponse(responseStr, contentStr); } void setRTSPResponse(RTSPClientConnection* ourClientConnection, char const* responseStr, u_int32_t sessionId, char const* contentStr) { ourClientConnection->setRTSPResponse(responseStr, sessionId, contentStr); } protected: RTSPServer& fOurRTSPServer; // same as ::fOurServer Boolean fIsMulticast, fStreamAfterSETUP; unsigned char fTCPStreamIdCount; // used for (optional) RTP/TCP Boolean usesTCPTransport() const { return fTCPStreamIdCount > 0; } unsigned fNumStreamStates; struct streamState { ServerMediaSubsession* subsession; int tcpSocketNum; void* streamToken; } * fStreamStates; // Member variables used to implement "handleCmd_SETUP()": RTSPServer::RTSPClientConnection* fOurClientConnection; char const* fURLPreSuffix; char const* fURLSuffix; char const* fFullRequestStr; char const* fTrackId; }; protected: // redefined virtual functions // If you subclass "RTSPClientConnection", then you must also redefine this virtual function in order // to create new objects of your subclass: virtual ClientConnection* createNewClientConnection(int clientSocket, struct sockaddr_storage const& clientAddr); protected: // If you subclass "RTSPClientSession", then you must also redefine this virtual function in order // to create new objects of your subclass: virtual ClientSession* createNewClientSession(u_int32_t sessionId); private: static void incomingConnectionHandlerHTTPIPv4(void*, int /*mask*/); void incomingConnectionHandlerHTTPIPv4(); static void incomingConnectionHandlerHTTPIPv6(void*, int /*mask*/); void incomingConnectionHandlerHTTPIPv6(); void noteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum); void unnoteTCPStreamingOnSocket(int socketNum, RTSPClientSession* clientSession, unsigned trackNum); void stopTCPStreamingOnSocket(int socketNum); private: friend class RTSPClientConnection; friend class RTSPClientSession; friend class RegisterRequestRecord; friend class DeregisterRequestRecord; int fHTTPServerSocketIPv4, fHTTPServerSocketIPv6; // for optional RTSP-over-HTTP tunneling Port fHTTPServerPort; // ditto HashTable* fClientConnectionsForHTTPTunneling; // maps client-supplied 'session cookie' strings to "RTSPClientConnection"s // (used only for optional RTSP-over-HTTP tunneling) HashTable* fTCPStreamingDatabase; // maps TCP socket numbers to ids of sessions that are streaming over it (RTP/RTCP-over-TCP) HashTable* fPendingRegisterOrDeregisterRequests; unsigned fRegisterOrDeregisterRequestCounter; UserAuthenticationDatabase* fAuthDB; Boolean fAllowStreamingRTPOverTCP; // by default, True Boolean fOurConnectionsUseTLS; // by default, False Boolean fWeServeSRTP; // used only if "fOurConnectionsUseTLS" is True Boolean fWeEncryptSRTP; // used only if "fWeServeSRTP" is True }; ////////// A subclass of "RTSPServer" that implements the "REGISTER" command to set up proxying on the specified URL ////////// class LIVEMEDIA_API RTSPServerWithREGISTERProxying: public RTSPServer { public: static RTSPServerWithREGISTERProxying* createNew(UsageEnvironment& env, Port ourPort = 554, UserAuthenticationDatabase* authDatabase = NULL, UserAuthenticationDatabase* authDatabaseForREGISTER = NULL, unsigned reclamationSeconds = 65, Boolean streamRTPOverTCP = False, int verbosityLevelForProxying = 0, char const* backEndUsername = NULL, char const* backEndPassword = NULL); protected: RTSPServerWithREGISTERProxying(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort, UserAuthenticationDatabase* authDatabase, UserAuthenticationDatabase* authDatabaseForREGISTER, unsigned reclamationSeconds, Boolean streamRTPOverTCP, int verbosityLevelForProxying, char const* backEndUsername, char const* backEndPassword); // called only by createNew(); virtual ~RTSPServerWithREGISTERProxying(); protected: // redefined virtual functions virtual char const* allowedCommandNames(); virtual Boolean weImplementREGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, char const* proxyURLSuffix, char*& responseStr); virtual void implementCmd_REGISTER(char const* cmd/*"REGISTER" or "DEREGISTER"*/, char const* url, char const* urlSuffix, int socketToRemoteServer, Boolean deliverViaTCP, char const* proxyURLSuffix); virtual UserAuthenticationDatabase* getAuthenticationDatabaseForCommand(char const* cmdName); private: Boolean fStreamRTPOverTCP; int fVerbosityLevelForProxying; unsigned fRegisteredProxyCounter; char* fAllowedCommandNames; UserAuthenticationDatabase* fAuthDBForREGISTER; char* fBackEndUsername; char* fBackEndPassword; }; // A special version of "parseTransportHeader()", used just for parsing the "Transport:" header // in an incoming "REGISTER" command: LIVEMEDIA_API void parseTransportHeaderForREGISTER(char const* buf, // in Boolean &reuseConnection, // out Boolean& deliverViaTCP, // out char*& proxyURLSuffix); // out #endif