1use crate::protocol::Message;
2use powdb_query::executor::{is_read_only_statement, Engine};
3use powdb_query::parser;
4use powdb_query::result::{QueryError, QueryResult};
5use powdb_storage::types::Value;
6use std::collections::HashMap;
7use std::net::IpAddr;
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant};
10use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
11use tokio::sync::watch;
12use tracing::{debug, error, info, warn};
13use zeroize::Zeroizing;
14
15pub type AuthRateLimiter = Arc<Mutex<HashMap<IpAddr, (u32, Instant)>>>;
17
18const MAX_QUERY_LENGTH: usize = 1024 * 1024;
20
21const WRITE_TIMEOUT: Duration = Duration::from_secs(30);
24
25const MAX_AUTH_FAILURES: u32 = 5;
27
28const AUTH_FAILURE_WINDOW: Duration = Duration::from_secs(60);
30
31pub fn new_rate_limiter() -> AuthRateLimiter {
33 Arc::new(Mutex::new(HashMap::new()))
34}
35
36fn is_rate_limited(limiter: &AuthRateLimiter, ip: IpAddr) -> bool {
39 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
40 let now = Instant::now();
42 map.retain(|_, (_, ts)| now.duration_since(*ts) < AUTH_FAILURE_WINDOW);
43
44 if let Some((count, _)) = map.get(&ip) {
45 *count >= MAX_AUTH_FAILURES
46 } else {
47 false
48 }
49}
50
51fn record_auth_failure(limiter: &AuthRateLimiter, ip: IpAddr) {
53 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
54 let now = Instant::now();
55 let entry = map.entry(ip).or_insert((0, now));
56 if now.duration_since(entry.1) >= AUTH_FAILURE_WINDOW {
58 *entry = (1, now);
59 } else {
60 entry.0 += 1;
61 }
62}
63
64fn clear_auth_failures(limiter: &AuthRateLimiter, ip: IpAddr) {
66 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
67 map.remove(&ip);
68}
69
70fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
73 use sha2::{Digest, Sha256};
74 let ha = Sha256::digest(a);
75 let hb = Sha256::digest(b);
76 let mut diff = 0u8;
77 for (x, y) in ha.iter().zip(hb.iter()) {
78 diff |= x ^ y;
79 }
80 diff == 0
81}
82
83const SAFE_ERROR_PREFIXES: &[&str] = &[
85 "table not found",
86 "column not found",
87 "parse error",
88 "type mismatch",
89 "unknown table",
90 "unknown column",
91 "unknown function",
92 "syntax error",
93 "expected",
94 "unexpected",
95 "missing",
96 "duplicate",
97 "invalid",
98 "cannot",
99 "no such",
100 "already exists",
101];
102
103fn sanitize_error(e: &str) -> String {
107 let lower = e.to_lowercase();
108 for prefix in SAFE_ERROR_PREFIXES {
109 if lower.starts_with(prefix) {
110 return e.to_string();
111 }
112 }
113 "query execution error".into()
114}
115
116async fn write_msg<W: AsyncWrite + Unpin>(writer: &mut BufWriter<W>, msg: &Message) -> bool {
119 let write_fut = async {
120 if msg.write_to(writer).await.is_err() {
121 return false;
122 }
123 writer.flush().await.is_ok()
124 };
125 tokio::time::timeout(WRITE_TIMEOUT, write_fut)
126 .await
127 .unwrap_or_default()
128}
129
130pub struct ConnOpts<'a> {
133 pub engine: Arc<RwLock<Engine>>,
134 pub expected_password: Option<Zeroizing<String>>,
137 pub shutdown_rx: &'a mut watch::Receiver<bool>,
138 pub idle_timeout: Duration,
139 pub query_timeout: Duration,
140 pub rate_limiter: Option<&'a AuthRateLimiter>,
141 pub peer_addr: Option<std::net::SocketAddr>,
142}
143
144fn dispatch_query(engine: &Arc<RwLock<Engine>>, query: &str) -> Result<QueryResult, QueryError> {
148 let stmt_result = parser::parse(query).map_err(|e| e.to_string());
149
150 let can_try_read = matches!(&stmt_result, Ok(s) if is_read_only_statement(s));
151 if can_try_read {
152 let res = {
153 let eng = engine
154 .read()
155 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
156 eng.execute_powql_readonly(query)
157 };
158 match res {
159 Ok(r) => return Ok(r),
160 Err(QueryError::ReadonlyNeedsWrite) => {
161 }
163 Err(e) => return Err(e),
164 }
165 }
166
167 let mut eng = engine
168 .write()
169 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
170 eng.execute_powql(query)
171}
172
173pub async fn handle_connection<S>(stream: S, opts: ConnOpts<'_>)
174where
175 S: AsyncRead + AsyncWrite + Unpin,
176{
177 let ConnOpts {
178 engine,
179 expected_password,
180 shutdown_rx,
181 idle_timeout,
182 query_timeout,
183 rate_limiter,
184 peer_addr,
185 } = opts;
186
187 let peer = peer_addr
188 .map(|a| a.to_string())
189 .unwrap_or_else(|| "unknown".into());
190 let peer_ip = peer_addr.map(|a| a.ip());
191
192 let (reader, writer) = tokio::io::split(stream);
193 let mut reader = BufReader::new(reader);
194 let mut writer = BufWriter::new(writer);
195
196 let connect_msg = loop {
201 match tokio::time::timeout(idle_timeout, Message::read_from_preauth(&mut reader)).await {
202 Ok(Ok(Some(Message::Ping))) => {
203 debug!(peer = %peer, "pre-auth ping");
204 if !write_msg(&mut writer, &Message::Pong).await {
205 return;
206 }
207 continue;
208 }
209 Ok(Ok(Some(msg))) => break msg,
210 Ok(Ok(None)) => {
211 debug!(peer = %peer, "client closed before CONNECT");
212 return;
213 }
214 Ok(Err(e)) => {
215 error!(peer = %peer, error = %e, "error reading CONNECT");
216 return;
217 }
218 Err(_) => {
219 warn!(peer = %peer, "idle timeout waiting for CONNECT");
220 return;
221 }
222 }
223 };
224
225 match connect_msg {
226 Message::Connect { db_name, password } => {
227 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
229 if is_rate_limited(limiter, ip) {
230 warn!(peer = %peer, "rate limited: too many auth failures");
231 let err = Message::Error {
232 message: "too many auth failures, try again later".into(),
233 };
234 write_msg(&mut writer, &err).await;
235 return;
236 }
237 }
238
239 if let Some(expected) = &expected_password {
240 if !password
241 .as_deref()
242 .is_some_and(|p| constant_time_eq(p.as_bytes(), expected.as_bytes()))
243 {
244 warn!(peer = %peer, db = %db_name, "auth rejected: bad password");
245 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
247 record_auth_failure(limiter, ip);
248 }
249 let err = Message::Error {
250 message: "authentication failed".into(),
251 };
252 write_msg(&mut writer, &err).await;
253 return;
254 }
255 }
256 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
258 clear_auth_failures(limiter, ip);
259 }
260 info!(peer = %peer, db = %db_name, "client connected");
261 let ok = Message::ConnectOk {
262 version: env!("CARGO_PKG_VERSION").into(),
263 };
264 if !write_msg(&mut writer, &ok).await {
265 return;
266 }
267 }
268 _ => {
269 warn!(peer = %peer, "first message was not CONNECT");
270 let err = Message::Error {
271 message: "expected CONNECT".into(),
272 };
273 write_msg(&mut writer, &err).await;
274 return;
275 }
276 }
277
278 loop {
280 let msg = tokio::select! {
281 result = tokio::time::timeout(idle_timeout, Message::read_from(&mut reader)) => {
283 match result {
284 Ok(Ok(Some(msg))) => msg,
285 Ok(Ok(None)) => break,
286 Ok(Err(e)) => {
287 error!(peer = %peer, error = %e, "read error");
288 break;
289 }
290 Err(_) => {
291 info!(peer = %peer, "idle timeout, closing connection");
292 let err = Message::Error { message: "idle timeout".into() };
293 write_msg(&mut writer, &err).await;
294 break;
295 }
296 }
297 }
298 _ = shutdown_rx.changed() => {
300 if *shutdown_rx.borrow() {
301 info!(peer = %peer, "server shutting down, closing connection");
302 let err = Message::Error { message: "server shutting down".into() };
303 write_msg(&mut writer, &err).await;
304 break;
305 }
306 continue;
307 }
308 };
309
310 let response = match msg {
311 Message::Ping => {
312 debug!(peer = %peer, "ping");
313 Message::Pong
314 }
315 Message::Query { query } => {
316 if query.len() > MAX_QUERY_LENGTH {
317 Message::Error {
318 message: format!(
319 "query too large: {} bytes (max {})",
320 query.len(),
321 MAX_QUERY_LENGTH
322 ),
323 }
324 } else {
325 debug!(peer = %peer, query = %query, "received query");
326 let handle = tokio::task::spawn_blocking({
327 let engine = engine.clone();
328 let query = query.clone();
329 move || dispatch_query(&engine, &query)
330 });
331 let abort_handle = handle.abort_handle();
332 match tokio::time::timeout(query_timeout, handle).await {
333 Ok(Ok(Ok(result))) => query_result_to_message(result),
334 Ok(Ok(Err(e))) => Message::Error {
335 message: sanitize_error(&e.to_string()),
336 },
337 Ok(Err(e)) => Message::Error {
338 message: format!("internal error: {e}"),
339 },
340 Err(_) => {
341 abort_handle.abort();
342 warn!(peer = %peer, query = %query, "query timeout exceeded");
343 Message::Error {
344 message: "query timeout exceeded".into(),
345 }
346 }
347 }
348 }
349 }
350 Message::Disconnect => {
351 debug!(peer = %peer, "received DISCONNECT");
352 break;
353 }
354 _ => Message::Error {
355 message: "unexpected message type".into(),
356 },
357 };
358
359 if !write_msg(&mut writer, &response).await {
360 break;
361 }
362 }
363
364 info!(peer = %peer, "client disconnected");
365}
366
367fn query_result_to_message(result: QueryResult) -> Message {
368 match result {
369 QueryResult::Rows { columns, rows } => {
370 let str_rows: Vec<Vec<String>> = rows
371 .iter()
372 .map(|row| row.iter().map(value_to_display).collect())
373 .collect();
374 Message::ResultRows {
375 columns,
376 rows: str_rows,
377 }
378 }
379 QueryResult::Scalar(val) => Message::ResultScalar {
380 value: value_to_display(&val),
381 },
382 QueryResult::Modified(n) => Message::ResultOk { affected: n },
383 QueryResult::Created(name) => Message::ResultMessage {
384 message: format!("type {name} created"),
385 },
386 QueryResult::Executed { message } => Message::ResultMessage { message },
387 }
388}
389
390fn value_to_display(v: &Value) -> String {
391 match v {
392 Value::Int(n) => n.to_string(),
393 Value::Float(n) => format!("{n}"),
394 Value::Bool(b) => b.to_string(),
395 Value::Str(s) => s.clone(),
396 Value::DateTime(t) => format!("{t}"),
397 Value::Uuid(u) => format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
398 u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7],
399 u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]),
400 Value::Bytes(b) => format!("<{} bytes>", b.len()),
401 Value::Empty => "{}".into(),
402 }
403}