1use crate::metrics::{Metrics, QueryOutcome};
2use crate::protocol::{Message, WireParam};
3use powdb_auth::{Permission, Role, UserStore};
4use powdb_query::executor::{is_read_only_statement, Engine};
5use powdb_query::parser;
6use powdb_query::result::{QueryError, QueryResult};
7use powdb_query::sql;
8use powdb_storage::types::Value;
9use std::collections::HashMap;
10use std::net::IpAddr;
11use std::sync::{Arc, Mutex, RwLock};
12use std::time::{Duration, Instant};
13use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
14use tokio::sync::{watch, OwnedSemaphorePermit, Semaphore};
15use tracing::{debug, error, info, warn};
16use zeroize::Zeroizing;
17
18pub type AuthRateLimiter = Arc<Mutex<HashMap<IpAddr, (u32, Instant)>>>;
20
21pub type TxGate = Arc<Semaphore>;
26
27pub fn new_tx_gate() -> TxGate {
29 Arc::new(Semaphore::new(1))
30}
31
32const MAX_QUERY_LENGTH: usize = 1024 * 1024;
34
35#[cfg(not(test))]
39const MAX_RESPONSE_PAYLOAD_SIZE: usize = 64 * 1024 * 1024;
40#[cfg(test)]
41const MAX_RESPONSE_PAYLOAD_SIZE: usize = 1024;
42
43const WRITE_TIMEOUT: Duration = Duration::from_secs(30);
46
47const MAX_AUTH_FAILURES: u32 = 5;
49
50const AUTH_FAILURE_WINDOW: Duration = Duration::from_secs(60);
52
53pub fn new_rate_limiter() -> AuthRateLimiter {
55 Arc::new(Mutex::new(HashMap::new()))
56}
57
58fn is_rate_limited(limiter: &AuthRateLimiter, ip: IpAddr) -> bool {
61 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
62 let now = Instant::now();
64 map.retain(|_, (_, ts)| now.duration_since(*ts) < AUTH_FAILURE_WINDOW);
65
66 if let Some((count, _)) = map.get(&ip) {
67 *count >= MAX_AUTH_FAILURES
68 } else {
69 false
70 }
71}
72
73fn record_auth_failure(limiter: &AuthRateLimiter, ip: IpAddr) {
75 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
76 let now = Instant::now();
77 let entry = map.entry(ip).or_insert((0, now));
78 if now.duration_since(entry.1) >= AUTH_FAILURE_WINDOW {
80 *entry = (1, now);
81 } else {
82 entry.0 += 1;
83 }
84}
85
86fn clear_auth_failures(limiter: &AuthRateLimiter, ip: IpAddr) {
88 let mut map = limiter.lock().unwrap_or_else(|e| e.into_inner());
89 map.remove(&ip);
90}
91
92fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
95 use sha2::{Digest, Sha256};
96 let ha = Sha256::digest(a);
97 let hb = Sha256::digest(b);
98 let mut diff = 0u8;
99 for (x, y) in ha.iter().zip(hb.iter()) {
100 diff |= x ^ y;
101 }
102 diff == 0
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct Principal {
110 pub name: String,
111 pub role: String,
112}
113
114fn is_ddl_statement(stmt: &powdb_query::ast::Statement) -> bool {
120 use powdb_query::ast::Statement;
121 let inner = match stmt {
122 Statement::Explain(inner) => inner.as_ref(),
123 other => other,
124 };
125 matches!(
126 inner,
127 Statement::CreateType(_)
128 | Statement::AlterTable(_)
129 | Statement::DropTable(_)
130 | Statement::CreateView(_)
131 | Statement::DropView(_)
132 )
133}
134
135fn required_permission(stmt: &powdb_query::ast::Statement) -> Permission {
141 if is_read_only_statement(stmt) {
142 Permission::Read
143 } else if is_ddl_statement(stmt) {
144 Permission::Ddl
145 } else {
146 Permission::Write
147 }
148}
149
150fn check_statement_permitted(
162 principal: Option<&Principal>,
163 stmt: &powdb_query::ast::Statement,
164) -> Result<(), QueryError> {
165 let Some(p) = principal else {
166 return Ok(());
169 };
170 if is_read_only_statement(stmt) {
173 return Ok(());
174 }
175 let needed = required_permission(stmt);
176 if Role::builtin(&p.role).is_some_and(|r| r.allows(needed)) {
177 return Ok(());
178 }
179 let kind = if needed == Permission::Ddl {
180 "schema-definition"
181 } else {
182 "write"
183 };
184 Err(QueryError::Execution(format!(
185 "permission denied: role '{}' cannot execute {kind} statements",
186 p.role
187 )))
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
192pub enum AuthOutcome {
193 Authenticated { principal: Option<Principal> },
197 Rejected,
200}
201
202pub fn authenticate_connect(
214 users: &UserStore,
215 expected_password: Option<&str>,
216 username: Option<&str>,
217 password: Option<&str>,
218) -> AuthOutcome {
219 if !users.is_empty() {
220 let Some(name) = username else {
222 return AuthOutcome::Rejected;
223 };
224 let Some(candidate) = password else {
225 return AuthOutcome::Rejected;
226 };
227 match users.authenticate(name, candidate) {
228 Some(user) => AuthOutcome::Authenticated {
229 principal: Some(Principal {
230 name: user.name.clone(),
231 role: user.role.clone(),
232 }),
233 },
234 None => AuthOutcome::Rejected,
235 }
236 } else {
237 match expected_password {
239 Some(expected) => {
240 if password.is_some_and(|p| constant_time_eq(p.as_bytes(), expected.as_bytes())) {
241 AuthOutcome::Authenticated { principal: None }
242 } else {
243 AuthOutcome::Rejected
244 }
245 }
246 None => AuthOutcome::Authenticated { principal: None },
247 }
248 }
249}
250
251const SAFE_ERROR_PREFIXES: &[&str] = &[
253 "table not found",
254 "column not found",
255 "parse error",
256 "type mismatch",
257 "unknown table",
258 "unknown column",
259 "unknown function",
260 "syntax error",
261 "expected",
262 "unexpected",
263 "missing",
264 "duplicate",
265 "invalid",
266 "cannot",
267 "no such",
268 "already exists",
269 "permission denied",
270 "row too large",
271 "unique constraint violation",
272 "sort input exceeds",
277 "join result exceeds",
278 "query exceeded memory budget",
279 "result too large",
280];
281
282fn sanitize_error(e: &str) -> String {
286 let lower = e.to_lowercase();
287 for prefix in SAFE_ERROR_PREFIXES {
288 if lower.starts_with(prefix) {
289 return e.to_string();
290 }
291 }
292 "query execution error".into()
293}
294
295async fn write_msg<W: AsyncWrite + Unpin>(writer: &mut BufWriter<W>, msg: &Message) -> bool {
298 let write_fut = async {
299 if msg.write_to(writer).await.is_err() {
300 return false;
301 }
302 writer.flush().await.is_ok()
303 };
304 tokio::time::timeout(WRITE_TIMEOUT, write_fut)
305 .await
306 .unwrap_or_default()
307}
308
309pub struct ConnOpts<'a> {
312 pub engine: Arc<RwLock<Engine>>,
313 pub tx_gate: TxGate,
314 pub expected_password: Option<Zeroizing<String>>,
317 pub users: Arc<UserStore>,
321 pub shutdown_rx: &'a mut watch::Receiver<bool>,
322 pub idle_timeout: Duration,
323 pub query_timeout: Duration,
324 pub rate_limiter: Option<&'a AuthRateLimiter>,
325 pub peer_addr: Option<std::net::SocketAddr>,
326 pub metrics: Arc<Metrics>,
328}
329
330fn dispatch_query(
339 engine: &Arc<RwLock<Engine>>,
340 query: &str,
341 principal: Option<&Principal>,
342) -> Result<QueryResult, QueryError> {
343 let stmt_result = parser::parse(query).map_err(|e| e.to_string());
344
345 if let Ok(stmt) = &stmt_result {
349 check_statement_permitted(principal, stmt)?;
350 }
351
352 let can_try_read = matches!(&stmt_result, Ok(s) if is_read_only_statement(s));
353 if can_try_read {
354 let res = {
355 let eng = engine
356 .read()
357 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
358 eng.execute_powql_readonly(query)
359 };
360 match res {
361 Ok(r) => return Ok(r),
362 Err(QueryError::ReadonlyNeedsWrite) => {
363 }
365 Err(e) => return Err(e),
366 }
367 }
368
369 let mut eng = engine
370 .write()
371 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
372 eng.execute_powql(query)
373}
374
375fn dispatch_sql_query(
376 engine: &Arc<RwLock<Engine>>,
377 query: &str,
378 principal: Option<&Principal>,
379) -> Result<QueryResult, QueryError> {
380 let stmt_result = sql::parse_sql(query).map_err(|e| e.to_string());
381
382 if let Ok(stmt) = &stmt_result {
383 check_statement_permitted(principal, stmt)?;
384 }
385
386 let can_try_read = matches!(&stmt_result, Ok(s) if is_read_only_statement(s));
387 if can_try_read {
388 let res = {
389 let eng = engine
390 .read()
391 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
392 eng.execute_sql_readonly(query)
393 };
394 match res {
395 Ok(r) => return Ok(r),
396 Err(QueryError::ReadonlyNeedsWrite) => {}
397 Err(e) => return Err(e),
398 }
399 }
400
401 let mut eng = engine
402 .write()
403 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
404 eng.execute_sql(query)
405}
406
407fn wire_param_to_value(p: &WireParam) -> powdb_query::ast::ParamValue {
410 use powdb_query::ast::ParamValue;
411 match p {
412 WireParam::Null => ParamValue::Null,
413 WireParam::Int(v) => ParamValue::Int(*v),
414 WireParam::Float(v) => ParamValue::Float(*v),
415 WireParam::Bool(v) => ParamValue::Bool(*v),
416 WireParam::Str(s) => ParamValue::Str(s.clone()),
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426enum TransactionControl {
427 Begin,
428 Commit,
429 Rollback,
430}
431
432fn transaction_control(stmt: &powdb_query::ast::Statement) -> Option<TransactionControl> {
433 use powdb_query::ast::Statement;
434 match stmt {
435 Statement::Begin => Some(TransactionControl::Begin),
436 Statement::Commit => Some(TransactionControl::Commit),
437 Statement::Rollback => Some(TransactionControl::Rollback),
438 _ => None,
439 }
440}
441
442fn classify_query_transaction_control(query: &str) -> Option<TransactionControl> {
443 parser::parse(query)
444 .ok()
445 .and_then(|stmt| transaction_control(&stmt))
446}
447
448fn classify_sql_transaction_control(query: &str) -> Option<TransactionControl> {
449 sql::parse_sql(query)
450 .ok()
451 .and_then(|stmt| transaction_control(&stmt))
452}
453
454fn classify_params_transaction_control(
455 query: &str,
456 params: &[WireParam],
457) -> Option<TransactionControl> {
458 let bound: Vec<powdb_query::ast::ParamValue> = params.iter().map(wire_param_to_value).collect();
459 parser::parse_with_params(query, &bound)
460 .ok()
461 .and_then(|stmt| transaction_control(&stmt))
462}
463
464fn dispatch_query_with_params(
465 engine: &Arc<RwLock<Engine>>,
466 query: &str,
467 params: &[WireParam],
468 principal: Option<&Principal>,
469) -> Result<QueryResult, QueryError> {
470 let bound: Vec<powdb_query::ast::ParamValue> = params.iter().map(wire_param_to_value).collect();
471
472 let stmt_result = parser::parse_with_params(query, &bound).map_err(|e| e.to_string());
475
476 if let Ok(stmt) = &stmt_result {
477 check_statement_permitted(principal, stmt)?;
478 }
479
480 let can_try_read = matches!(&stmt_result, Ok(s) if is_read_only_statement(s));
481 if can_try_read {
482 let res = {
483 let eng = engine
484 .read()
485 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
486 eng.execute_powql_readonly_with_params(query, &bound)
487 };
488 match res {
489 Ok(r) => return Ok(r),
490 Err(QueryError::ReadonlyNeedsWrite) => {
491 }
493 Err(e) => return Err(e),
494 }
495 }
496
497 let mut eng = engine
498 .write()
499 .map_err(|e| QueryError::Execution(format!("lock poisoned: {e}")))?;
500 eng.execute_powql_with_params(query, &bound)
501}
502
503async fn execute_wire_query(
504 engine: Arc<RwLock<Engine>>,
505 tx_gate: TxGate,
506 tx_permit: &mut Option<OwnedSemaphorePermit>,
507 query: String,
508 principal: Option<Principal>,
509 query_timeout: Duration,
510 metrics: &Arc<Metrics>,
511) -> Message {
512 match classify_query_transaction_control(&query) {
513 Some(TransactionControl::Begin) => {
514 if tx_permit.is_some() {
515 return Message::Error {
516 message: sanitize_error("transaction already active"),
517 };
518 }
519 let permit = match tx_gate.acquire_owned().await {
520 Ok(permit) => permit,
521 Err(_) => {
522 return Message::Error {
523 message: "query execution error".into(),
524 }
525 }
526 };
527 let response = run_blocking_query(
528 engine,
529 query,
530 principal,
531 query_timeout,
532 metrics,
533 |engine, query, principal| dispatch_query(&engine, &query, principal.as_ref()),
534 )
535 .await;
536 if is_success_response(&response) {
537 *tx_permit = Some(permit);
538 }
539 response
540 }
541 Some(TransactionControl::Commit | TransactionControl::Rollback) => {
542 let response = run_blocking_query(
543 engine,
544 query,
545 principal,
546 query_timeout,
547 metrics,
548 |engine, query, principal| dispatch_query(&engine, &query, principal.as_ref()),
549 )
550 .await;
551 if is_success_response(&response) {
552 tx_permit.take();
553 }
554 response
555 }
556 None if tx_permit.is_some() => {
557 run_blocking_query(
558 engine,
559 query,
560 principal,
561 query_timeout,
562 metrics,
563 |engine, query, principal| dispatch_query(&engine, &query, principal.as_ref()),
564 )
565 .await
566 }
567 None => {
568 let permit = match tx_gate.acquire_owned().await {
569 Ok(permit) => permit,
570 Err(_) => {
571 return Message::Error {
572 message: "query execution error".into(),
573 }
574 }
575 };
576 let response = run_blocking_query(
577 engine,
578 query,
579 principal,
580 query_timeout,
581 metrics,
582 |engine, query, principal| dispatch_query(&engine, &query, principal.as_ref()),
583 )
584 .await;
585 drop(permit);
586 response
587 }
588 }
589}
590
591async fn execute_wire_query_sql(
592 engine: Arc<RwLock<Engine>>,
593 tx_gate: TxGate,
594 tx_permit: &mut Option<OwnedSemaphorePermit>,
595 query: String,
596 principal: Option<Principal>,
597 query_timeout: Duration,
598 metrics: &Arc<Metrics>,
599) -> Message {
600 match classify_sql_transaction_control(&query) {
601 Some(TransactionControl::Begin) => {
602 if tx_permit.is_some() {
603 return Message::Error {
604 message: sanitize_error("transaction already active"),
605 };
606 }
607 let permit = match tx_gate.acquire_owned().await {
608 Ok(permit) => permit,
609 Err(_) => {
610 return Message::Error {
611 message: "query execution error".into(),
612 }
613 }
614 };
615 let response = run_blocking_query(
616 engine,
617 query,
618 principal,
619 query_timeout,
620 metrics,
621 |engine, query, principal| dispatch_sql_query(&engine, &query, principal.as_ref()),
622 )
623 .await;
624 if is_success_response(&response) {
625 *tx_permit = Some(permit);
626 }
627 response
628 }
629 Some(TransactionControl::Commit | TransactionControl::Rollback) => {
630 let response = run_blocking_query(
631 engine,
632 query,
633 principal,
634 query_timeout,
635 metrics,
636 |engine, query, principal| dispatch_sql_query(&engine, &query, principal.as_ref()),
637 )
638 .await;
639 if is_success_response(&response) {
640 tx_permit.take();
641 }
642 response
643 }
644 None if tx_permit.is_some() => {
645 run_blocking_query(
646 engine,
647 query,
648 principal,
649 query_timeout,
650 metrics,
651 |engine, query, principal| dispatch_sql_query(&engine, &query, principal.as_ref()),
652 )
653 .await
654 }
655 None => {
656 let permit = match tx_gate.acquire_owned().await {
657 Ok(permit) => permit,
658 Err(_) => {
659 return Message::Error {
660 message: "query execution error".into(),
661 }
662 }
663 };
664 let response = run_blocking_query(
665 engine,
666 query,
667 principal,
668 query_timeout,
669 metrics,
670 |engine, query, principal| dispatch_sql_query(&engine, &query, principal.as_ref()),
671 )
672 .await;
673 drop(permit);
674 response
675 }
676 }
677}
678
679#[allow(clippy::too_many_arguments)]
683async fn execute_wire_query_with_params(
684 engine: Arc<RwLock<Engine>>,
685 tx_gate: TxGate,
686 tx_permit: &mut Option<OwnedSemaphorePermit>,
687 query: String,
688 params: Vec<WireParam>,
689 principal: Option<Principal>,
690 query_timeout: Duration,
691 metrics: &Arc<Metrics>,
692) -> Message {
693 match classify_params_transaction_control(&query, ¶ms) {
694 Some(TransactionControl::Begin) => {
695 if tx_permit.is_some() {
696 return Message::Error {
697 message: sanitize_error("transaction already active"),
698 };
699 }
700 let permit = match tx_gate.acquire_owned().await {
701 Ok(permit) => permit,
702 Err(_) => {
703 return Message::Error {
704 message: "query execution error".into(),
705 }
706 }
707 };
708 let response = run_blocking_query(
709 engine,
710 (query, params),
711 principal,
712 query_timeout,
713 metrics,
714 |engine, (query, params), principal| {
715 dispatch_query_with_params(&engine, &query, ¶ms, principal.as_ref())
716 },
717 )
718 .await;
719 if is_success_response(&response) {
720 *tx_permit = Some(permit);
721 }
722 response
723 }
724 Some(TransactionControl::Commit | TransactionControl::Rollback) => {
725 let response = run_blocking_query(
726 engine,
727 (query, params),
728 principal,
729 query_timeout,
730 metrics,
731 |engine, (query, params), principal| {
732 dispatch_query_with_params(&engine, &query, ¶ms, principal.as_ref())
733 },
734 )
735 .await;
736 if is_success_response(&response) {
737 tx_permit.take();
738 }
739 response
740 }
741 None if tx_permit.is_some() => {
742 run_blocking_query(
743 engine,
744 (query, params),
745 principal,
746 query_timeout,
747 metrics,
748 |engine, (query, params), principal| {
749 dispatch_query_with_params(&engine, &query, ¶ms, principal.as_ref())
750 },
751 )
752 .await
753 }
754 None => {
755 let permit = match tx_gate.acquire_owned().await {
756 Ok(permit) => permit,
757 Err(_) => {
758 return Message::Error {
759 message: "query execution error".into(),
760 }
761 }
762 };
763 let response = run_blocking_query(
764 engine,
765 (query, params),
766 principal,
767 query_timeout,
768 metrics,
769 |engine, (query, params), principal| {
770 dispatch_query_with_params(&engine, &query, ¶ms, principal.as_ref())
771 },
772 )
773 .await;
774 drop(permit);
775 response
776 }
777 }
778}
779
780async fn run_blocking_query<T, F>(
781 engine: Arc<RwLock<Engine>>,
782 input: T,
783 principal: Option<Principal>,
784 query_timeout: Duration,
785 metrics: &Arc<Metrics>,
786 f: F,
787) -> Message
788where
789 T: Send + 'static,
790 F: FnOnce(Arc<RwLock<Engine>>, T, Option<Principal>) -> Result<QueryResult, QueryError>
791 + Send
792 + 'static,
793{
794 let _in_flight = metrics.in_flight_guard();
795 let start = Instant::now();
796 let mut handle = tokio::task::spawn_blocking(move || f(engine, input, principal));
797 let mut exceeded_timeout = false;
798 let join_result = tokio::select! {
799 result = &mut handle => result,
800 _ = tokio::time::sleep(query_timeout) => {
801 exceeded_timeout = true;
802 handle.await
807 }
808 };
809
810 let (message, outcome) = match join_result {
811 Ok(Ok(result)) => match query_result_to_message(result) {
812 Ok(message) => (message, QueryOutcome::Ok),
813 Err(e) => (
814 Message::Error {
815 message: sanitize_error(&e.to_string()),
816 },
817 QueryOutcome::Error,
818 ),
819 },
820 Ok(Err(e)) => {
821 let outcome = if matches!(e, QueryError::MemoryLimitExceeded { .. }) {
822 QueryOutcome::MemoryLimit
823 } else {
824 QueryOutcome::Error
825 };
826 (
827 Message::Error {
828 message: sanitize_error(&e.to_string()),
829 },
830 outcome,
831 )
832 }
833 Err(e) => (
834 Message::Error {
835 message: format!("internal error: {e}"),
836 },
837 QueryOutcome::Error,
838 ),
839 };
840 if exceeded_timeout {
841 metrics.record_query(start.elapsed(), QueryOutcome::Timeout);
842 } else {
843 metrics.record_query(start.elapsed(), outcome);
844 }
845 message
846}
847
848fn is_success_response(msg: &Message) -> bool {
849 matches!(
850 msg,
851 Message::ResultRows { .. }
852 | Message::ResultScalar { .. }
853 | Message::ResultOk { .. }
854 | Message::ResultMessage { .. }
855 )
856}
857
858fn rollback_open_transaction(engine: Arc<RwLock<Engine>>, principal: Option<Principal>) {
859 let _ = dispatch_query(&engine, "rollback", principal.as_ref());
860}
861
862pub async fn handle_connection<S>(stream: S, opts: ConnOpts<'_>)
863where
864 S: AsyncRead + AsyncWrite + Unpin,
865{
866 let ConnOpts {
867 engine,
868 tx_gate,
869 expected_password,
870 users,
871 shutdown_rx,
872 idle_timeout,
873 query_timeout,
874 rate_limiter,
875 peer_addr,
876 metrics,
877 } = opts;
878
879 let peer = peer_addr
880 .map(|a| a.to_string())
881 .unwrap_or_else(|| "unknown".into());
882 let peer_ip = peer_addr.map(|a| a.ip());
883
884 let (reader, writer) = tokio::io::split(stream);
885 let mut reader = BufReader::new(reader);
886 let mut writer = BufWriter::new(writer);
887
888 let connect_msg = loop {
893 match tokio::time::timeout(idle_timeout, Message::read_from_preauth(&mut reader)).await {
894 Ok(Ok(Some(Message::Ping))) => {
895 debug!(peer = %peer, "pre-auth ping");
896 if !write_msg(&mut writer, &Message::Pong).await {
897 return;
898 }
899 continue;
900 }
901 Ok(Ok(Some(msg))) => break msg,
902 Ok(Ok(None)) => {
903 debug!(peer = %peer, "client closed before CONNECT");
904 return;
905 }
906 Ok(Err(e)) => {
907 error!(peer = %peer, error = %e, "error reading CONNECT");
908 return;
909 }
910 Err(_) => {
911 warn!(peer = %peer, "idle timeout waiting for CONNECT");
912 return;
913 }
914 }
915 };
916
917 let principal: Option<Principal>;
920 match connect_msg {
921 Message::Connect {
922 db_name,
923 password,
924 username,
925 } => {
926 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
928 if is_rate_limited(limiter, ip) {
929 warn!(peer = %peer, "rate limited: too many auth failures");
930 let err = Message::Error {
931 message: "too many auth failures, try again later".into(),
932 };
933 write_msg(&mut writer, &err).await;
934 return;
935 }
936 }
937
938 let outcome = authenticate_connect(
939 &users,
940 expected_password.as_ref().map(|p| p.as_str()),
941 username.as_deref(),
942 password.as_ref().map(|p| p.as_str()),
943 );
944
945 match outcome {
946 AuthOutcome::Rejected => {
947 warn!(peer = %peer, db = %db_name, "auth rejected");
948 metrics.inc_auth_failure();
949 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
951 record_auth_failure(limiter, ip);
952 }
953 let err = Message::Error {
954 message: "authentication failed".into(),
955 };
956 write_msg(&mut writer, &err).await;
957 return;
958 }
959 AuthOutcome::Authenticated {
960 principal: auth_principal,
961 } => {
962 if let (Some(limiter), Some(ip)) = (rate_limiter, peer_ip) {
964 clear_auth_failures(limiter, ip);
965 }
966 match &auth_principal {
967 Some(p) => {
968 info!(peer = %peer, db = %db_name, user = %p.name, role = %p.role, "authenticated");
969 }
970 None => {
971 info!(peer = %peer, db = %db_name, "client connected");
972 }
973 }
974 principal = auth_principal;
975 }
976 }
977
978 let ok = Message::ConnectOk {
979 version: env!("CARGO_PKG_VERSION").into(),
980 };
981 if !write_msg(&mut writer, &ok).await {
982 return;
983 }
984 }
985 _ => {
986 warn!(peer = %peer, "first message was not CONNECT");
987 let err = Message::Error {
988 message: "expected CONNECT".into(),
989 };
990 write_msg(&mut writer, &err).await;
991 return;
992 }
993 }
994
995 let mut tx_permit: Option<OwnedSemaphorePermit> = None;
996
997 loop {
999 let msg = tokio::select! {
1000 result = tokio::time::timeout(idle_timeout, Message::read_from(&mut reader)) => {
1002 match result {
1003 Ok(Ok(Some(msg))) => msg,
1004 Ok(Ok(None)) => break,
1005 Ok(Err(e)) => {
1006 error!(peer = %peer, error = %e, "read error");
1007 break;
1008 }
1009 Err(_) => {
1010 info!(peer = %peer, "idle timeout, closing connection");
1011 let err = Message::Error { message: "idle timeout".into() };
1012 write_msg(&mut writer, &err).await;
1013 break;
1014 }
1015 }
1016 }
1017 _ = shutdown_rx.changed() => {
1019 if *shutdown_rx.borrow() {
1020 info!(peer = %peer, "server shutting down, closing connection");
1021 let err = Message::Error { message: "server shutting down".into() };
1022 write_msg(&mut writer, &err).await;
1023 break;
1024 }
1025 continue;
1026 }
1027 };
1028
1029 let response = match msg {
1030 Message::Ping => {
1031 debug!(peer = %peer, "ping");
1032 Message::Pong
1033 }
1034 Message::Query { query } => {
1035 if query.len() > MAX_QUERY_LENGTH {
1036 Message::Error {
1037 message: format!(
1038 "query too large: {} bytes (max {})",
1039 query.len(),
1040 MAX_QUERY_LENGTH
1041 ),
1042 }
1043 } else {
1044 debug!(peer = %peer, query = %query, "received query");
1045 let response = execute_wire_query(
1046 engine.clone(),
1047 tx_gate.clone(),
1048 &mut tx_permit,
1049 query.clone(),
1050 principal.clone(),
1051 query_timeout,
1052 &metrics,
1053 )
1054 .await;
1055 response
1056 }
1057 }
1058 Message::QuerySql { query } => {
1059 if query.len() > MAX_QUERY_LENGTH {
1060 Message::Error {
1061 message: format!(
1062 "query too large: {} bytes (max {})",
1063 query.len(),
1064 MAX_QUERY_LENGTH
1065 ),
1066 }
1067 } else {
1068 debug!(peer = %peer, query = %query, "received SQL query");
1069 let response = execute_wire_query_sql(
1070 engine.clone(),
1071 tx_gate.clone(),
1072 &mut tx_permit,
1073 query.clone(),
1074 principal.clone(),
1075 query_timeout,
1076 &metrics,
1077 )
1078 .await;
1079 response
1080 }
1081 }
1082 Message::QueryWithParams { query, params } => {
1083 if query.len() > MAX_QUERY_LENGTH {
1084 Message::Error {
1085 message: format!(
1086 "query too large: {} bytes (max {})",
1087 query.len(),
1088 MAX_QUERY_LENGTH
1089 ),
1090 }
1091 } else {
1092 debug!(peer = %peer, query = %query, n_params = params.len(), "received parameterized query");
1093 let response = execute_wire_query_with_params(
1094 engine.clone(),
1095 tx_gate.clone(),
1096 &mut tx_permit,
1097 query.clone(),
1098 params.clone(),
1099 principal.clone(),
1100 query_timeout,
1101 &metrics,
1102 )
1103 .await;
1104 response
1105 }
1106 }
1107 Message::Disconnect => {
1108 debug!(peer = %peer, "received DISCONNECT");
1109 break;
1110 }
1111 _ => Message::Error {
1112 message: "unexpected message type".into(),
1113 },
1114 };
1115
1116 if !write_msg(&mut writer, &response).await {
1117 break;
1118 }
1119 }
1120
1121 if tx_permit.is_some() {
1128 let engine = engine.clone();
1129 let principal = principal.clone();
1130 let _ =
1131 tokio::task::spawn_blocking(move || rollback_open_transaction(engine, principal)).await;
1132 }
1133 tx_permit.take();
1134
1135 info!(peer = %peer, "client disconnected");
1136}
1137
1138fn charge_response_bytes(total: &mut usize, bytes: usize) -> Result<(), QueryError> {
1139 *total = total.saturating_add(bytes);
1140 if *total > MAX_RESPONSE_PAYLOAD_SIZE {
1141 return Err(QueryError::Execution(format!(
1142 "result too large: encoded response exceeds {} bytes; add a limit or narrower projection",
1143 MAX_RESPONSE_PAYLOAD_SIZE
1144 )));
1145 }
1146 Ok(())
1147}
1148
1149fn query_result_to_message(result: QueryResult) -> Result<Message, QueryError> {
1150 match result {
1151 QueryResult::Rows { columns, rows } => {
1152 let mut encoded_bytes = 2usize; let mut out_columns = Vec::with_capacity(columns.len());
1154 for col in columns {
1155 charge_response_bytes(&mut encoded_bytes, 4 + col.len())?;
1156 out_columns.push(col);
1157 }
1158 charge_response_bytes(&mut encoded_bytes, 4)?; let mut str_rows = Vec::with_capacity(rows.len());
1161 for row in rows {
1162 let mut str_row = Vec::with_capacity(row.len());
1163 for value in row {
1164 let display = value_to_display(&value);
1165 charge_response_bytes(&mut encoded_bytes, 4 + display.len())?;
1166 str_row.push(display);
1167 }
1168 str_rows.push(str_row);
1169 }
1170 Ok(Message::ResultRows {
1171 columns: out_columns,
1172 rows: str_rows,
1173 })
1174 }
1175 QueryResult::Scalar(val) => Ok(Message::ResultScalar {
1176 value: value_to_display(&val),
1177 }),
1178 QueryResult::Modified(n) => Ok(Message::ResultOk { affected: n }),
1179 QueryResult::Created(name) => Ok(Message::ResultMessage {
1180 message: format!("type {name} created"),
1181 }),
1182 QueryResult::Executed { message } => Ok(Message::ResultMessage { message }),
1183 }
1184}
1185
1186fn value_to_display(v: &Value) -> String {
1190 v.to_wire_string()
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196
1197 #[test]
1200 fn null_serializes_as_null_bareword_on_wire() {
1201 assert_eq!(value_to_display(&Value::Empty), "null");
1202 }
1203
1204 #[test]
1207 fn unique_violation_error_surfaces_to_remote_clients() {
1208 assert_eq!(
1211 sanitize_error("unique constraint violation on User.email"),
1212 "unique constraint violation on User.email"
1213 );
1214 }
1215
1216 #[test]
1217 fn internal_errors_stay_generic() {
1218 assert_eq!(
1219 sanitize_error("some internal io panic detail"),
1220 "query execution error"
1221 );
1222 }
1223
1224 #[test]
1225 fn resource_limit_errors_surface_actionable_hints() {
1226 for msg in [
1231 "sort input exceeds row limit — add a LIMIT clause",
1232 "join result exceeds row limit",
1233 "query exceeded memory budget: requested 100 bytes, limit 50 bytes",
1234 "result too large: encoded response exceeds 1024 bytes; add a limit or narrower projection",
1235 ] {
1236 assert_eq!(sanitize_error(msg), msg, "should pass through verbatim");
1237 }
1238 }
1239
1240 #[test]
1241 fn oversized_result_is_rejected_before_wire_encoding() {
1242 let long = "x".repeat(MAX_RESPONSE_PAYLOAD_SIZE);
1243 let result = QueryResult::Rows {
1244 columns: vec!["payload".into()],
1245 rows: vec![vec![Value::Str(long)]],
1246 };
1247 let err = query_result_to_message(result).unwrap_err();
1248 assert!(
1249 err.to_string().starts_with("result too large"),
1250 "unexpected error: {err}"
1251 );
1252 }
1253
1254 fn parsed(q: &str) -> powdb_query::ast::Statement {
1257 parser::parse(q).unwrap()
1258 }
1259
1260 fn principal(role: &str) -> Option<Principal> {
1261 Some(Principal {
1262 name: "u".into(),
1263 role: role.into(),
1264 })
1265 }
1266
1267 #[test]
1268 fn readonly_can_read_but_not_write() {
1269 let p = principal("readonly");
1270 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1272 assert!(check_statement_permitted(p.as_ref(), &parsed("count(User)")).is_ok());
1273 assert!(check_statement_permitted(p.as_ref(), &parsed("explain User")).is_ok());
1274 for q in [
1276 r#"insert User { name := "x" }"#,
1277 "User filter .id = 1 update { age := 2 }",
1278 "User filter .id = 1 delete",
1279 "drop User",
1280 "alter User add column c: str",
1281 "type T { required id: int }",
1282 "begin",
1283 "commit",
1284 "rollback",
1285 ] {
1286 let err = check_statement_permitted(p.as_ref(), &parsed(q))
1287 .expect_err(&format!("must deny: {q}"));
1288 assert!(
1289 err.to_string().contains("permission denied"),
1290 "unexpected error for {q}: {err}"
1291 );
1292 }
1293 }
1294
1295 #[test]
1296 fn readwrite_and_admin_have_full_query_access() {
1297 for role in ["readwrite", "admin"] {
1298 let p = principal(role);
1299 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1300 assert!(check_statement_permitted(
1301 p.as_ref(),
1302 &parsed(r#"insert User { name := "x" }"#)
1303 )
1304 .is_ok());
1305 assert!(check_statement_permitted(p.as_ref(), &parsed("drop User")).is_ok());
1306 }
1307 }
1308
1309 #[test]
1310 fn unknown_role_fails_closed_for_writes() {
1311 let p = principal("mystery");
1312 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1313 assert!(
1314 check_statement_permitted(p.as_ref(), &parsed(r#"insert User { name := "x" }"#))
1315 .is_err()
1316 );
1317 }
1318
1319 #[test]
1320 fn no_principal_means_full_access() {
1321 assert!(check_statement_permitted(None, &parsed("drop User")).is_ok());
1323 assert!(check_statement_permitted(None, &parsed(r#"insert User { name := "x" }"#)).is_ok());
1324 }
1325
1326 fn store_with_alice() -> UserStore {
1327 let mut s = UserStore::new();
1328 s.create_user("alice", "pw", "readwrite").unwrap();
1329 s
1330 }
1331
1332 #[test]
1335 fn empty_store_no_password_is_open() {
1336 let s = UserStore::new();
1337 assert_eq!(
1338 authenticate_connect(&s, None, None, None),
1339 AuthOutcome::Authenticated { principal: None }
1340 );
1341 assert_eq!(
1343 authenticate_connect(&s, None, Some("x"), Some("y")),
1344 AuthOutcome::Authenticated { principal: None }
1345 );
1346 }
1347
1348 #[test]
1349 fn empty_store_correct_shared_password_succeeds() {
1350 let s = UserStore::new();
1351 assert_eq!(
1352 authenticate_connect(&s, Some("pw"), None, Some("pw")),
1353 AuthOutcome::Authenticated { principal: None }
1354 );
1355 }
1356
1357 #[test]
1358 fn empty_store_wrong_shared_password_rejected() {
1359 let s = UserStore::new();
1360 assert_eq!(
1361 authenticate_connect(&s, Some("pw"), None, Some("bad")),
1362 AuthOutcome::Rejected
1363 );
1364 }
1365
1366 #[test]
1367 fn empty_store_missing_password_rejected_when_expected() {
1368 let s = UserStore::new();
1369 assert_eq!(
1370 authenticate_connect(&s, Some("pw"), None, None),
1371 AuthOutcome::Rejected
1372 );
1373 }
1374
1375 #[test]
1376 fn empty_store_ignores_username_for_shared_password() {
1377 let s = UserStore::new();
1380 assert_eq!(
1381 authenticate_connect(&s, Some("pw"), Some("whoever"), Some("pw")),
1382 AuthOutcome::Authenticated { principal: None }
1383 );
1384 }
1385
1386 #[test]
1389 fn user_auth_success_binds_principal() {
1390 let s = store_with_alice();
1391 assert_eq!(
1392 authenticate_connect(&s, None, Some("alice"), Some("pw")),
1393 AuthOutcome::Authenticated {
1394 principal: Some(Principal {
1395 name: "alice".into(),
1396 role: "readwrite".into(),
1397 })
1398 }
1399 );
1400 }
1401
1402 #[test]
1403 fn user_auth_wrong_password_rejected() {
1404 let s = store_with_alice();
1405 assert_eq!(
1406 authenticate_connect(&s, None, Some("alice"), Some("bad")),
1407 AuthOutcome::Rejected
1408 );
1409 }
1410
1411 #[test]
1412 fn user_auth_unknown_user_rejected() {
1413 let s = store_with_alice();
1414 assert_eq!(
1415 authenticate_connect(&s, None, Some("mallory"), Some("pw")),
1416 AuthOutcome::Rejected
1417 );
1418 }
1419
1420 #[test]
1421 fn user_auth_missing_username_rejected() {
1422 let s = store_with_alice();
1423 assert_eq!(
1424 authenticate_connect(&s, None, None, Some("pw")),
1425 AuthOutcome::Rejected
1426 );
1427 }
1428
1429 #[test]
1430 fn user_auth_missing_password_rejected() {
1431 let s = store_with_alice();
1432 assert_eq!(
1433 authenticate_connect(&s, Some("pw"), Some("alice"), None),
1434 AuthOutcome::Rejected
1435 );
1436 }
1437
1438 #[test]
1439 fn user_auth_ignores_shared_password_when_users_present() {
1440 let s = store_with_alice();
1443 assert_eq!(
1444 authenticate_connect(&s, Some("shared"), None, Some("shared")),
1445 AuthOutcome::Rejected
1446 );
1447 }
1448}