protected IEnumerable<IServerResponseObject> ProcessBackendResponses_Ver_3(NpgsqlConnector context) { using (new ContextResetter(context)) { Stream stream = context.Stream; NpgsqlMediator mediator = context.Mediator; NpgsqlRowDescription lastRowDescription = null; var buffer = context.TmpBuffer; var queue = context.ArrayBuffer; List<NpgsqlError> errors = null; for (; ; ) { // Check the first Byte of response. BackEndMessageCode message = (BackEndMessageCode)stream.ReadByte(); switch (message) { case BackEndMessageCode.ErrorResponse: NpgsqlError error = new NpgsqlError(stream, buffer, queue); error.ErrorSql = mediator.SqlSent; if (errors == null) errors = new List<NpgsqlError>(); errors.Add(error); // 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: // Get the length in case we're getting AuthenticationGSSContinue int authDataLength = PGUtil.ReadInt32(stream, buffer) - 8; AuthenticationRequestType authType = (AuthenticationRequestType)PGUtil.ReadInt32(stream, buffer); switch (authType) { case AuthenticationRequestType.AuthenticationOk: break; case AuthenticationRequestType.AuthenticationClearTextPassword: // Send the PasswordPacket. ChangeState(context, NpgsqlStartupState.Instance); context.Authenticate(context.Password); break; case AuthenticationRequestType.AuthenticationMD5Password: // 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. if (errors == null) errors = new List<NpgsqlError>(); errors.Add( new NpgsqlError(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType))); throw new NpgsqlException(errors); } break; case BackEndMessageCode.RowDescription: yield return lastRowDescription = new NpgsqlRowDescription(stream, context.OidToNameMapping, context.CompatVersion, buffer, queue); break; case BackEndMessageCode.ParameterDescription: // Do nothing,for instance, just read... int length = PGUtil.ReadInt32(stream, buffer); int nb_param = PGUtil.ReadInt16(stream, buffer); //WTF for (int i = 0; i < nb_param; i++) { int typeoid = PGUtil.ReadInt32(stream, buffer); } break; case BackEndMessageCode.DataRow: yield return new ForwardsOnlyRow(new StringRowReader(lastRowDescription, stream, buffer, queue)); break; case BackEndMessageCode.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, buffer); stream.ReadByte(); ChangeState(context, NpgsqlReadyState.Instance); if (errors != null) { throw new NpgsqlException(errors); } yield break; case BackEndMessageCode.BackendKeyData: // BackendKeyData message. NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(stream, buffer); 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(stream, buffer, queue)); break; case BackEndMessageCode.CompletedResponse: PGUtil.ReadInt32(stream, buffer); yield return new CompletedResponse(stream, queue); break; case BackEndMessageCode.ParseComplete: // Just read up the message length. PGUtil.ReadInt32(stream, buffer); yield break; case BackEndMessageCode.BindComplete: // Just read up the message length. PGUtil.ReadInt32(stream, buffer); yield break; case BackEndMessageCode.EmptyQueryResponse: PGUtil.ReadInt32(stream, buffer); break; case BackEndMessageCode.NotificationResponse: // Eat the length PGUtil.ReadInt32(stream, buffer); context.FireNotification(new NpgsqlNotificationEventArgs(stream, true, buffer, queue)); if (context.IsNotificationThreadRunning) { yield break; } break; case BackEndMessageCode.ParameterStatus: NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus(stream, queue); 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. PGUtil.ReadInt32(stream, buffer); break; case BackEndMessageCode.CopyInResponse: // Enter COPY sub protocol and start pushing data to server ChangeState(context, NpgsqlCopyInState.Instance); PGUtil.ReadInt32(stream, buffer); // length redundant context.CurrentState.StartCopy(context, ReadCopyHeader(stream, buffer)); 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 ChangeState(context, NpgsqlCopyOutState.Instance); PGUtil.ReadInt32(stream, buffer); // length redundant context.CurrentState.StartCopy(context, ReadCopyHeader(stream, buffer)); 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: Int32 len = PGUtil.ReadInt32(stream, buffer) - 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: PGUtil.ReadInt32(stream, buffer); // 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)); } } } }
protected IEnumerable <IServerResponseObject> ProcessBackendResponses_Ver_3(NpgsqlConnector context) { using (new ContextResetter(context)) { Stream stream = context.Stream; NpgsqlMediator mediator = context.Mediator; NpgsqlRowDescription lastRowDescription = null; var buffer = context.TmpBuffer; var queue = context.ArrayBuffer; List <NpgsqlError> errors = null; for (; ;) { // Check the first Byte of response. BackEndMessageCode message = (BackEndMessageCode)stream.ReadByte(); switch (message) { case BackEndMessageCode.ErrorResponse: NpgsqlError error = new NpgsqlError(stream, buffer, queue); error.ErrorSql = mediator.SqlSent; if (errors == null) { errors = new List <NpgsqlError>(); } errors.Add(error); // 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: // Get the length in case we're getting AuthenticationGSSContinue int authDataLength = PGUtil.ReadInt32(stream, buffer) - 8; AuthenticationRequestType authType = (AuthenticationRequestType)PGUtil.ReadInt32(stream, buffer); switch (authType) { case AuthenticationRequestType.AuthenticationOk: break; case AuthenticationRequestType.AuthenticationClearTextPassword: // Send the PasswordPacket. ChangeState(context, NpgsqlStartupState.Instance); context.Authenticate(context.Password); break; case AuthenticationRequestType.AuthenticationMD5Password: // 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. if (errors == null) { errors = new List <NpgsqlError>(); } errors.Add( new NpgsqlError(String.Format(resman.GetString("Exception_AuthenticationMethodNotSupported"), authType))); throw new NpgsqlException(errors); } break; case BackEndMessageCode.RowDescription: yield return(lastRowDescription = new NpgsqlRowDescription(stream, context.OidToNameMapping, context.CompatVersion, buffer, queue)); break; case BackEndMessageCode.ParameterDescription: // Do nothing,for instance, just read... int length = PGUtil.ReadInt32(stream, buffer); int nb_param = PGUtil.ReadInt16(stream, buffer); //WTF for (int i = 0; i < nb_param; i++) { int typeoid = PGUtil.ReadInt32(stream, buffer); } break; case BackEndMessageCode.DataRow: yield return(new ForwardsOnlyRow(new StringRowReader(lastRowDescription, stream, buffer, queue))); break; case BackEndMessageCode.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, buffer); stream.ReadByte(); ChangeState(context, NpgsqlReadyState.Instance); if (errors != null) { throw new NpgsqlException(errors); } yield break; case BackEndMessageCode.BackendKeyData: // BackendKeyData message. NpgsqlBackEndKeyData backend_keydata = new NpgsqlBackEndKeyData(stream, buffer); 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(stream, buffer, queue)); break; case BackEndMessageCode.CompletedResponse: PGUtil.ReadInt32(stream, buffer); yield return(new CompletedResponse(stream, queue)); break; case BackEndMessageCode.ParseComplete: // Just read up the message length. PGUtil.ReadInt32(stream, buffer); yield break; case BackEndMessageCode.BindComplete: // Just read up the message length. PGUtil.ReadInt32(stream, buffer); yield break; case BackEndMessageCode.EmptyQueryResponse: PGUtil.ReadInt32(stream, buffer); break; case BackEndMessageCode.NotificationResponse: // Eat the length PGUtil.ReadInt32(stream, buffer); context.FireNotification(new NpgsqlNotificationEventArgs(stream, true, buffer, queue)); if (context.IsNotificationThreadRunning) { yield break; } break; case BackEndMessageCode.ParameterStatus: NpgsqlParameterStatus parameterStatus = new NpgsqlParameterStatus(stream, queue); 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. PGUtil.ReadInt32(stream, buffer); break; case BackEndMessageCode.CopyInResponse: // Enter COPY sub protocol and start pushing data to server ChangeState(context, NpgsqlCopyInState.Instance); PGUtil.ReadInt32(stream, buffer); // length redundant context.CurrentState.StartCopy(context, ReadCopyHeader(stream, buffer)); 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 ChangeState(context, NpgsqlCopyOutState.Instance); PGUtil.ReadInt32(stream, buffer); // length redundant context.CurrentState.StartCopy(context, ReadCopyHeader(stream, buffer)); 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: Int32 len = PGUtil.ReadInt32(stream, buffer) - 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: PGUtil.ReadInt32(stream, buffer); // 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)); } } } }
public void AddParameterStatus(NpgsqlParameterStatus ps) { if (_serverParameters.ContainsKey(ps.Parameter)) { _serverParameters[ps.Parameter] = ps; } else { _serverParameters.Add(ps.Parameter, ps); } _useConformantStrings = null; }
/// <summary> /// Opens the physical connection to the server. /// </summary> /// <remarks>Usually called by the RequestConnector /// Method of the connection pool manager.</remarks> internal void Open() { ServerVersion = null; // If Connection.ConnectionString specifies a protocol version, we will // not try to fall back to version 2 on failure. // Reset state to initialize new connector in pool. CurrentState = NpgsqlClosedState.Instance; // Get a raw connection, possibly SSL... CurrentState.Open(this); try { // Establish protocol communication and handle authentication... CurrentState.Startup(this); } catch (NpgsqlException) { throw; } // Change the state of connection to open and ready. State = ConnectionState.Open; CurrentState = NpgsqlReadyState.Instance; // Fall back to the old way, SELECT VERSION(). // This should not happen for protocol version 3+. if (ServerVersion == null) { using (NpgsqlCommand command = new NpgsqlCommand("set DATESTYLE TO ISO;select version();", this)) { ServerVersion = new Version(PGUtil.ExtractServerVersion((string)command.ExecuteScalar())); } } StringBuilder sbInit = new StringBuilder(); // Adjust client encoding. NpgsqlParameterStatus clientEncodingParam = null; if (!ServerParameters.TryGetValue("client_encoding", out clientEncodingParam) || !string.Equals(clientEncodingParam.ParameterValue, "UTF8", StringComparison.OrdinalIgnoreCase) && !string.Equals(clientEncodingParam.ParameterValue, "UNICODE", StringComparison.OrdinalIgnoreCase)) { sbInit.AppendLine("SET CLIENT_ENCODING TO UTF8;"); } if (!string.IsNullOrEmpty(settings.SearchPath)) { // TODO: Add proper message when finding a semicolon in search_path. // This semicolon could lead to a sql injection security hole as someone could write in connection string: // searchpath=public;delete from table; and it would be executed. if (settings.SearchPath.Contains(";")) { throw new InvalidOperationException(); } sbInit.AppendLine("SET SEARCH_PATH=" + settings.SearchPath + ";"); } if (!string.IsNullOrEmpty(settings.ApplicationName)) { if (!SupportsApplicationName) { throw new InvalidOperationException("ApplicationName not supported."); } if (settings.ApplicationName.Contains(";")) { throw new InvalidOperationException(); } sbInit.AppendLine("SET APPLICATION_NAME='" + settings.ApplicationName.Replace('\'', '-') + "';"); } /* * Try to set SSL negotiation to 0. As of 2010-03-29, recent problems in SSL library implementations made * postgresql to add a parameter to set a value when to do this renegotiation or 0 to disable it. * Currently, Npgsql has a problem with renegotiation so, we are trying to disable it here. * This only works on postgresql servers where the ssl renegotiation settings is supported of course. * See http://lists.pgfoundry.org/pipermail/npgsql-devel/2010-February/001065.html for more information. */ sbInit.AppendLine("SET ssl_renegotiation_limit=0;"); /* * Set precision digits to maximum value possible. For postgresql before 9 it was 2, after that, it is 3. * This way, we set first to 2 and then to 3. If there is an error because of 3, it will have been set to 2 at least. * Check bug report #1010992 for more information. */ sbInit.AppendLine("SET extra_float_digits=3;"); try { new NpgsqlCommand(sbInit.ToString(), this).ExecuteBlind(); } catch { foreach (var line in sbInit.ToString().Split(Environment.NewLine.ToCharArray())) { try { if (line.Length > 0) { new NpgsqlCommand(line, this).ExecuteBlind(); } } catch { } } } // Make a shallow copy of the type mapping that the connector will own. // It is possible that the connector may add types to its private // mapping that will not be valid to another connector, even // if connected to the same backend version. _oidToNameMapping = NpgsqlTypesHelper.CreateAndLoadInitialTypesMapping(this).Clone(); ProcessServerVersion(); // The connector is now fully initialized. Beyond this point, it is // safe to release it back to the pool rather than closing it. IsInitialized = true; }