1pub mod actions;
16pub mod drda;
17
18const FIRST_REQUEST_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
24
25const IDLE_BETWEEN_REQUESTS_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
33
34const MAX_DSS_PER_COMMAND: usize = 16;
37
38const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
40
41const CONNECTION_CAP_REFUSAL: &[u8] = b"";
49
50use std::net::SocketAddr;
51use std::sync::Arc;
52
53use anyhow::Result;
54use tokio::io::{AsyncReadExt, AsyncWriteExt};
55use tokio::net::TcpListener;
56use tokio::sync::{mpsc, Mutex};
57use tracing::{debug, error, info, warn};
58
59use crate::llm::action_helper::call_llm;
60use crate::llm::actions::protocol_trait::ActionResult;
61use crate::llm::ollama_client::OllamaClient;
62use crate::logging::emit::Log;
63use crate::protocol::Event;
64use crate::server::connection::ConnectionId;
65use crate::server::db2::actions::{Db2Protocol, DB2_CONNECT_EVENT, DB2_QUERY_EVENT};
66use crate::state::app_state::AppState;
67
68pub struct Db2Server;
70
71impl Db2Server {
72 pub async fn spawn_with_llm_actions(
76 listen_addr: SocketAddr,
77 llm_client: OllamaClient,
78 app_state: Arc<AppState>,
79 status_tx: mpsc::UnboundedSender<String>,
80 server_id: crate::state::ServerId,
81 ) -> Result<SocketAddr> {
82 let listener = TcpListener::bind(listen_addr).await?;
83 let actual_addr = listener.local_addr()?;
84
85 Log::new(Some(&status_tx)).info(format!("Db2 server listening on {}", actual_addr));
86
87 let task_registrar = app_state.clone();
88 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
89 let accept_handle = tokio::spawn(async move {
90 loop {
91 match crate::server::accept_bounded::accept_bounded(
92 &listener,
93 &limiter,
94 CONNECTION_CAP_REFUSAL,
95 "Db2",
96 Some(&status_tx),
97 )
98 .await
99 {
100 Ok((stream, remote_addr, permit)) => {
101 let connection_id =
102 ConnectionId::new(app_state.get_next_unified_id().await);
103 let local_addr_conn = stream.local_addr().unwrap_or(actual_addr);
104 debug!("Db2 connection {} from {}", connection_id, remote_addr);
105
106 use crate::state::server::{
107 ConnectionState as ServerConnectionState, ConnectionStatus,
108 ProtocolConnectionInfo,
109 };
110 let now = crate::utils::clock::Instant::now();
111 let conn_state = ServerConnectionState {
112 id: connection_id,
113 remote_addr,
114 local_addr: local_addr_conn,
115 bytes_sent: 0,
116 bytes_received: 0,
117 packets_sent: 0,
118 packets_received: 0,
119 last_activity: now,
120 status: ConnectionStatus::Active,
121 status_changed_at: now,
122 protocol_info: ProtocolConnectionInfo::empty(),
123 };
124 app_state
125 .add_connection_to_server(server_id, conn_state)
126 .await;
127 let _ = status_tx.send("__UPDATE_UI__".to_string());
128
129 let mut handler = Db2Handler {
130 connection_id,
131 server_id,
132 llm_client: llm_client.clone(),
133 app_state: app_state.clone(),
134 status_tx: status_tx.clone(),
135 protocol: Arc::new(Db2Protocol::new(
136 connection_id,
137 app_state.clone(),
138 status_tx.clone(),
139 )),
140 authenticated: false,
141 };
142
143 let conn_owner = app_state.clone();
144 let task_owner = app_state.clone();
146 task_owner
147 .spawn_server_task(server_id, async move {
148 let _permit = permit;
151 if let Err(e) = handler.run(stream).await {
152 debug!("Db2 connection {} ended: {}", connection_id, e);
153 }
154 conn_owner
155 .close_connection_on_server(server_id, connection_id)
156 .await;
157 })
158 .await;
159 }
160 Err(e) => {
161 error!("Db2 accept error: {}", e);
162 break;
163 }
164 }
165 }
166 });
167
168 task_registrar
169 .register_server_task(server_id, accept_handle)
170 .await;
171
172 Ok(actual_addr)
173 }
174}
175
176struct Db2Handler {
178 connection_id: ConnectionId,
179 server_id: crate::state::ServerId,
180 llm_client: OllamaClient,
181 app_state: Arc<AppState>,
182 status_tx: mpsc::UnboundedSender<String>,
183 protocol: Arc<Db2Protocol>,
184 authenticated: bool,
186}
187
188impl Db2Handler {
189 async fn run(&mut self, stream: tokio::net::TcpStream) -> Result<()> {
190 let (reader, write_half) = tokio::io::split(stream);
195 let write_half = Arc::new(Mutex::new(write_half));
196
197 let peer_rx = crate::server::peer_support::register_peer_channel(
203 &self.app_state,
204 self.server_id,
205 self.connection_id.as_u32(),
206 )
207 .await;
208 crate::server::peer_support::spawn_peer_command_task(
209 peer_rx,
210 self.protocol.clone(),
211 self.app_state.clone(),
212 self.server_id,
213 self.connection_id.as_u32(),
214 write_half.clone(),
215 self.status_tx.clone(),
216 );
217
218 let result = self.run_loop(reader, &write_half).await;
219
220 self.app_state
224 .remove_peer_handle(self.server_id, self.connection_id.as_u32())
225 .await;
226 result
227 }
228
229 async fn run_loop(
231 &mut self,
232 mut reader: tokio::io::ReadHalf<tokio::net::TcpStream>,
233 write_half: &Arc<Mutex<tokio::io::WriteHalf<tokio::net::TcpStream>>>,
234 ) -> Result<()> {
235 let mut buf: Vec<u8> = Vec::new();
236 let mut tmp = [0u8; 8192];
237 let mut command: Vec<drda::ParsedDss> = Vec::new();
238 let mut discarding = false;
243
244 loop {
245 while buf.len() < 6 || buf.len() < drda::dss_declared_len(&buf).unwrap_or(6) {
247 let read_timeout = if self.authenticated {
254 IDLE_BETWEEN_REQUESTS_TIMEOUT
255 } else {
256 FIRST_REQUEST_READ_TIMEOUT
257 };
258 let n = match tokio::time::timeout(read_timeout, reader.read(&mut tmp)).await {
259 Ok(read) => read?,
260 Err(_) => {
261 debug!(
262 "Db2 connection {} sent nothing for {}s; closing idle connection",
263 self.connection_id,
264 read_timeout.as_secs()
265 );
266 return Ok(());
267 }
268 };
269 if n == 0 {
270 return Ok(()); }
272 buf.extend_from_slice(&tmp[..n]);
273 if buf.len() >= 2 {
274 if let Some(total) = drda::dss_declared_len(&buf) {
275 if total < 6 {
276 warn!("Db2: impossible DSS length {}, dropping connection", total);
277 return Ok(());
278 }
279 }
280 }
281 }
282
283 let (parsed, consumed) = match drda::parse_dss(&buf) {
284 Ok(v) => v,
285 Err(e) => {
286 warn!("Db2: DRDA parse error: {}", e);
287 return Ok(());
288 }
289 };
290 buf.drain(0..consumed);
291
292 self.app_state
293 .update_connection_stats(
294 self.server_id,
295 self.connection_id,
296 Some(consumed as u64),
297 None,
298 Some(1),
299 None,
300 )
301 .await;
302
303 let continues = parsed.format & 0x50 == 0x50;
308 let chained = parsed.format & 0x40 != 0;
309 command.push(parsed);
310 if continues {
311 if command.len() > MAX_DSS_PER_COMMAND {
312 warn!(
313 "Db2: a command chained more than {MAX_DSS_PER_COMMAND} DSSes under one \
314 correlator decision=refused_oversized; dropping the connection"
315 );
316 return Ok(());
317 }
318 continue;
319 }
320 let this = std::mem::take(&mut command);
321 if discarding {
322 debug!("Db2: ignoring the rest of a request chain after a refused SECCHK");
323 discarding = chained;
324 continue;
325 }
326 let reply = self.dispatch(&this).await;
327 let refused_login = this[0].codepoint == drda::cp::SECCHK && !self.authenticated;
328 discarding = refused_login && chained;
329 let reply = reply.map(|bytes| drda::chain_reply(bytes, chained && !refused_login));
330 if let Some(bytes) = reply {
331 self.app_state
332 .update_connection_stats(
333 self.server_id,
334 self.connection_id,
335 None,
336 Some(bytes.len() as u64),
337 None,
338 Some(1),
339 )
340 .await;
341 let mut writer = write_half.lock().await;
342 writer.write_all(&bytes).await?;
343 writer.flush().await?;
344 }
345 }
346 }
347
348 async fn dispatch(&mut self, command: &[drda::ParsedDss]) -> Option<Vec<u8>> {
351 use drda::cp;
352 let req = command.first()?;
353 let data = &command[1..];
354 let corr = req.correlator;
355 debug!(
356 "Db2: DRDA command 0x{:04X} (correlator {})",
357 req.codepoint, corr
358 );
359
360 match req.codepoint {
361 cp::EXCSAT => Some(self.reply_excsatrd(corr, &req.body)),
362 cp::RDBCMM => Some(end_unit_of_work(corr, 1)),
363 cp::RDBRLLBCK => Some(end_unit_of_work(corr, 2)),
364 cp::ACCSEC => Some(self.reply_accsecrd(corr, &req.body)),
365 cp::SECCHK => Some(self.handle_secchk(corr, &req.body).await),
366 cp::ACCRDB => Some(self.reply_accrdbrm(corr, &req.body)),
367 cp::EXCSQLIMM | cp::EXCSQLSTT => {
368 let sqlstt = drda::find_param(&req.body, cp::SQLSTT).or_else(|| {
371 data.iter()
372 .find(|d| d.codepoint == cp::SQLSTT)
373 .map(|d| d.body.clone())
374 });
375 match sqlstt {
376 Some(sqlstt) => {
377 let sql = drda::decode_sqlstt(&sqlstt);
378 Some(self.handle_query(corr, sql).await)
379 }
380 None if req.codepoint == cp::EXCSQLSTT => Some(cmdnsprm(corr, req.codepoint)),
382 None => None,
384 }
385 }
386 cp::EXCSQLSET => Some(self.session_settings(corr, data)),
387 cp::SQLSTT => {
388 let sql = drda::decode_sqlstt(&req.body);
389 Some(self.handle_query(corr, sql).await)
390 }
391 other => Some(cmdnsprm(corr, other)),
392 }
393 }
394
395 fn session_settings(&self, corr: u16, data: &[drda::ParsedDss]) -> Vec<u8> {
401 if !self.authenticated {
402 warn!("Db2 EXCSQLSET decision=fail_closed_not_authenticated");
403 let card = drda::sqlcard_error(-30082, "08001");
404 return drda::encode_dss(drda::DSSFMT_OBJDSS, false, corr, &card);
405 }
406 for d in data.iter().filter(|d| d.codepoint == drda::cp::SQLSTT) {
407 info!(
408 "Db2 EXCSQLSET decision=session_setting {}",
409 crate::utils::truncate_for_log(&drda::decode_sqlstt(&d.body), 200)
410 );
411 }
412 drda::encode_dss(drda::DSSFMT_OBJDSS, false, corr, &drda::sqlcard_success())
413 }
414
415 fn reply_excsatrd(&self, corr: u16, body: &[u8]) -> Vec<u8> {
417 use drda::cp;
418 let mut inner = Vec::new();
419 inner.extend_from_slice(&drda::encode_scalar_str(cp::EXTNAM, "netget-db2"));
420 inner.extend_from_slice(&drda::encode_scalar_str(cp::SRVCLSNM, "QDB2/NETGET"));
421 inner.extend_from_slice(&drda::encode_scalar_str(cp::SRVNAM, "NETGET"));
422 inner.extend_from_slice(&drda::encode_scalar_str(cp::SRVRLSLV, "SQL11055"));
423 let mut levels = Vec::new();
428 if let Some(asked) = drda::find_param(body, cp::MGRLVLLS) {
429 for pair in asked.chunks_exact(4) {
430 let manager = u16::from_be_bytes([pair[0], pair[1]]);
431 let level = match manager {
432 0x1403 | 0x2407 | 0x240F | 0x1440 => {
433 u16::from_be_bytes([pair[2], pair[3]]).min(7)
434 }
435 _ => 0,
436 };
437 levels.extend_from_slice(&manager.to_be_bytes());
438 levels.extend_from_slice(&level.to_be_bytes());
439 }
440 }
441 inner.extend_from_slice(&drda::encode_scalar(cp::MGRLVLLS, &levels));
442 let rd = drda::encode_object(cp::EXCSATRD, &inner);
443 drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rd)
444 }
445
446 fn reply_accsecrd(&self, corr: u16, body: &[u8]) -> Vec<u8> {
456 use drda::cp;
457 let requested = drda::find_param(body, cp::SECMEC).unwrap_or_else(|| vec![0x00, 0x03]);
458 let supported = matches!(requested.as_slice(), [0x00, 0x03] | [0x00, 0x04]);
459 let mut inner = if supported {
460 drda::encode_scalar(cp::SECMEC, &requested)
461 } else {
462 warn!(
463 "Db2 ACCSEC decision=refused_secmec: the requester asked for security mechanism \
464 {requested:02X?}; offering USRIDPWD (3) and USRIDONL (4)"
465 );
466 drda::encode_scalar(cp::SECMEC, &[0x00, 0x03, 0x00, 0x04])
467 };
468 if !supported {
469 inner.extend_from_slice(&drda::encode_scalar(cp::SECCHKCD, &[0x01]));
470 }
471 let rd = drda::encode_object(cp::ACCSECRD, &inner);
472 drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rd)
473 }
474
475 async fn handle_secchk(&mut self, corr: u16, body: &[u8]) -> Vec<u8> {
479 use drda::cp;
480 let user_id = drda::find_param(body, cp::USRID)
481 .map(|b| drda::ebcdic_to_ascii(&b))
482 .unwrap_or_default();
483 let rdb_name = drda::find_param(body, cp::RDBNAM)
484 .map(|b| drda::ebcdic_to_ascii(&b))
485 .unwrap_or_default();
486 let has_password = drda::find_param(body, cp::PASSWORD)
487 .map(|b| !b.is_empty())
488 .unwrap_or(false);
489
490 let event = Event::new(
491 &DB2_CONNECT_EVENT,
492 serde_json::json!({
493 "user_id": user_id,
494 "rdb_name": rdb_name,
495 "has_password": has_password,
496 }),
497 );
498
499 match call_llm(
500 &self.llm_client,
501 &self.app_state,
502 self.server_id,
503 Some(self.connection_id),
504 &event,
505 self.protocol.as_ref(),
506 )
507 .await
508 {
509 Ok(result) => match first_custom(&result.protocol_results) {
510 Some(("db2_accept_connection", _)) => {
511 self.authenticated = true;
512 info!("Db2 SECCHK decision=model_accept user={user_id} rdb={rdb_name}");
513 let _ = self
514 .status_tx
515 .send(format!("→ Db2 login accepted for {user_id}"));
516 secchkrm(corr, drda::svrcod::INFO, drda::secchkcd::SUCCESS)
517 }
518 Some(("db2_reject_connection", data)) => {
519 let code = match data.get("sec_check_code").and_then(|v| v.as_str()) {
520 Some("userid_unknown") => drda::secchkcd::USERID_UNKNOWN,
521 Some("userid_missing") => drda::secchkcd::USERID_MISSING,
522 _ => drda::secchkcd::PASSWORD_INVALID,
523 };
524 info!("Db2 SECCHK decision=model_reject user={user_id} secchkcd=0x{code:02X}");
525 let _ = self
526 .status_tx
527 .send("↩ Db2 login denied by model".to_string());
528 secchkrm(corr, drda::svrcod::ERROR, code)
529 }
530 _ => {
531 warn!(
534 "Db2 SECCHK decision=fail_closed_no_action user={user_id}: the handler \
535 produced no db2_accept_connection; refusing"
536 );
537 secchkrm(corr, drda::svrcod::ERROR, drda::secchkcd::PASSWORD_INVALID)
538 }
539 },
540 Err(e) => {
541 error!(
547 "Db2 SECCHK decision=fail_closed_llm_error user={user_id}: LLM error: {:#}",
548 e
549 );
550 let _ = self.status_tx.send(format!(
551 "✗ Db2 auth backend error: {}",
552 crate::utils::truncate_for_log(&e.to_string(), 200)
553 ));
554 secchkrm(corr, drda::svrcod::ERROR, drda::secchkcd::PASSWORD_INVALID)
555 }
556 }
557 }
558
559 fn reply_accrdbrm(&self, corr: u16, body: &[u8]) -> Vec<u8> {
565 use drda::cp;
566 if !self.authenticated {
567 warn!("Db2 ACCRDB decision=fail_closed_not_authenticated: answering RDBATHRM");
568 let mut inner = drda::encode_scalar(cp::SVRCOD, &drda::svrcod::ERROR.to_be_bytes());
569 if let Some(rdbnam) = drda::find_param(body, cp::RDBNAM) {
570 inner.extend_from_slice(&drda::encode_scalar(cp::RDBNAM, &rdbnam));
571 }
572 let rm = drda::encode_object(cp::RDBATHRM, &inner);
573 return drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rm);
574 }
575 let severity = drda::svrcod::INFO;
576 let mut inner = Vec::new();
577 inner.extend_from_slice(&drda::encode_scalar(cp::SVRCOD, &severity.to_be_bytes()));
578 inner.extend_from_slice(&drda::encode_scalar_str(cp::PRDID, "SQL11055"));
579 inner.extend_from_slice(&drda::encode_scalar_str(cp::TYPDEFNAM, "QTDSQLASC"));
580 let mut ovr = drda::encode_scalar(cp::CCSIDSBC, &1208u16.to_be_bytes());
582 ovr.extend_from_slice(&drda::encode_scalar(cp::CCSIDMBC, &1208u16.to_be_bytes()));
583 inner.extend_from_slice(&drda::encode_scalar(cp::TYPDEFOVR, &ovr));
584 let rm = drda::encode_object(cp::ACCRDBRM, &inner);
585 drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rm)
586 }
587
588 async fn handle_query(&mut self, corr: u16, sql: String) -> Vec<u8> {
590 if !self.authenticated {
591 warn!(
592 "Db2 statement decision=fail_closed_not_authenticated: a statement arrived \
593 before an accepted SECCHK; replying SQLCODE -30082"
594 );
595 let card = drda::sqlcard_error(-30082, "08001");
596 return drda::encode_dss(drda::DSSFMT_OBJDSS, false, corr, &card);
597 }
598
599 let event = Event::new(
600 &DB2_QUERY_EVENT,
601 serde_json::json!({
602 "sql_text": sql,
603 "statement_type": "execute_immediate",
604 }),
605 );
606
607 match call_llm(
608 &self.llm_client,
609 &self.app_state,
610 self.server_id,
611 Some(self.connection_id),
612 &event,
613 self.protocol.as_ref(),
614 )
615 .await
616 {
617 Ok(result) => {
618 let card = match first_custom(&result.protocol_results) {
619 Some(("db2_query_ok", data)) => {
620 let sqlcode = data.get("sqlcode").and_then(|v| v.as_i64()).unwrap_or(0);
621 debug!("Db2 statement decision=model_answer sqlcode={sqlcode}");
622 let rows = data.get("rows_affected").and_then(|v| v.as_i64());
623 if let (0, Some(rows)) = (sqlcode, rows) {
624 drda::sqlcard(0, "00000", rows.clamp(0, i32::MAX as i64) as i32)
626 } else if sqlcode == 0 {
627 drda::sqlcard_success()
628 } else if sqlcode > 0 {
629 drda::sqlcard_error(sqlcode as i32, "02000")
631 } else {
632 drda::sqlcard_error(sqlcode as i32, "42000")
633 }
634 }
635 Some(("db2_query_error", data)) => {
636 let sqlcode =
637 data.get("sqlcode").and_then(|v| v.as_i64()).unwrap_or(-104) as i32;
638 let sqlstate = data
639 .get("sqlstate")
640 .and_then(|v| v.as_str())
641 .unwrap_or("42601");
642 debug!(
643 "Db2 statement decision=model_reject sqlcode={sqlcode} \
644 sqlstate={sqlstate}"
645 );
646 drda::sqlcard_error(sqlcode, sqlstate)
647 }
648 _ => {
649 warn!(
652 "Db2 statement decision=fail_closed_no_action: the handler produced \
653 no db2_query_ok or db2_query_error; returning SQLCODE -901"
654 );
655 drda::sqlcard_error(-901, "58004")
656 }
657 };
658 drda::encode_dss(drda::DSSFMT_OBJDSS, false, corr, &card)
659 }
660 Err(e) => {
661 error!(
665 "Db2 statement decision=fail_closed_llm_error: LLM error: {:#}",
666 e
667 );
668 let _ = self.status_tx.send(format!(
669 "✗ Db2 query backend error: {}",
670 crate::utils::truncate_for_log(&e.to_string(), 200)
671 ));
672 let card = drda::sqlcard_error(-901, "58004");
673 drda::encode_dss(drda::DSSFMT_OBJDSS, false, corr, &card)
674 }
675 }
676 }
677}
678
679fn cmdnsprm(corr: u16, codepoint: u16) -> Vec<u8> {
682 use drda::cp;
683 warn!("Db2: unsupported DRDA command 0x{:04X}", codepoint);
684 let mut inner = drda::encode_scalar(cp::SVRCOD, &drda::svrcod::ERROR.to_be_bytes());
685 inner.extend_from_slice(&drda::encode_scalar(0x000C, &codepoint.to_be_bytes()));
686 let rm = drda::encode_object(cp::CMDNSPRM, &inner);
687 drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rm)
688}
689
690fn end_unit_of_work(corr: u16, uowdsp: u8) -> Vec<u8> {
694 use drda::cp;
695 let mut inner = drda::encode_scalar(cp::SVRCOD, &drda::svrcod::WARNING.to_be_bytes());
696 inner.extend_from_slice(&drda::encode_scalar(cp::UOWDSP, &[uowdsp]));
697 let rm = drda::encode_object(cp::ENDUOWRM, &inner);
698 let mut out = drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rm);
699 out.extend(drda::encode_dss(
700 drda::DSSFMT_OBJDSS,
701 false,
702 corr,
703 &drda::sqlcard_success(),
704 ));
705 out
706}
707
708fn secchkrm(corr: u16, svrcod: u16, secchkcd: u8) -> Vec<u8> {
711 use drda::cp;
712 let mut inner = Vec::new();
713 inner.extend_from_slice(&drda::encode_scalar(cp::SVRCOD, &svrcod.to_be_bytes()));
714 inner.extend_from_slice(&drda::encode_scalar(cp::SECCHKCD, &[secchkcd]));
715 let rm = drda::encode_object(cp::SECCHKRM, &inner);
716 drda::encode_dss(drda::DSSFMT_RPYDSS, false, corr, &rm)
717}
718
719fn first_custom(results: &[ActionResult]) -> Option<(&str, &serde_json::Value)> {
721 results.iter().find_map(|r| match r {
722 ActionResult::Custom { name, data } => Some((name.as_str(), data)),
723 _ => None,
724 })
725}