| 1192 | c.sendError("FATAL", "28000", "no user specified") |
| 1193 | return fmt.Errorf("no user specified") |
| 1194 | } |
| 1195 | |
| 1196 | break |
| 1197 | } |
| 1198 | |
| 1199 | // Request password |
| 1200 | if err := wire.WriteAuthCleartextPassword(c.writer); err != nil { |
| 1201 | return err |
| 1202 | } |
| 1203 | if err := c.flushWriter(); err != nil { |
| 1204 | return fmt.Errorf("failed to flush writer: %w", err) |
| 1205 | } |
| 1206 | |
| 1207 | // Read password response |
| 1208 | msgType, body, err := wire.ReadMessage(c.reader) |
| 1209 | if err != nil { |
| 1210 | return err |
| 1211 | } |
| 1212 | |
| 1213 | if msgType != wire.MsgPassword { |
| 1214 | c.sendError("FATAL", "28000", "expected password message") |
| 1215 | return fmt.Errorf("expected password message, got %c", msgType) |
| 1216 | } |
| 1217 | |
| 1218 | // Password is null-terminated |
| 1219 | password := string(bytes.TrimRight(body, "\x00")) |
| 1220 | |
| 1221 | // Validate password (constant-time; does not leak whether the user exists) |
| 1222 | if !auth.ValidateUserPassword(c.server.cfg.Users, c.username, password) { |
| 1223 | // Record failed authentication attempt |
| 1224 | banned := c.server.rateLimiter.RecordFailedAuth(c.conn.RemoteAddr()) |
| 1225 | if banned { |
| 1226 | c.logger().Warn("IP banned after too many failed auth attempts.", "remote_addr", c.conn.RemoteAddr()) |
| 1227 | } |
| 1228 | c.sendError("FATAL", "28P01", "password authentication failed") |
| 1229 | return fmt.Errorf("authentication failed for user %q", c.username) |
| 1230 | } |
| 1231 | |
| 1232 | // Record successful authentication (clears failed attempt counter) |
| 1233 | c.server.rateLimiter.RecordSuccessfulAuth(c.conn.RemoteAddr()) |
| 1234 | |
| 1235 | // Send auth OK |
| 1236 | if err := wire.WriteAuthOK(c.writer); err != nil { |
| 1237 | return err |
| 1238 | } |
| 1239 | |
| 1240 | c.logger().Info("User authenticated.", "remote_addr", c.conn.RemoteAddr()) |
| 1241 | return nil |
| 1242 | } |
| 1243 | |
| 1244 | func (c *clientConn) sendInitialParams() { |
| 1245 | params := map[string]string{ |
| 1246 | "server_version": "15.0 (Duckgres)", |
| 1247 | "server_encoding": "UTF8", |
| 1248 | "client_encoding": "UTF8", |
| 1249 | "DateStyle": "ISO, MDY", |
| 1250 | "TimeZone": "UTC", |
| 1251 | "integer_datetimes": "on", |