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