예제 #1
0
        void ProcessAuthenticationMessage(AuthenticationRequestMessage msg)
        {
            PasswordMessage passwordMessage;

            switch (msg.AuthRequestType)
            {
                case AuthenticationRequestType.AuthenticationOk:
                    return;

                case AuthenticationRequestType.AuthenticationCleartextPassword:
                    passwordMessage = PasswordMessage.CreateClearText(Password);
                    break;

                case AuthenticationRequestType.AuthenticationMD5Password:
                    passwordMessage = PasswordMessage.CreateMD5(Password, UserName, ((AuthenticationMD5PasswordMessage)msg).Salt);
                    break;

                case AuthenticationRequestType.AuthenticationGSS:
                    if (!IntegratedSecurity) {
                        throw new Exception("GSS authentication but IntegratedSecurity not enabled");
                    }
                    // For GSSAPI we have to use the supplied hostname
                    SSPI = new SSPIHandler(Host, "POSTGRES", true);
                    passwordMessage = new PasswordMessage(SSPI.Continue(null));
                    break;

                case AuthenticationRequestType.AuthenticationSSPI:
                    if (!IntegratedSecurity) {
                        throw new Exception("SSPI authentication but IntegratedSecurity not enabled");
                    }
                    // For SSPI we have to get the IP-Address (hostname doesn't work)
                    var ipAddressString = ((IPEndPoint)Socket.RemoteEndPoint).Address.ToString();
                    SSPI = new SSPIHandler(ipAddressString, "POSTGRES", false);
                    passwordMessage = new PasswordMessage(SSPI.Continue(null));
                    break;

                case AuthenticationRequestType.AuthenticationGSSContinue:
                    var passwdRead = SSPI.Continue(((AuthenticationGSSContinueMessage)msg).AuthenticationData);
                    if (passwdRead.Length != 0)
                    {
                        passwordMessage = new PasswordMessage(passwdRead);
                        break;
                    }
                    return;

                default:
                    throw new NotSupportedException(String.Format(L10N.AuthenticationMethodNotSupported, msg.AuthRequestType));
            }
            passwordMessage.Prepare();
            passwordMessage.Write(Buffer);
            Buffer.Flush();
        }
