public bool IsValid(Rtsp.Messages.RtspMessage received_message) { string authorization = received_message.Headers["Authorization"]; // Check Username and Password if (authentication_type == Type.Basic && authorization.StartsWith("Basic ")) { string base64_str = authorization.Substring(6); // remove 'Basic ' byte[] data = Convert.FromBase64String(base64_str); string decoded = Encoding.UTF8.GetString(data); int split_position = decoded.IndexOf(':'); string decoded_username = decoded.Substring(0, split_position); string decoded_password = decoded.Substring(split_position + 1); if ((decoded_username == username) && (decoded_password == password)) { Console.WriteLine("Basic Authorization passed"); return(true); } else { Console.WriteLine("Basic Authorization failed"); return(false); } } // Check Username, URI, Nonce and the MD5 hashed Response if (authentication_type == Type.Digest && authorization.StartsWith("Digest ")) { string value_str = authorization.Substring(7); // remove 'Digest ' string[] values = value_str.Split(','); string auth_header_username = null; string auth_header_realm = null; string auth_header_nonce = null; string auth_header_uri = null; string auth_header_response = null; string message_method = null; string message_uri = null; try { message_method = received_message.Command.Split(' ')[0]; message_uri = received_message.Command.Split(' ')[1]; } catch {} foreach (string value in values) { string[] tuple = value.Trim().Split(new char[] { '=' }, 2); // split on first '=' if (tuple.Length == 2 && tuple[0].Equals("username")) { auth_header_username = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (tuple.Length == 2 && tuple[0].Equals("realm")) { auth_header_realm = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (tuple.Length == 2 && tuple[0].Equals("nonce")) { auth_header_nonce = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (tuple.Length == 2 && tuple[0].Equals("uri")) { auth_header_uri = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } else if (tuple.Length == 2 && tuple[0].Equals("response")) { auth_header_response = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes } } // Create the MD5 Hash using all parameters passed in the Auth Header with the // addition of the 'Password' String hashA1 = CalculateMD5Hash(md5, auth_header_username + ":" + auth_header_realm + ":" + this.password); String hashA2 = CalculateMD5Hash(md5, message_method + ":" + auth_header_uri); String expected_response = CalculateMD5Hash(md5, hashA1 + ":" + auth_header_nonce + ":" + hashA2); // Check if everything matches // ToDo - extract paths from the URIs (ignoring SETUP's trackID) if ((auth_header_username == this.username) && (auth_header_realm == this.realm) && (auth_header_nonce == this.nonce) && (auth_header_response == expected_response) ) { Console.WriteLine("Digest Authorization passed"); return(true); } else { Console.WriteLine("Digest Authorization failed"); return(false); } } return(false); }
// Process each RTSP message that is received private void RTSP_Message_Received(object sender, RtspChunkEventArgs e) { // Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message Rtsp.RtspListener listener = sender as Rtsp.RtspListener; Rtsp.Messages.RtspMessage message = e.Message as Rtsp.Messages.RtspMessage; Console.WriteLine("RTSP message received " + message); // Update the RTSP Keepalive Timeout // We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { // found the connection connection.time_since_last_rtsp_keepalive = DateTime.UtcNow; break; } } } #region Handle OPTIONS message if (message is Rtsp.Messages.RtspRequestOptions) { // Create the reponse to OPTIONS Rtsp.Messages.RtspResponse options_response = (e.Message as Rtsp.Messages.RtspRequestOptions).CreateResponse("DESCRIBE,SETUP,PLAY,TEARDOWN"); listener.SendMessage(options_response); } #endregion #region Handle DESCRIBE message if (message is Rtsp.Messages.RtspRequestDescribe) { String requested_url = (message as Rtsp.Messages.RtspRequestDescribe).RtspUri.ToString(); Console.WriteLine("Request for " + requested_url); // TODO. Check the requsted_url is valid. In this example we accept any RTSP URL // Make the Base64 SPS and PPS String sps_str = Convert.ToBase64String(StreamInfo.SPS); String pps_str = Convert.ToBase64String(StreamInfo.PPS); StringBuilder sdp = new StringBuilder(); // Generate the SDP // The sprop-parameter-sets provide the SPS and PPS for H264 video // The packetization-mode defines the H264 over RTP payloads used but is Optional sdp.Append("v=0\n"); sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n"); sdp.Append($"s=SysDVR - https://github.com/exelix11/sysdvr - [PID {Process.GetCurrentProcess().Id}]\n"); if (video_source != null) { sdp.Append("m=video 0 RTP/AVP 96\n"); sdp.Append("c=IN IP4 0.0.0.0\n"); sdp.Append("a=control:trackID=0\n"); sdp.Append("a=rtpmap:96 H264/90000\n"); sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n"); } if (audio_source != null) { sdp.Append("m=audio 0 RTP/AVP 97\n"); sdp.Append("a=rtpmap:97 L16/48000/2\n"); sdp.Append("a=control:trackID=1\n"); } byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString()); // Create the reponse to DESCRIBE // This must include the Session Description Protocol (SDP) Rtsp.Messages.RtspResponse describe_response = (e.Message as Rtsp.Messages.RtspRequestDescribe).CreateResponse(); describe_response.AddHeader("Content-Base: " + requested_url); describe_response.AddHeader("Content-Type: application/sdp"); describe_response.Data = sdp_bytes; describe_response.AdjustContentLength(); listener.SendMessage(describe_response); } #endregion #region Handle SETUP message if (message is Rtsp.Messages.RtspRequestSetup) { var setupMessage = message as Rtsp.Messages.RtspRequestSetup; // Check the RTSP transport // If it is UDP or Multicast, create the sockets // If it is RTP over RTSP we send data via the RTSP Listener // FIXME client may send more than one possible transport. // very rare Rtsp.Messages.RtspTransport transport = setupMessage.GetTransports()[0]; // Construct the Transport: reply from the Server to the client Rtsp.Messages.RtspTransport transport_reply = new Rtsp.Messages.RtspTransport(); transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.TCP) { // RTP over RTSP mode transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.TCP; transport_reply.Interleaved = new Rtsp.Messages.PortCouple(transport.Interleaved.First, transport.Interleaved.Second); } Rtsp.UDPSocket udp_pair = null; if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == false) { Boolean udp_supported = true; if (udp_supported) { // RTP over UDP mode // Create a pair of UDP sockets - One is for the Video, one is for the RTCP udp_pair = new Rtsp.UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => { // RTCP data received Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString()); }; udp_pair.Start(); // start listening for data on the UDP ports // Pass the Port of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = false; transport_reply.ClientPort = new Rtsp.Messages.PortCouple(udp_pair.data_port, udp_pair.control_port); } else { transport_reply = null; } } if (transport.LowerTransport == Rtsp.Messages.RtspTransport.LowerTransportType.UDP && transport.IsMulticast == true) { // RTP over Multicast UDP mode} // Create a pair of UDP sockets in Multicast Mode // Pass the Ports of the two sockets back in the reply transport_reply.LowerTransport = Rtsp.Messages.RtspTransport.LowerTransportType.UDP; transport_reply.IsMulticast = true; transport_reply.Port = new Rtsp.Messages.PortCouple(7000, 7001); // FIX // for now until implemented transport_reply = null; } if (transport_reply != null) { // Update the session with transport information String copy_of_session_id = ""; lock (rtsp_list) { foreach (RTSPConnection connection in rtsp_list) { if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) { var stream = int.Parse(setupMessage.RtspUri.LocalPath.Split('=')[1]); connection.sessions[stream].is_active = true; // found the connection // Add the transports to the connection connection.sessions[stream].client_transport = transport; connection.sessions[stream].transport_reply = transport_reply; // If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname connection.sessions[stream].udp_pair = udp_pair; connection.sessions[stream].session_id = session_handle.ToString(); session_handle++; // Copy the Session ID copy_of_session_id = connection.sessions[stream].session_id; break; } } } Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); setup_response.Headers[Rtsp.Messages.RtspHeaderNames.Transport] = transport_reply.ToString(); setup_response.Session = copy_of_session_id; listener.SendMessage(setup_response); } else { Rtsp.Messages.RtspResponse setup_response = setupMessage.CreateResponse(); // unsuported transport setup_response.ReturnCode = 461; listener.SendMessage(setup_response); } } #endregion #region Handle PLAY message // Must have a Session ID if (message is Rtsp.Messages.RtspRequestPlay) { lock (rtsp_list) { // Search for the Session in the Sessions List. Change the state to "PLAY" bool session_found = false; foreach (RTSPConnection connection in rtsp_list) { if (message.Session == connection.video.session_id || message.Session == connection.audio.session_id) { // found the session session_found = true; connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO) string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end string rtp_info = "url=" + ((Rtsp.Messages.RtspRequestPlay)message).RtspUri + ";seq=" + connection.video.sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp; // Send the reply Rtsp.Messages.RtspResponse play_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_response.AddHeader("Range: " + range); play_response.AddHeader("RTP-Info: " + rtp_info); listener.SendMessage(play_response); break; } } if (session_found == false) { // Session ID was not found in the list of Sessions. Send a 454 error Rtsp.Messages.RtspResponse play_failed_response = (e.Message as Rtsp.Messages.RtspRequestPlay).CreateResponse(); play_failed_response.ReturnCode = 454; // Session Not Found listener.SendMessage(play_failed_response); } } } #endregion #region Handle TEARDOWN // Must have a Session ID if (message is Rtsp.Messages.RtspRequestTeardown) { lock (rtsp_list) { // Search for the Session in the Sessions List. foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list { rtsp_list.Remove(connection); connection.Dispose(); listener.Dispose(); } } } #endregion }
public static Authentication GetAuthenticationInfo(Rtsp.Messages.RtspMessage received_message) { string authorization = received_message.Headers["Authorization"]; Authentication authInfo = null; if (authorization.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase)) { string base64_str = authorization.Substring(6); // remove 'Basic ' byte[] data = Convert.FromBase64String(base64_str); string decoded = Encoding.UTF8.GetString(data); int split_position = decoded.IndexOf(':'); string decoded_username = decoded.Substring(0, split_position); string decoded_password = decoded.Substring(split_position + 1); authInfo = new Authentication(decoded_username, decoded_password, null, Type.Basic); } else { _logger.Warn($"Authentication type not supported:{authorization}"); } // Check Username, URI, Nonce and the MD5 hashed Response //else if (authorization.StartsWith("Digest ",StringComparison.OrdinalIgnoreCase)) //{ // string value_str = authorization.Substring(7); // remove 'Digest ' // string[] values = value_str.Split(','); // string auth_header_username = null; // string auth_header_realm = null; // string auth_header_nonce = null; // string auth_header_uri = null; // string auth_header_response = null; // string message_method = null; // string message_uri = null; // try // { // message_method = received_message.Command.Split(' ')[0]; // message_uri = received_message.Command.Split(' ')[1]; // } // catch { } // foreach (string value in values) // { // string[] tuple = value.Trim().Split(new char[] { '=' }, 2); // split on first '=' // if (tuple.Length == 2 && tuple[0].Equals("username")) // { // auth_header_username = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes // } // else if (tuple.Length == 2 && tuple[0].Equals("realm")) // { // auth_header_realm = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes // } // else if (tuple.Length == 2 && tuple[0].Equals("nonce")) // { // auth_header_nonce = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes // } // else if (tuple.Length == 2 && tuple[0].Equals("uri")) // { // auth_header_uri = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes // } // else if (tuple.Length == 2 && tuple[0].Equals("response")) // { // auth_header_response = tuple[1].Trim(new char[] { ' ', '\"' }); // trim space and quotes // } // } // // Create the MD5 Hash using all parameters passed in the Auth Header with the // // addition of the 'Password' // String hashA1 = CalculateMD5Hash(md5, auth_header_username + ":" + auth_header_realm + ":" + this.password); // String hashA2 = CalculateMD5Hash(md5, message_method + ":" + auth_header_uri); // String expected_response = CalculateMD5Hash(md5, hashA1 + ":" + auth_header_nonce + ":" + hashA2); // // Check if everything matches // // ToDo - extract paths from the URIs (ignoring SETUP's trackID) // if ((auth_header_username == this.username) // && (auth_header_realm == this.realm) // && (auth_header_nonce == this.nonce) // && (auth_header_response == expected_response) // ) // { // _logger.Debug("Digest Authorization passed"); // return true; // } // else // { // _logger.Debug("Digest Authorization failed"); // return false; // } //} return(authInfo); }