/*
* 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;
}
}