/* * Mentalis.org Security Library * * Copyright © 2002-2005, The KPD-Team * All rights reserved. * http://www.mentalis.org/ * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Neither the name of the KPD-Team, nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.IO; using System.Net; using System.Net.Sockets; using Org.Mentalis.Security.Ssl.Shared; namespace Org.Mentalis.Security.Ssl { /// /// Provides the underlying stream of data for secure network access. /// public class SecureNetworkStream : Stream { /// /// Creates a new instance of the SecureNetworkStream class for the specified . /// /// The SecureSocket that provides the network data. /// is a null reference (Nothing in Visual Basic). /// is not connected -or- the SocketType property of is not SocketType.Stream. /// is a nonblocking socket. public SecureNetworkStream(SecureSocket socket) : this(socket, FileAccess.ReadWrite, false) {} /// /// Creates a new instance of the SecureNetworkStream class for the specified . /// /// The SecureSocket that provides the network data. /// true if the socket will be owned by this NetworkStream instance; otherwise, false. /// is a null reference (Nothing in Visual Basic). /// is not connected -or- the SocketType property of is not SocketType.Stream. /// is a nonblocking socket. public SecureNetworkStream(SecureSocket socket, bool ownsSocket) : this(socket, FileAccess.ReadWrite, ownsSocket) {} /// /// Creates a new instance of the SecureNetworkStream class for the specified . /// /// The SecureSocket that provides the network data. /// One of the values that sets the CanRead and CanWrite properties of the SecureNetworkStream. /// is a null reference (Nothing in Visual Basic). /// is not connected -or- The SocketType property of socket is not SocketType.Stream. /// is a nonblocking socket. public SecureNetworkStream(SecureSocket socket, FileAccess access) : this(socket, access, false) {} /// /// Creates a new instance of the SecureNetworkStream class for the specified . /// /// The SecureSocket that provides the network data. /// One of the FileAccess values that sets the CanRead and CanWrite properties of the SecureNetworkStream. /// true if the socket will be owned by this SecureNetworkStream instance; otherwise, false. /// is a null reference (Nothing in Visual Basic). /// is not connected -or- the SocketType property of socket is not SocketType.Stream. /// is a nonblocking socket. public SecureNetworkStream(SecureSocket socket, FileAccess access, bool ownsSocket) { if (socket == null) throw new ArgumentNullException(); if (!socket.Blocking) throw new IOException(); if (!socket.Connected || socket.SocketType != SocketType.Stream) throw new ArgumentException(); m_CanRead = (access == FileAccess.Read || access == FileAccess.ReadWrite); m_CanWrite = (access == FileAccess.Write || access == FileAccess.ReadWrite); m_OwnsSocket = ownsSocket; m_Socket = socket; } /// /// Gets a value that indicates whether the current stream supports writing. /// /// true if data can be written to the stream; otherwise, false. public override bool CanRead { get { return m_CanRead; } } /// /// Gets a value that indicates whether the current stream supports writing. /// /// true if data can be written to the stream; otherwise, false. public override bool CanWrite { get { return m_CanWrite; } } /// /// Gets a value indicating whether the stream supports seeking. This property always returns false. /// /// false to indicate that SecureNetworkStream cannot seek a specific location in the stream. public override bool CanSeek { get { return false; } } /// /// Flushes data from the stream. This method is reserved for future use. /// /// /// The Flush method implements the Stream.Flush method but, because SecureNetworkStream is not buffered, has no effect on secure network streams. Calling the Flush method will not throw an exception. /// public override void Flush() {} /// /// The length of the data available on the stream. This property always throws a NotSupportedException. /// /// The length of the data available on the stream. This property is not supported. /// The Length property is not supported. public override long Length { get { throw new NotSupportedException(); } } /// /// Gets or sets the current position in the stream. This property always throws a NotSupportedException. /// /// The current position in the stream. This property is not supported. /// The Position property is not supported. public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } /// /// Sets the current position of the stream to the given value. This method always throws a NotSupportedException. /// /// This parameter is not used. /// This parameter is not used. /// The position in the stream. This method is not supported. /// The Seek method is not supported. public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } /// /// Sets the length of the stream. This method always throws a NotSupportedException. /// /// This parameter is not used. /// The SetLength method is not supported. public override void SetLength(long value) { throw new NotSupportedException(); } /// /// Gets the underlying network connection. /// /// A that represents the underlying network connection. protected SecureSocket Socket { get { return m_Socket; } } /// /// Reads data from the stream. /// /// The location in memory to store data read from the stream. /// The location in the buffer to begin storing the data to. /// The number of bytes to read from the stream. /// The number of bytes read from the stream. /// is a null reference (Nothing in Visual Basic). /// The specified or exceeds the size of . /// There is a failure while reading from the network. public override int Read(byte[] buffer, int offset, int size) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || offset > buffer.Length || size < 0 || size > buffer.Length - offset) throw new ArgumentOutOfRangeException(); if (Socket == null) throw new IOException(); try { return Socket.Receive(buffer, offset, size, SocketFlags.None); } catch (Exception e) { throw new IOException("An I/O exception occurred.", e); } } /// /// Writes data to the stream. /// /// The data to write to the stream. /// The location in the buffer to start writing data from. /// The number of bytes to write to the stream. /// is a null reference (Nothing in Visual Basic). /// The specified or exceeds the size of . /// There is a failure while writing to the network. public override void Write(byte[] buffer, int offset, int size) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || offset > buffer.Length || size < 0 || size > buffer.Length - offset) throw new ArgumentOutOfRangeException(); if (Socket == null) throw new IOException(); try { Socket.Send(buffer, offset, size, SocketFlags.None); } catch (Exception e) { throw new IOException("An I/O exception occurred.", e); } } /// /// Changes the security protocol. This method can only be used to 'upgrade' a connection from no-security to either SSL or TLS. /// /// The new parameters. /// An error occurs while changing the security protocol. /// /// Programs should only call this method if there is no active or ! /// public void ChangeSecurityProtocol(SecurityOptions options) { Socket.ChangeSecurityProtocol(options); } /// /// Closes the stream and optionally closes the underlying . /// /// /// The Close method frees resources used by the SecureNetworkStream instance and, if the SecureNetworkStream owns the underlying socket, closes the underlying socket. /// public override void Close() { if (m_OwnsSocket) { try { Socket.Shutdown(SocketShutdown.Both); } catch { } finally { Socket.Close(); } } } /// /// Begins an asynchronous read from a stream. /// /// The location in memory that stores the data from the stream. /// The location in buffer to begin storing the data to. /// The maximum number of bytes to read. /// The delegate to call when the asynchronous call is complete. /// An object containing additional information supplied by the client. /// An representing the asynchronous call. /// is a null reference (Nothing in Visual Basic). /// The specified or exceeds the size of . /// There is a failure while reading from the network. public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || offset > buffer.Length || size < 0 || size > buffer.Length - offset) throw new ArgumentOutOfRangeException(); if (Socket == null) throw new IOException(); try { return Socket.BeginReceive(buffer, offset, size, SocketFlags.None, callback, state); } catch (Exception e) { throw new IOException("An I/O exception occurred.", e); } } /// /// Handles the end of an asynchronous read. /// /// An representing an asynchronous call. /// The number of bytes read from the stream. /// is a null reference (Nothing in Visual Basic). /// There is a failure while reading from the network. public override int EndRead(IAsyncResult asyncResult) { if (asyncResult == null) throw new ArgumentNullException(); if (Socket == null) throw new IOException(); try { return Socket.EndReceive(asyncResult); } catch (Exception e) { throw new IOException("An I/O exception occurred.", e); } } /// /// Begins an asynchronous write to a stream. /// /// The location in memory that holds the data to send. /// The location in buffer to begin sending the data. /// The size of buffer. /// The delegate to call when the asynchronous call is complete. /// An object containing additional information supplied by the client. /// An representing the asynchronous call. /// is a null reference (Nothing in Visual Basic). /// The specified or exceeds the size of . /// There is a failure while writing to the network. // Thanks go out to Martin Plante for notifying us about a bug in this method. public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { if (buffer == null) throw new ArgumentNullException(); if (offset < 0 || offset > buffer.Length || size < 0 || size > buffer.Length - offset) throw new ArgumentOutOfRangeException(); if (Socket == null) throw new IOException(); if (WriteResult != null) throw new IOException(); TransferItem localResult = new TransferItem(new byte[size], 0, size, new AsyncResult(callback, state, null), DataType.ApplicationData); WriteResult = localResult; Array.Copy(buffer, offset, localResult.Buffer, 0, size); try { Socket.BeginSend(localResult.Buffer, 0, size, SocketFlags.None, new AsyncCallback(OnBytesSent), (int)0); return localResult.AsyncResult; } catch { throw new IOException(); } } /// /// Called when the bytes have been sent to the remote server /// /// The representing the asynchronous call. private void OnBytesSent(IAsyncResult asyncResult) { try { int sent = Socket.EndSend(asyncResult); sent += (int)asyncResult.AsyncState; if (sent == WriteResult.Buffer.Length) { OnWriteComplete(null); } else { Socket.BeginSend(WriteResult.Buffer, sent, WriteResult.Buffer.Length - sent, SocketFlags.None, new AsyncCallback(OnBytesSent), sent); } } catch (Exception e) { OnWriteComplete(e); } } /// /// Called when all bytes have been sent to the remote host, or when a network error occurred. /// /// The error that occurred. private void OnWriteComplete(Exception e) { if (WriteResult.AsyncResult != null) { WriteResult.AsyncResult.AsyncException = e; WriteResult.AsyncResult.Notify(); } } /// /// Handles the end of an asynchronous write. /// /// The representing the asynchronous call. /// is a null reference (Nothing in Visual Basic). /// The parameter was not returned by a call to the BeginWrite method. /// An error occurs while writing to the network. public override void EndWrite(IAsyncResult asyncResult) { if (asyncResult == null) throw new ArgumentNullException(); if (asyncResult != WriteResult.AsyncResult) throw new ArgumentException(); if (Socket == null) throw new IOException(); WriteResult = null; if (((AsyncResult)asyncResult).AsyncException != null) throw new IOException(); } /// /// Holds the object returned by BeginWrite. /// /// A object. internal TransferItem WriteResult { get { return m_WriteResult; } set { m_WriteResult = value; } } /// /// Gets a value indicating whether data is available on the stream to be read. /// /// true if data is available on the stream to be read; otherwise, false. public virtual bool DataAvailable { get { if (Socket == null) return false; try { return Socket.Available > 0; } catch { return false; } } } /// Holds the value of the property private TransferItem m_WriteResult = null; /// true if the SecureNetworkStream owns the SecureSocket, false otherwise. private bool m_OwnsSocket; /// Holds the value of the property private bool m_CanRead; /// Holds the value of the property private bool m_CanWrite; /// Holds the value of the property private SecureSocket m_Socket; } }