예제 #2
0
        IServerMessage ReadMessageInternal()
        {
            for (;;)
            {
                // Check the first Byte of response.
                Stream.Read(_buffer, 0, 1);
                var message = (BackEndMessageCode) _buffer[0];
                switch (message)
                {
                    case BackEndMessageCode.ErrorResponse:
                        var error = new NpgsqlError(Stream);
                        _log.Trace("Received backend error: " + error.Message);
                        error.ErrorSql = Mediator.GetSqlSent();

                        // We normally accumulate errors until the query ends (ReadyForQuery). But
                        // during the connection phase errors need to be thrown immediately
                        // Possible error in the NpgsqlStartupState:
                        //        Invalid password.
                        // Possible error in the NpgsqlConnectedState:
                        //        No pg_hba.conf configured.

                        if (State == ConnectorState.Connecting) {
                            throw new NpgsqlException(error);
                        }

                        _pendingErrors.Add(error);
                        continue;

                    case BackEndMessageCode.AuthenticationRequest:
                        // Get the length in case we're getting AuthenticationGSSContinue
                        var authDataLength = Stream.ReadInt32() - 8;

                        var authType = (AuthenticationRequestType)Stream.ReadInt32();
                        _log.Trace("Received AuthenticationRequest of type " + authType);
                        switch (authType)
                        {
                            case AuthenticationRequestType.AuthenticationOk:
                                continue;
                            case AuthenticationRequestType.AuthenticationClearTextPassword:
                                // Send the PasswordPacket.
                                SendPasswordMessage(PGUtil.NullTerminateArray(Password));
                                continue;
                            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

                                var md5 = MD5.Create();

                                // 1.
                                var passwd = Password;
                                var saltUserName = BackendEncoding.UTF8Encoding.GetBytes(UserName);

                                var cryptBuf = new byte[passwd.Length + saltUserName.Length];

                                passwd.CopyTo(cryptBuf, 0);
                                saltUserName.CopyTo(cryptBuf, passwd.Length);

                                var sb = new StringBuilder();
                                var hashResult = md5.ComputeHash(cryptBuf);
                                foreach (byte b in hashResult)
                                {
                                    sb.Append(b.ToString("x2"));
                                }

                                var prehash = sb.ToString();

                                var prehashbytes = BackendEncoding.UTF8Encoding.GetBytes(prehash);
                                cryptBuf = new byte[prehashbytes.Length + 4];

                                Stream.Read(cryptBuf, prehashbytes.Length, 4);
                                // Send the PasswordPacket.

                                // 2.
                                prehashbytes.CopyTo(cryptBuf, 0);

                                sb = new StringBuilder("md5");
                                // This is needed as the backend expects md5 result starts with "md5"
                                hashResult = md5.ComputeHash(cryptBuf);
                                foreach (var b in hashResult)
                                {
                                    sb.Append(b.ToString("x2"));
                                }

                                SendPasswordMessage(PGUtil.NullTerminateArray(BackendEncoding.UTF8Encoding.GetBytes(sb.ToString())));
                                continue;

                            case AuthenticationRequestType.AuthenticationGSS:
                            {
                                if (IntegratedSecurity)
                                {
                                    // For GSSAPI we have to use the supplied hostname
                                    SSPI = new SSPIHandler(Host, "POSTGRES", true);
                                    SendPasswordMessage(SSPI.Continue(null));
                                    continue;
                                }
                                else
                                {
                                    // TODO: correct exception
                                    throw new Exception();
                                }
                            }

                            case AuthenticationRequestType.AuthenticationSSPI:
                            {
                                if (IntegratedSecurity)
                                {
                                    // For SSPI we have to get the IP-Address (hostname doesn't work)
                                    var ipAddressString = ((IPEndPoint)Socket.RemoteEndPoint).Address.ToString();
                                    SSPI = new SSPIHandler(ipAddressString, "POSTGRES", false);
                                    SendPasswordMessage(SSPI.Continue(null));
                                    continue;
                                }
                                else
                                {
                                    // TODO: correct exception
                                    throw new Exception();
                                }
                            }

                            case AuthenticationRequestType.AuthenticationGSSContinue:
                            {
                                var authData = new byte[authDataLength];
                                Stream.CheckedStreamRead(authData, 0, authDataLength);
                                var passwdRead = SSPI.Continue(authData);
                                if (passwdRead.Length != 0)
                                {
                                    SendPasswordMessage(passwdRead);
                                }
                                continue;
                            }

                            default:
                                throw new NotSupportedException(String.Format(L10N.AuthenticationMethodNotSupported, authType));
                        }

                    case BackEndMessageCode.RowDescription:
                        _log.Trace("Received RowDescription");
                        return new NpgsqlRowDescription(Stream, OidToNameMapping);

                    case BackEndMessageCode.ParameterDescription:
                        _log.Trace("Received ParameterDescription");
                        // PostgreSQL has found out what parameter types we must use

                        Stream.ReadInt32();
                        var nbParam = Stream.ReadInt16();
                        var typeoids = new int[nbParam];
                        for (var i = 0; i < nbParam; i++)
                        {
                            typeoids[i] = Stream.ReadInt32();  // typeoid
                        }
                        return new ParameterDescriptionResponse(typeoids);

                    case BackEndMessageCode.DataRow:
                        _log.Trace("Received DataRow");
                        State = ConnectorState.Fetching;
                        return new StringRowReader(Stream);

                    case BackEndMessageCode.ReadyForQuery:
                        _log.Trace("Received 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.
                        Stream.ReadInt32();
                        Stream.ReadByte();

                        State = ConnectorState.Ready;

                        if (_pendingErrors.Any()) {
                            var e = new NpgsqlException(_pendingErrors);
                            _pendingErrors.Clear();
                            throw e;
                        }
                        return ReadyForQueryMsg.Instance;

                    case BackEndMessageCode.BackendKeyData:
                        _log.Trace("Received BackendKeyData");
                        BackEndKeyData = new NpgsqlBackEndKeyData(Stream);
                        // Wait for ReadForQuery message
                        continue;

                    case BackEndMessageCode.NoticeResponse:
                        _log.Trace("Received NoticeResponse");
                        // Notices and errors are identical except that we
                        // just throw notices away completely ignored.
                        FireNotice(new NpgsqlError(Stream));
                        continue;

                    case BackEndMessageCode.CompletedResponse:
                        _log.Trace("Received CompletedResponse");
                        Stream.ReadInt32();
                        return new CompletedResponse(Stream);

                    case BackEndMessageCode.ParseComplete:
                        _log.Trace("Received ParseComplete");
                        // Just read up the message length.
                        Stream.ReadInt32();
                        continue;

                    case BackEndMessageCode.BindComplete:
                        _log.Trace("Received BindComplete");
                        // Just read up the message length.
                        Stream.ReadInt32();
                        continue;

                    case BackEndMessageCode.EmptyQueryResponse:
                        _log.Trace("Received EmptyQueryResponse");
                        Stream.ReadInt32();
                        continue;

                    case BackEndMessageCode.NotificationResponse:
                        _log.Trace("Received NotificationResponse");
                        // Eat the length
                        Stream.ReadInt32();
                        FireNotification(new NpgsqlNotificationEventArgs(Stream, true));
                        if (IsNotificationThreadRunning) {
                            throw new Exception("Internal state error, notification thread is running");
                        }
                        continue;

                    case BackEndMessageCode.ParameterStatus:
                        var paramStatus = new NpgsqlParameterStatus(Stream);
                        _log.TraceFormat("Received ParameterStatus {0}={1}", paramStatus.Parameter, paramStatus.ParameterValue);
                        AddParameterStatus(paramStatus);

                        if (paramStatus.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.
                            var versionString = paramStatus.ParameterValue.Trim();
                            for (var idx = 0; idx != versionString.Length; ++idx)
                            {
                                var c = paramStatus.ParameterValue[idx];
                                if (!char.IsDigit(c) && c != '.')
                                {
                                    versionString = versionString.Substring(0, idx);
                                    break;
                                }
                            }
                            ServerVersion = new Version(versionString);
                        }
                        continue;

                    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.
                        _log.Trace("Received NoData");
                        Stream.ReadInt32();
                        continue;

                    case BackEndMessageCode.CopyInResponse:
                        _log.Trace("Received CopyInResponse");
                        // Enter COPY sub protocol and start pushing data to server
                        State = ConnectorState.CopyIn;
                        Stream.ReadInt32(); // length redundant
                        StartCopyIn(ReadCopyHeader());
                        return CopyInResponseMsg.Instance;
                        // Either StartCopy called us again to finish the operation or control should be passed for user to feed copy data

                    case BackEndMessageCode.CopyOutResponse:
                        _log.Trace("Received CopyOutResponse");
                        // Enter COPY sub protocol and start pulling data from server
                        State = ConnectorState.CopyOut;
                        Stream.ReadInt32(); // length redundant
                        StartCopyOut(ReadCopyHeader());
                        return CopyOutResponseMsg.Instance;
                        // Either StartCopy called us again to finish the operation or control should be passed for user to feed copy data

                    case BackEndMessageCode.CopyData:
                        _log.Trace("Received CopyData");
                        var len = Stream.ReadInt32() - 4;
                        var buf = new byte[len];
                        Stream.ReadBytes(buf, 0, len);
                        Mediator.ReceivedCopyData = buf;
                        return CopyDataMsg.Instance;
                        // read data from server one chunk at a time while staying in copy operation mode

                    case BackEndMessageCode.CopyDone:
                        _log.Trace("Received CopyDone");
                        Stream.ReadInt32(); // CopyDone can not have content so this is always 4
                        // This will be followed by normal CommandComplete + ReadyForQuery so no op needed
                        continue;

                    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));
                }
            }
        }