// Npgsql.NpgsqlClosedState.cs
//
// Authors:
// Dave Joyner
// Daniel Nauck
//
// Copyright (C) 2002 The Npgsql Development Team
// npgsql-general@gborg.postgresql.org
// http://gborg.postgresql.org/project/npgsql/projdisplay.php
//
// Permission to use, copy, modify, and distribute this software and its
// documentation for any purpose, without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph and the following two paragraphs appear in all copies.
//
// IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
// INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
// DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
// THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
// ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using Mono.Security.Protocol.Tls;
using SecurityProtocolType=Mono.Security.Protocol.Tls.SecurityProtocolType;
using System.Security.Cryptography.X509Certificates;
namespace Npgsql
{
internal class NpgsqlNetworkStream : NetworkStream
{
NpgsqlConnector mContext = null;
public NpgsqlNetworkStream(Socket socket, Boolean owner)
: base(socket, owner)
{
}
public void AttachConnector(NpgsqlConnector context)
{
mContext = context;
}
protected override void Dispose(bool disposing)
{
if (!disposing)
{
if (mContext != null)
{
mContext.Close();
mContext = null;
}
}
base.Dispose(disposing);
}
}
internal sealed class NpgsqlClosedState : NpgsqlState
{
private static readonly NpgsqlClosedState _instance = new NpgsqlClosedState();
private static readonly String CLASSNAME = MethodBase.GetCurrentMethod().DeclaringType.Name;
private NpgsqlClosedState()
: base()
{
}
public static NpgsqlClosedState Instance
{
get
{
NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Instance");
return _instance;
}
}
public override void Open(NpgsqlConnector context, Int32 timeout)
{
try
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
IAsyncResult result;
// Keep track of time remaining; Even though there may be multiple timeout-able calls,
// this allows us to still respect the caller's timeout expectation.
DateTime attemptStart;
attemptStart = DateTime.Now;
result = Dns.BeginGetHostAddresses(context.Host, null, null);
if (!result.AsyncWaitHandle.WaitOne(timeout, true))
{
// Timeout was used up attempting the Dns lookup
throw new TimeoutException(resman.GetString("Exception_DnsLookupTimeout"));
}
timeout -= Convert.ToInt32((DateTime.Now - attemptStart).TotalMilliseconds);
IPAddress[] ips = Dns.EndGetHostAddresses(result);
Socket socket = null;
Exception lastSocketException = null;
// try every ip address of the given hostname, use the first reachable one
// make sure not to exceed the caller's timeout expectation by splitting the
// time we have left between all the remaining ip's in the list.
for (int i = 0 ; i < ips.Length ; i++)
{
NpgsqlEventLog.LogMsg(resman, "Log_ConnectingTo", LogLevel.Debug, ips[i]);
IPEndPoint ep = new IPEndPoint(ips[i], context.Port);
socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
attemptStart = DateTime.Now;
try
{
result = socket.BeginConnect(ep, null, null);
if (!result.AsyncWaitHandle.WaitOne(timeout / (ips.Length - i), true))
{
throw new TimeoutException(resman.GetString("Exception_ConnectionTimeout"));
}
socket.EndConnect(result);
// connect was successful, leave the loop
break;
}
catch (Exception e)
{
NpgsqlEventLog.LogMsg(resman, "Log_FailedConnection", LogLevel.Normal, ips[i]);
timeout -= Convert.ToInt32((DateTime.Now - attemptStart).TotalMilliseconds);
lastSocketException = e;
socket.Close();
socket = null;
}
}
if (socket == null)
{
throw lastSocketException;
}
NpgsqlNetworkStream baseStream = new NpgsqlNetworkStream(socket, true);
Stream sslStream = null;
// If the PostgreSQL server has SSL connectors enabled Open SslClientStream if (response == 'S') {
if (context.SSL || (context.SslMode == SslMode.Require) || (context.SslMode == SslMode.Prefer))
{
baseStream
.WriteInt32(8)
.WriteInt32(80877103);
// Receive response
Char response = (Char) baseStream.ReadByte();
if (response == 'S')
{
//create empty collection
X509CertificateCollection clientCertificates = new X509CertificateCollection();
//trigger the callback to fetch some certificates
context.DefaultProvideClientCertificatesCallback(clientCertificates);
//if (context.UseMonoSsl)
if (!NpgsqlConnector.UseSslStream)
{
SslClientStream sslStreamPriv;
sslStreamPriv = new SslClientStream(
baseStream,
context.Host,
true,
SecurityProtocolType.Default,
clientCertificates);
sslStreamPriv.ClientCertSelectionDelegate =
new CertificateSelectionCallback(context.DefaultCertificateSelectionCallback);
sslStreamPriv.ServerCertValidationDelegate =
new CertificateValidationCallback(context.DefaultCertificateValidationCallback);
sslStreamPriv.PrivateKeyCertSelectionDelegate =
new PrivateKeySelectionCallback(context.DefaultPrivateKeySelectionCallback);
sslStream = sslStreamPriv;
}
else
{
SslStream sslStreamPriv;
sslStreamPriv = new SslStream(baseStream, true, context.DefaultValidateRemoteCertificateCallback);
sslStreamPriv.AuthenticateAsClient(context.Host, clientCertificates, System.Security.Authentication.SslProtocols.Default, false);
sslStream = sslStreamPriv;
}
}
else if (context.SslMode == SslMode.Require)
{
throw new InvalidOperationException(resman.GetString("Exception_Ssl_RequestError"));
}
}
context.Socket = socket;
context.BaseStream = baseStream;
context.Stream = new BufferedStream(sslStream == null ? baseStream : sslStream, 8192);
NpgsqlEventLog.LogMsg(resman, "Log_ConnectedTo", LogLevel.Normal, context.Host, context.Port);
ChangeState(context, NpgsqlConnectedState.Instance);
}
catch (Exception e)
{
throw new NpgsqlException(string.Format(resman.GetString("Exception_FailedConnection"), context.Host), e);
}
}
public override void Close(NpgsqlConnector context)
{
//DO NOTHING.
}
}
}