func (s *sniffingRouter) RouteBackend() error { // route the next message from backend to frotnend, // blocking and flushing if necessary err := s.be.Next(&s.beBuf) if err != nil { return err } if proto.IsBackendKeyData(&s.beBuf) { beInfo, err := proto.ReadBackendKeyData(&s.beBuf) if err != nil { return err } s.backendPid = beInfo.BackendPid s.secretKey = beInfo.SecretKey } var clone core.Message clone.InitFromMessage(&s.beBuf) s.beCh <- &clone err = s.fe.Send(&s.beBuf) if !s.be.HasNext() { return s.fe.Flush() } return nil }
func RejectFrontendConnection(c net.Conn) { var message fbcore.Message initFatalMessage(&message, "57A01", "no server connection available") _, _ = message.WriteTo(c) _ = c.Close() }
func (c *FrontendConnection) sendNotification(n *pq.Notification) error { var message fbcore.Message buf := &bytes.Buffer{} fbbuf.WriteInt32(buf, int32(n.BePid)) fbbuf.WriteCString(buf, n.Channel) fbbuf.WriteCString(buf, n.Extra) message.InitFromBytes(fbproto.MsgNotificationResponseA, buf.Bytes()) return c.WriteAndFlush(&message) }
func initFatalMessage(message *fbcore.Message, sqlstate, errorMessage string) { buf := &bytes.Buffer{} buf.WriteByte('S') fbbuf.WriteCString(buf, "FATAL") buf.WriteByte('C') fbbuf.WriteCString(buf, sqlstate) buf.WriteByte('M') fbbuf.WriteCString(buf, errorMessage) buf.WriteByte('\x00') message.InitFromBytes(fbproto.MsgErrorResponseE, buf.Bytes()) }
func (c *FrontendConnection) readExecuteMessage(msg *fbcore.Message) error { statementName, err := fbbuf.ReadCString(msg.Payload()) if err != nil { return err } if statementName != "" { return fmt.Errorf("attempted to use statement name %q; only unnamed statements are supported") } // ignore maxRowCount _, err = fbbuf.ReadInt32(msg.Payload()) // TODO: ensure we're at the end of the packet return err }
func (r trivialSelectResult) Respond(f Frontend) error { var msg fbcore.Message buf := &bytes.Buffer{} fbbuf.WriteInt16(buf, 1) fbbuf.WriteInt32(buf, 1) buf.WriteByte('1') msg.InitFromBytes(fbproto.MsgDataRowD, buf.Bytes()) err := f.WriteMessage(&msg) if err != nil { return err } return commandComplete("SELECT").Respond(f) }
func (c *FrontendConnection) readBindMessage(msg *fbcore.Message) error { portalName, err := fbbuf.ReadCString(msg.Payload()) if err != nil { return err } if portalName != "" { return fmt.Errorf("attempted to bind to a named portal %q; only the unnamed portal is supported") } statementName, err := fbbuf.ReadCString(msg.Payload()) if err != nil { return err } if statementName != "" { return fmt.Errorf("attempted to bind statement %q, even though it has not been parsed yet", statementName) } numParamFormats, err := fbbuf.ReadInt16(msg.Payload()) if err != nil { return err } if numParamFormats != 0 { return fmt.Errorf("the number of parameter formats (%d) does not match the number of parameters in the query (0)", numParamFormats) } numParameters, err := fbbuf.ReadInt16(msg.Payload()) if err != nil { return err } if numParameters != 0 { return fmt.Errorf("the number of parameters provided by the client (%d) does not match the number of parameters in the query (0)", numParameters) } // TODO: ensure we're at the end of the packet return nil }
func (qr errorResponse) Respond(f Frontend) error { var message fbcore.Message buf := &bytes.Buffer{} buf.WriteByte('S') fbbuf.WriteCString(buf, "ERROR") buf.WriteByte('C') fbbuf.WriteCString(buf, qr.sqlstate) buf.WriteByte('M') fbbuf.WriteCString(buf, qr.errorMessage) buf.WriteByte('\x00') message.InitFromBytes(fbproto.MsgErrorResponseE, buf.Bytes()) return f.WriteMessage(&message) }
func (c *FrontendConnection) readDescribeMessage(msg *fbcore.Message) (byte, error) { typ, err := fbbuf.ReadByte(msg.Payload()) if err != nil { return 0, err } if typ != 'S' && typ != 'P' { return 0, fmt.Errorf("invalid type %q", typ) } statementName, err := fbbuf.ReadCString(msg.Payload()) if err != nil { return 0, err } if statementName != "" { return 0, fmt.Errorf("tried to use statement/portal name %q; only unnamed statements and portals are supported") } // TODO: ensure we're at the end of the packet return typ, nil }
func (s *sniffingRouter) RouteFrontend() (err error) { // route the next message from frontend to backend, // blocking and flushing if necessary err = s.fe.Next(&s.feBuf) if err != nil { return } var clone core.Message clone.InitFromMessage(&s.feBuf) s.feCh <- &clone err = s.be.Send(&s.feBuf) if err != nil { return } if !s.fe.HasNext() { return s.be.Flush() } return }
// Process a log message, sending it to the client. func processLogMsg(die dieCh, primary *logplexc.Client, audit *logplexc.Client, msgInit msgInit, sr *serveRecord, exit exitFn) { var m core.Message for { // Poll request to exit select { case <-die: return default: break } msgInit(&m, exit) // Refuse to handle any log message above an arbitrary // size. Furthermore, exit the worker, closing the0 // connection, so that the client doesn't even bother // to wait for this process to drain the oversized // item and anything following it; these will be // dropped. It's on the client to gracefully handle // the error and re-connect after this happens. if m.Size() > 1*MB { exit("client %q sent oversized log record") } payload, err := m.Force() if err != nil { exit("could not retrieve payload of message: %v", err) } var lr logRecord parseLogRecord(&lr, payload, exit) routeLogRecord(&lr, primary, audit, sr, exit) } }
func (c *FrontendConnection) discardUntilSync() error { var message fbcore.Message for { err := c.stream.Next(&message) if err != nil { return err } switch message.MsgType() { case fbproto.MsgSyncS: _, err = message.Force() return nil default: _, err = message.Force() } if err != nil { return err } } }
func (c *FrontendConnection) readParseMessage(msg *fbcore.Message) (queryString string, err error) { statementName, err := fbbuf.ReadCString(msg.Payload()) if err != nil { return "", err } if statementName != "" { return "", fmt.Errorf("attempted to use statement name %q; only unnamed statements are supported") } queryString, err = fbbuf.ReadCString(msg.Payload()) if err != nil { return "", err } numParamTypes, err := fbbuf.ReadInt16(msg.Payload()) if err != nil { return "", err } if numParamTypes != 0 { return "", fmt.Errorf("attempted to prepare a statement with %d param types", numParamTypes) } // TODO: ensure we're at the end of the packet return queryString, nil }
func (c *FrontendConnection) auth(dbcfg VirtualDatabaseConfiguration, sm *fbproto.StartupMessage) bool { authFailed := func(sqlstate, format string, v ...interface{}) bool { var msg fbcore.Message message := fmt.Sprintf(format, v...) initFatalMessage(&msg, sqlstate, message) _ = c.WriteMessage(&msg) _ = c.FlushStream() return false } username, ok := sm.Params["user"] if !ok { return authFailed("08P01", `required startup parameter "user" nor present in startup packet`) } dbname, ok := sm.Params["database"] if !ok { dbname = username } authMethod, ok := dbcfg.FindDatabase(dbname) if !ok { return authFailed("3D000", "database %q does not exist", dbname) } switch authMethod { case "trust": return true case "md5": // handled below default: elog.Errorf("unrecognized authentication method %q", authMethod) return authFailed("XX000", "internal error") } salt := make([]byte, 4) _, err := rand.Read(salt) if err != nil { elog.Errorf("could not generate random salt: %s", err) return authFailed("XX000", "internal error") } var msg fbcore.Message buf := &bytes.Buffer{} fbbuf.WriteInt32(buf, 5) buf.Write(salt) msg.InitFromBytes(fbproto.MsgAuthenticationMD5PasswordR, buf.Bytes()) err = c.WriteAndFlush(&msg) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } err = c.stream.Next(&msg) if err == io.EOF { elog.Debugf("EOF during startup sequence") return false } else if err != nil { elog.Logf("error during startup sequence: %s", err) return false } if msg.MsgType() != fbproto.MsgPasswordMessageP { return authFailed("08P01", "unexpected response %x", msg.MsgType()) } // don't bother with messages which are clearly too big if msg.Size() > 100 { return authFailed("28001", "password authentication failed for user %q", username) } password, err := msg.Force() if err != nil { elog.Logf("error during startup sequence: %s", err) return false } success, err := dbcfg.MD5Auth(dbname, username, salt, password) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } if !success { return authFailed("28001", "password authentication failed for user %q", username) } return true }
func (qr bindComplete) Respond(f Frontend) error { var message fbcore.Message message.InitFromBytes(fbproto.MsgBindComplete2, []byte{}) return f.WriteMessage(&message) }
// This is the main loop for processing messages from the frontend. Note that // we must *never* send anything directly to the connection; all communication // must go through queryResultCh. We're also not responsible for doing any // cleanup in any case; that'll all be handled by mainLoop. func (c *FrontendConnection) queryProcessingMainLoop() { var unnamedStatement FrontendQuery var queryResult QueryResult var sendReadyForQuery bool sessionLoop: for { var message fbcore.Message err := c.stream.Next(&message) if err != nil { c.setSessionError(err) break sessionLoop } queryResult = nil sendReadyForQuery = false switch message.MsgType() { case fbproto.MsgParseP: queryString, err := c.readParseMessage(&message) if err != nil { c.setSessionError(err) break sessionLoop } unnamedStatement, err = ParseQuery(queryString) if err != nil { queryResult = NewErrorResponse("42601", err.Error()) c.queryResultCh <- queryResultSync{queryResult, false} err = c.discardUntilSync() if err != nil { c.setSessionError(err) break sessionLoop } queryResult = NewNopResponder() sendReadyForQuery = true } else { queryResult = NewParseComplete() sendReadyForQuery = false } case fbproto.MsgExecuteE: err = c.readExecuteMessage(&message) if unnamedStatement == nil { err = fmt.Errorf("attempted to execute the unnamed prepared statement when one does not exist") c.setSessionError(err) break sessionLoop } queryResult, err = unnamedStatement.Process(c) if err != nil { c.setSessionError(err) break sessionLoop } sendReadyForQuery = false // Disallow reuse; not exactly following the protocol to the letter, // but apps reusing the unnamed statement should not exist, either. unnamedStatement = nil case fbproto.MsgDescribeD: _, err = c.readDescribeMessage(&message) if err != nil { c.setSessionError(err) break sessionLoop } if unnamedStatement == nil { err = fmt.Errorf("attempted to describe the unnamed prepared statement when one does not exist") c.setSessionError(err) break sessionLoop } queryResult = unnamedStatement.Describe() sendReadyForQuery = false case fbproto.MsgBindB: err = c.readBindMessage(&message) if err != nil { c.setSessionError(err) break sessionLoop } queryResult = NewBindComplete() sendReadyForQuery = false case fbproto.MsgSyncS: queryResult = NewNopResponder() sendReadyForQuery = true case fbproto.MsgQueryQ: query, err := fbproto.ReadQuery(&message) if err != nil { c.setSessionError(err) break sessionLoop } q, err := ParseQuery(query.Query) if err != nil { queryResult = NewErrorResponse("42601", err.Error()) } else { resultDescription := q.Describe() // Special case in SimpleQuery processing: we only send the // Describe() response over if it's a RowDescription. This is // somewhat magical and very weird, but that's what the upstream // server does, so we ought to do the same thing here. if DescriptionIsRowDescription(resultDescription) { c.queryResultCh <- queryResultSync{resultDescription, false} } queryResult, err = q.Process(c) if err != nil { c.setSessionError(err) break sessionLoop } } sendReadyForQuery = true // Simple Query also clears the unnamed statement; this matches // what Postgres does. unnamedStatement = nil case fbproto.MsgTerminateX: c.setSessionError(errGracefulTermination) break sessionLoop default: c.setSessionError(fmt.Errorf("unrecognized frontend message type 0x%x", message.MsgType())) break sessionLoop } if queryResult != nil { c.queryResultCh <- queryResultSync{queryResult, sendReadyForQuery} } } // wake mainLoop to clean up close(c.queryResultCh) }
func (qr noData) Respond(f Frontend) error { var message fbcore.Message message.InitFromBytes(fbproto.MsgNoDataN, []byte{}) return f.WriteMessage(&message) }
func (c *FrontendConnection) startup(startupParameters map[string]string, dbcfg VirtualDatabaseConfiguration) bool { var message fbcore.Message var err error for { err = c.stream.Next(&message) if err != nil { elog.Logf("error while reading startup packet: %s", err) return false } if fbproto.IsStartupMessage(&message) { break } else if fbproto.IsSSLRequest(&message) { _, err = message.Force() if err != nil { elog.Logf("error while reading SSLRequest: %s", err) return false } err = c.stream.SendSSLRequestResponse(fbcore.RejectSSLRequest) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } err = c.FlushStream() if err != nil { elog.Logf("error during startup sequence: %s", err) } } else if fbproto.IsCancelRequest(&message) { _ = c.stream.Close() return false } else { elog.Warningf("unrecognized frontend message type 0x%x during startup", message.MsgType()) return false } } sm, err := fbproto.ReadStartupMessage(&message) if err != nil { elog.Logf("error while reading startup packet: %s", err) return false } if !c.auth(dbcfg, sm) { // error already logged _ = c.stream.Close() return false } fbproto.InitAuthenticationOk(&message) err = c.WriteMessage(&message) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } for k, v := range startupParameters { buf := &bytes.Buffer{} fbbuf.WriteCString(buf, k) fbbuf.WriteCString(buf, v) message.InitFromBytes(fbproto.MsgParameterStatusS, buf.Bytes()) err = c.WriteMessage(&message) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } } fbproto.InitReadyForQuery(&message, fbproto.RfqIdle) err = c.WriteMessage(&message) if err != nil { elog.Logf("error during startup sequence: %s", err) return false } err = c.FlushStream() if err != nil { elog.Logf("error during startup sequence: %s", err) return false } return true }
func (eqr emptyQueryResponse) Respond(f Frontend) error { var message fbcore.Message message.InitFromBytes(fbproto.MsgEmptyQueryResponseI, nil) return f.WriteMessage(&message) }
// Process the identity ('I') message, reporting the identity therein. func processIdentMsg(msgInit msgInit, exit exitFn) string { var m core.Message msgInit(&m, exit) // Read the remote system identifier string if m.MsgType() != 'I' { exit("expected identification ('I') message, "+ "but received %c", m.MsgType()) } // hard-coded lengh limit, but it's very generous if m.Size() > 10*KB { log.Printf("oversized message string, msg size is %d", m.Size()) } s, err := buf.ReadCString(m.Payload()) if err != nil { exit("couldn't read identification string: %v", err) } return s }
// Read the version message, calling exit if this is not a supported // version. func processVerMsg(msgInit msgInit, exit exitFn) { var m core.Message msgInit(&m, exit) if m.MsgType() != 'V' { exit("expected version ('V') message, "+ "but received %c", m.MsgType()) } // hard-coded lengh limit, but it's very generous if m.Size() > 10*KB { log.Printf("oversized message string, msg size is %d", m.Size()) } s, err := buf.ReadCString(m.Payload()) if err != nil { exit("couldn't read version string: %v", err) } if !(strings.HasPrefix(s, "PG-9.0") || strings.HasPrefix(s, "PG-9.1") || strings.HasPrefix(s, "PG-9.2") || strings.HasPrefix(s, "PG-9.3") || strings.HasPrefix(s, "PG-9.4") || strings.HasPrefix(s, "PG-9.5")) || !strings.HasSuffix(s, "/logfebe-1") { exit("protocol version not supported: %s", s) } }