protected IEnumerable<IServerResponseObject> ProcessBackendResponses_Ver_3(NpgsqlConnector context)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessBackendResponses");
using (new ContextResetter(context))
{
Stream stream = context.Stream;
NpgsqlMediator mediator = context.Mediator;
NpgsqlRowDescription lastRowDescription = null;
List<NpgsqlError> errors = new List<NpgsqlError>();
for (;;)
{
// Check the first Byte of response.
BackEndMessageCode message = (BackEndMessageCode) stream.ReadByte();
switch (message)
{
case BackEndMessageCode.ErrorResponse:
NpgsqlError error = new NpgsqlError(context.BackendProtocolVersion, stream);
error.ErrorSql = mediator.SqlSent;
errors.Add(error);
NpgsqlEventLog.LogMsg(resman, "Log_ErrorResponse", LogLevel.Debug, error.Message);
// Return imediately if it is in the startup state or connected state as
// there is no more messages to consume.
// Possible error in the NpgsqlStartupState:
// Invalid password.
// Possible error in the NpgsqlConnectedState:
// No pg_hba.conf configured.
if (!context.RequireReadyForQuery)
{
throw new NpgsqlException(errors);
}
break;
case BackEndMessageCode.AuthenticationRequest:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "AuthenticationRequest");
// Get the length in case we're getting AuthenticationGSSContinue
int authDataLength = PGUtil.ReadInt32(stream) - 8;
AuthenticationRequestType authType = (AuthenticationRequestType) PGUtil.ReadInt32(stream);
switch (authType)
{
case AuthenticationRequestType.AuthenticationOk:
NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationOK", LogLevel.Debug);
break;
case AuthenticationRequestType.AuthenticationClearTextPassword:
NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationClearTextRequest", LogLevel.Debug);
// Send the PasswordPacket.
ChangeState(context, NpgsqlStartupState.Instance);
context.Authenticate(context.Password);
break;
case AuthenticationRequestType.AuthenticationMD5Password:
NpgsqlEventLog.LogMsg(resman, "Log_AuthenticationMD5Request", LogLevel.Debug);
// Now do the "MD5-Thing"
// for this the Password has to be:
// 1. md5-hashed with the username as salt
// 2. md5-hashed again with the salt we get from the backend
MD5 md5 = MD5.Create();
// 1.
byte[] passwd = context.Password;
byte[] saltUserName = ENCODING_UTF8.GetBytes(context.UserName);
byte[] crypt_buf = new byte[passwd.Length + saltUserName.Length];
passwd.CopyTo(crypt_buf, 0);
saltUserName.CopyTo(crypt_buf, passwd.Length);
StringBuilder sb = new StringBuilder();
byte[] hashResult = md5.ComputeHash(crypt_buf);
foreach (byte b in hashResult)
{
sb.Append(b.ToString("x2"));
}
String prehash = sb.ToString();
byte[] prehashbytes = ENCODING_UTF8.GetBytes(prehash);
crypt_buf = new byte[prehashbytes.Length + 4];
stream.Read(crypt_buf, prehashbytes.Length, 4);
// Send the PasswordPacket.
ChangeState(context, NpgsqlStartupState.Instance);
// 2.
prehashbytes.CopyTo(crypt_buf, 0);
sb = new StringBuilder("md5"); // This is needed as the backend expects md5 result starts with "md5"
hashResult = md5.ComputeHash(crypt_buf);
foreach (byte b in hashResult)
{
sb.Append(b.ToString("x2"));
}
context.Authenticate(ENCODING_UTF8.GetBytes(sb.ToString()));
break;
#if WINDOWS && UNMANAGED
case AuthenticationRequestType.AuthenticationSSPI:
{
if (context.IntegratedSecurity)
{
// For SSPI we have to get the IP-Address (hostname doesn't work)
string ipAddressString = ((IPEndPoint)context.Socket.RemoteEndPoint).Address.ToString();
context.SSPI = new SSPIHandler(ipAddressString, "POSTGRES");
ChangeState(context, NpgsqlStartupState.Instance);
context.Authenticate(context.SSPI.Continue(null));
break;
}
else
{
// TODO: correct exception
throw new Exception();
}
}
case AuthenticationRequestType.AuthenticationGSSContinue:
{
byte[] authData = new byte[authDataLength];
PGUtil.CheckedStreamRead(stream, authData, 0, authDataLength);
byte[] passwd_read = context.SSPI.Continue(authData);
if (passwd_read.Length != 0)
{
context.Authenticate(passwd_read);
}
break;
}
#endif
default:
// Only AuthenticationClearTextPassword and AuthenticationMD5Password supported for now.
errors.Add(
new NpgsqlError(context.BackendProtocolVersion,
String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType)));
throw new NpgsqlException(errors);
}
break;
case BackEndMessageCode.RowDescription:
yield return lastRowDescription = new NpgsqlRowDescriptionV3(stream, context.OidToNameMapping, context.CompatVersion);
break;
case BackEndMessageCode.ParameterDescription:
// Do nothing,for instance, just read...
int lenght = PGUtil.ReadInt32(stream);
int nb_param = PGUtil.ReadInt16(stream);
for (int i = 0; i < nb_param; i++)
{
int typeoid = PGUtil.ReadInt32(stream);
}
break;
case BackEndMessageCode.DataRow:
yield return new ForwardsOnlyRow(new StringRowReaderV3(lastRowDescription, stream));
break;
case BackEndMessageCode.ReadyForQuery:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ReadyForQuery");
// Possible status bytes returned:
// I = Idle (no transaction active).
// T = In transaction, ready for more.
// E = Error in transaction, queries will fail until transaction aborted.
// Just eat the status byte, we have no use for it at this time.
PGUtil.ReadInt32(stream);
stream.ReadByte();
ChangeState(context, NpgsqlReadyState.Instance);
if (errors.Count != 0)
{
throw new NpgsqlException(errors);
}
yield break;
case BackEndMessageCode.BackendKeyData:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BackendKeyData");
// BackendKeyData message.
NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(context.BackendProtocolVersion, stream);
context.BackEndKeyData = backend_keydata;
// Wait for ReadForQuery message
break;
case BackEndMessageCode.NoticeResponse:
// Notices and errors are identical except that we
// just throw notices away completely ignored.
context.FireNotice(new NpgsqlError(context.BackendProtocolVersion, stream));
break;
case BackEndMessageCode.CompletedResponse:
PGUtil.ReadInt32(stream);
yield return new CompletedResponse(stream);
break;
case BackEndMessageCode.ParseComplete:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParseComplete");
// Just read up the message length.
PGUtil.ReadInt32(stream);
yield break;
case BackEndMessageCode.BindComplete:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "BindComplete");
// Just read up the message length.
PGUtil.ReadInt32(stream);
yield break;
case BackEndMessageCode.EmptyQueryResponse:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "EmptyQueryResponse");
PGUtil.ReadInt32(stream);
break;
case BackEndMessageCode.NotificationResponse:
// Eat the length
PGUtil.ReadInt32(stream);
context.FireNotification(new NpgsqlNotificationEventArgs(stream, true));
if (context.IsNotificationThreadRunning)
{
yield break;
}
break;
case BackEndMessageCode.ParameterStatus:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus(stream);
NpgsqlEventLog.LogMsg(resman, "Log_ParameterStatus", LogLevel.Debug, parameterStatus.Parameter,
parameterStatus.ParameterValue);
context.AddParameterStatus(parameterStatus);
if (parameterStatus.Parameter == "server_version")
{
// Deal with this here so that if there are
// changes in a future backend version, we can handle it here in the
// protocol handler and leave everybody else put of it.
string versionString = parameterStatus.ParameterValue.Trim();
for (int idx = 0; idx != versionString.Length; ++idx)
{
char c = parameterStatus.ParameterValue[idx];
if (!char.IsDigit(c) && c != '.')
{
versionString = versionString.Substring(0, idx);
break;
}
}
context.ServerVersion = new Version(versionString);
}
break;
case BackEndMessageCode.NoData:
// This nodata message may be generated by prepare commands issued with queries which doesn't return rows
// for example insert, update or delete.
// Just eat the message.
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "ParameterStatus");
PGUtil.ReadInt32(stream);
break;
case BackEndMessageCode.CopyInResponse:
// Enter COPY sub protocol and start pushing data to server
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CopyInResponse");
ChangeState(context, NpgsqlCopyInState.Instance);
PGUtil.ReadInt32(stream); // length redundant
context.CurrentState.StartCopy(context, ReadCopyHeader(stream));
yield break;
// Either StartCopy called us again to finish the operation or control should be passed for user to feed copy data
case BackEndMessageCode.CopyOutResponse:
// Enter COPY sub protocol and start pulling data from server
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CopyOutResponse");
ChangeState(context, NpgsqlCopyOutState.Instance);
PGUtil.ReadInt32(stream); // length redundant
context.CurrentState.StartCopy(context, ReadCopyHeader(stream));
yield break;
// Either StartCopy called us again to finish the operation or control should be passed for user to feed copy data
case BackEndMessageCode.CopyData:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CopyData");
Int32 len = PGUtil.ReadInt32(stream) - 4;
byte[] buf = new byte[len];
PGUtil.ReadBytes(stream, buf, 0, len);
context.Mediator.ReceivedCopyData = buf;
yield break; // read data from server one chunk at a time while staying in copy operation mode
case BackEndMessageCode.CopyDone:
NpgsqlEventLog.LogMsg(resman, "Log_ProtocolMessage", LogLevel.Debug, "CopyDone");
PGUtil.ReadInt32(stream); // CopyDone can not have content so this is always 4
// This will be followed by normal CommandComplete + ReadyForQuery so no op needed
break;
case BackEndMessageCode.IO_ERROR:
// Connection broken. Mono returns -1 instead of throwing an exception as ms.net does.
throw new IOException();
default:
// This could mean a number of things
// We've gotten out of sync with the backend?
// We need to implement this type?
// Backend has gone insane?
// FIXME
// what exception should we really throw here?
throw new NotSupportedException(String.Format("Backend sent unrecognized response type: {0}", (Char) message));
}
}
}
}