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 {
1187 match v {
1188 Value::Int(n) => n.to_string(),
1189 Value::Float(n) => format!("{n}"),
1190 Value::Bool(b) => b.to_string(),
1191 Value::Str(s) => s.clone(),
1192 Value::DateTime(t) => format!("{t}"),
1193 Value::Uuid(u) => format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
1194 u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7],
1195 u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]),
1196 Value::Bytes(b) => format!("<{} bytes>", b.len()),
1197 Value::Empty => "null".into(),
1203 }
1204}
1205
1206#[cfg(test)]
1207mod tests {
1208 use super::*;
1209
1210 #[test]
1213 fn null_serializes_as_null_bareword_on_wire() {
1214 assert_eq!(value_to_display(&Value::Empty), "null");
1215 }
1216
1217 #[test]
1220 fn unique_violation_error_surfaces_to_remote_clients() {
1221 assert_eq!(
1224 sanitize_error("unique constraint violation on User.email"),
1225 "unique constraint violation on User.email"
1226 );
1227 }
1228
1229 #[test]
1230 fn internal_errors_stay_generic() {
1231 assert_eq!(
1232 sanitize_error("some internal io panic detail"),
1233 "query execution error"
1234 );
1235 }
1236
1237 #[test]
1238 fn resource_limit_errors_surface_actionable_hints() {
1239 for msg in [
1244 "sort input exceeds row limit — add a LIMIT clause",
1245 "join result exceeds row limit",
1246 "query exceeded memory budget: requested 100 bytes, limit 50 bytes",
1247 "result too large: encoded response exceeds 1024 bytes; add a limit or narrower projection",
1248 ] {
1249 assert_eq!(sanitize_error(msg), msg, "should pass through verbatim");
1250 }
1251 }
1252
1253 #[test]
1254 fn oversized_result_is_rejected_before_wire_encoding() {
1255 let long = "x".repeat(MAX_RESPONSE_PAYLOAD_SIZE);
1256 let result = QueryResult::Rows {
1257 columns: vec!["payload".into()],
1258 rows: vec![vec![Value::Str(long)]],
1259 };
1260 let err = query_result_to_message(result).unwrap_err();
1261 assert!(
1262 err.to_string().starts_with("result too large"),
1263 "unexpected error: {err}"
1264 );
1265 }
1266
1267 fn parsed(q: &str) -> powdb_query::ast::Statement {
1270 parser::parse(q).unwrap()
1271 }
1272
1273 fn principal(role: &str) -> Option<Principal> {
1274 Some(Principal {
1275 name: "u".into(),
1276 role: role.into(),
1277 })
1278 }
1279
1280 #[test]
1281 fn readonly_can_read_but_not_write() {
1282 let p = principal("readonly");
1283 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1285 assert!(check_statement_permitted(p.as_ref(), &parsed("count(User)")).is_ok());
1286 assert!(check_statement_permitted(p.as_ref(), &parsed("explain User")).is_ok());
1287 for q in [
1289 r#"insert User { name := "x" }"#,
1290 "User filter .id = 1 update { age := 2 }",
1291 "User filter .id = 1 delete",
1292 "drop User",
1293 "alter User add column c: str",
1294 "type T { required id: int }",
1295 "begin",
1296 "commit",
1297 "rollback",
1298 ] {
1299 let err = check_statement_permitted(p.as_ref(), &parsed(q))
1300 .expect_err(&format!("must deny: {q}"));
1301 assert!(
1302 err.to_string().contains("permission denied"),
1303 "unexpected error for {q}: {err}"
1304 );
1305 }
1306 }
1307
1308 #[test]
1309 fn readwrite_and_admin_have_full_query_access() {
1310 for role in ["readwrite", "admin"] {
1311 let p = principal(role);
1312 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1313 assert!(check_statement_permitted(
1314 p.as_ref(),
1315 &parsed(r#"insert User { name := "x" }"#)
1316 )
1317 .is_ok());
1318 assert!(check_statement_permitted(p.as_ref(), &parsed("drop User")).is_ok());
1319 }
1320 }
1321
1322 #[test]
1323 fn unknown_role_fails_closed_for_writes() {
1324 let p = principal("mystery");
1325 assert!(check_statement_permitted(p.as_ref(), &parsed("User")).is_ok());
1326 assert!(
1327 check_statement_permitted(p.as_ref(), &parsed(r#"insert User { name := "x" }"#))
1328 .is_err()
1329 );
1330 }
1331
1332 #[test]
1333 fn no_principal_means_full_access() {
1334 assert!(check_statement_permitted(None, &parsed("drop User")).is_ok());
1336 assert!(check_statement_permitted(None, &parsed(r#"insert User { name := "x" }"#)).is_ok());
1337 }
1338
1339 fn store_with_alice() -> UserStore {
1340 let mut s = UserStore::new();
1341 s.create_user("alice", "pw", "readwrite").unwrap();
1342 s
1343 }
1344
1345 #[test]
1348 fn empty_store_no_password_is_open() {
1349 let s = UserStore::new();
1350 assert_eq!(
1351 authenticate_connect(&s, None, None, None),
1352 AuthOutcome::Authenticated { principal: None }
1353 );
1354 assert_eq!(
1356 authenticate_connect(&s, None, Some("x"), Some("y")),
1357 AuthOutcome::Authenticated { principal: None }
1358 );
1359 }
1360
1361 #[test]
1362 fn empty_store_correct_shared_password_succeeds() {
1363 let s = UserStore::new();
1364 assert_eq!(
1365 authenticate_connect(&s, Some("pw"), None, Some("pw")),
1366 AuthOutcome::Authenticated { principal: None }
1367 );
1368 }
1369
1370 #[test]
1371 fn empty_store_wrong_shared_password_rejected() {
1372 let s = UserStore::new();
1373 assert_eq!(
1374 authenticate_connect(&s, Some("pw"), None, Some("bad")),
1375 AuthOutcome::Rejected
1376 );
1377 }
1378
1379 #[test]
1380 fn empty_store_missing_password_rejected_when_expected() {
1381 let s = UserStore::new();
1382 assert_eq!(
1383 authenticate_connect(&s, Some("pw"), None, None),
1384 AuthOutcome::Rejected
1385 );
1386 }
1387
1388 #[test]
1389 fn empty_store_ignores_username_for_shared_password() {
1390 let s = UserStore::new();
1393 assert_eq!(
1394 authenticate_connect(&s, Some("pw"), Some("whoever"), Some("pw")),
1395 AuthOutcome::Authenticated { principal: None }
1396 );
1397 }
1398
1399 #[test]
1402 fn user_auth_success_binds_principal() {
1403 let s = store_with_alice();
1404 assert_eq!(
1405 authenticate_connect(&s, None, Some("alice"), Some("pw")),
1406 AuthOutcome::Authenticated {
1407 principal: Some(Principal {
1408 name: "alice".into(),
1409 role: "readwrite".into(),
1410 })
1411 }
1412 );
1413 }
1414
1415 #[test]
1416 fn user_auth_wrong_password_rejected() {
1417 let s = store_with_alice();
1418 assert_eq!(
1419 authenticate_connect(&s, None, Some("alice"), Some("bad")),
1420 AuthOutcome::Rejected
1421 );
1422 }
1423
1424 #[test]
1425 fn user_auth_unknown_user_rejected() {
1426 let s = store_with_alice();
1427 assert_eq!(
1428 authenticate_connect(&s, None, Some("mallory"), Some("pw")),
1429 AuthOutcome::Rejected
1430 );
1431 }
1432
1433 #[test]
1434 fn user_auth_missing_username_rejected() {
1435 let s = store_with_alice();
1436 assert_eq!(
1437 authenticate_connect(&s, None, None, Some("pw")),
1438 AuthOutcome::Rejected
1439 );
1440 }
1441
1442 #[test]
1443 fn user_auth_missing_password_rejected() {
1444 let s = store_with_alice();
1445 assert_eq!(
1446 authenticate_connect(&s, Some("pw"), Some("alice"), None),
1447 AuthOutcome::Rejected
1448 );
1449 }
1450
1451 #[test]
1452 fn user_auth_ignores_shared_password_when_users_present() {
1453 let s = store_with_alice();
1456 assert_eq!(
1457 authenticate_connect(&s, Some("shared"), None, Some("shared")),
1458 AuthOutcome::Rejected
1459 );
1460 }
1461}