1#![forbid(unsafe_code)]
2
3use super::*;
4use crate::wire::ProtocolLimits;
5
6pub fn append_auth_phase_one(
7 out: &mut Vec<u8>,
8 user: &str,
9 program: &str,
10 machine: &str,
11 osuser: &str,
12 terminal: &str,
13 pid: u32,
14) -> Result<()> {
15 let mut writer = TtcWriter::new();
16 writer.write_function_code(TNS_FUNC_AUTH_PHASE_ONE);
17 write_auth_header(&mut writer, user, TNS_AUTH_MODE_LOGON, 5)?;
18 write_key_value(&mut writer, "AUTH_TERMINAL", terminal, 0)?;
19 write_key_value(&mut writer, "AUTH_PROGRAM_NM", program, 0)?;
20 write_key_value(&mut writer, "AUTH_MACHINE", machine, 0)?;
21 write_key_value(&mut writer, "AUTH_PID", &pid.to_string(), 0)?;
22 write_key_value(&mut writer, "AUTH_SID", osuser, 0)?;
23 out.extend_from_slice(&writer.into_bytes());
24 Ok(())
25}
26
27#[derive(Clone, Copy, Debug)]
37pub struct TokenPop<'a> {
38 pub header: &'a str,
39 pub signature: &'a str,
40}
41
42pub fn append_auth_phase_two_token(
52 out: &mut Vec<u8>,
53 user: &str,
54 token: &str,
55 driver_name: &str,
56 version_num: u32,
57 connect_string: &str,
58 edition: Option<&str>,
59) -> Result<()> {
60 append_auth_phase_two_token_with_pop(
61 out,
62 user,
63 token,
64 driver_name,
65 version_num,
66 connect_string,
67 edition,
68 None,
69 )
70}
71
72#[allow(clippy::too_many_arguments)] pub fn append_auth_phase_two_token_with_pop(
78 out: &mut Vec<u8>,
79 user: &str,
80 token: &str,
81 driver_name: &str,
82 version_num: u32,
83 connect_string: &str,
84 edition: Option<&str>,
85 pop: Option<TokenPop<'_>>,
86) -> Result<()> {
87 append_auth_phase_two_token_with_pop_and_proxy(
88 out,
89 user,
90 token,
91 driver_name,
92 version_num,
93 connect_string,
94 edition,
95 pop,
96 None,
97 )
98}
99
100#[allow(clippy::too_many_arguments)] pub fn append_auth_phase_two_token_with_pop_and_proxy(
112 out: &mut Vec<u8>,
113 user: &str,
114 token: &str,
115 driver_name: &str,
116 version_num: u32,
117 connect_string: &str,
118 edition: Option<&str>,
119 pop: Option<TokenPop<'_>>,
120 proxy_user: Option<&str>,
121) -> Result<()> {
122 let mut writer = TtcWriter::new();
123 writer.write_function_code(TNS_FUNC_AUTH_PHASE_TWO);
124 let mut num_pairs = 5u32;
128 let auth_mode = if pop.is_some() {
129 num_pairs += 2;
131 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_IAM_TOKEN
132 } else {
133 TNS_AUTH_MODE_LOGON
134 };
135 if proxy_user.is_some() {
136 num_pairs += 1;
137 }
138 if edition.is_some() {
139 num_pairs += 1;
140 }
141 if !connect_string.is_empty() {
142 num_pairs += 1;
143 }
144 write_auth_header(&mut writer, user, auth_mode, num_pairs)?;
145 if let Some(proxy_user) = proxy_user {
147 write_key_value(&mut writer, "PROXY_CLIENT_NAME", proxy_user, 0)?;
148 }
149 write_key_value(&mut writer, "AUTH_TOKEN", token, 0)?;
150 write_key_value(&mut writer, "SESSION_CLIENT_CHARSET", "873", 0)?;
151 write_key_value(&mut writer, "SESSION_CLIENT_DRIVER_NAME", driver_name, 0)?;
152 write_key_value(
153 &mut writer,
154 "SESSION_CLIENT_VERSION",
155 &version_num.to_string(),
156 0,
157 )?;
158 write_key_value(
159 &mut writer,
160 "AUTH_ALTER_SESSION",
161 "ALTER SESSION SET TIME_ZONE='+00:00'\0",
162 1,
163 )?;
164 if let Some(pop) = pop {
168 write_key_value(&mut writer, "AUTH_HEADER", pop.header, 0)?;
169 write_key_value(&mut writer, "AUTH_SIGNATURE", pop.signature, 0)?;
170 }
171 if let Some(edition) = edition {
176 write_key_value(&mut writer, "AUTH_ORA_EDITION", edition, 0)?;
177 }
178 if !connect_string.is_empty() {
179 write_key_value(&mut writer, "AUTH_CONNECT_STRING", connect_string, 0)?;
180 }
181 out.extend_from_slice(&writer.into_bytes());
182 Ok(())
183}
184
185pub fn build_auth_phase_two_payload(
186 user: &str,
187 encrypted: &crate::crypto::EncryptedPassword,
188 driver_name: &str,
189 version_num: u32,
190 connect_string: &str,
191) -> Result<Vec<u8>> {
192 build_auth_phase_two_payload_with_seq(
193 user,
194 encrypted,
195 driver_name,
196 version_num,
197 connect_string,
198 1,
199 )
200}
201
202pub fn build_auth_phase_two_payload_with_seq(
203 user: &str,
204 encrypted: &crate::crypto::EncryptedPassword,
205 driver_name: &str,
206 version_num: u32,
207 connect_string: &str,
208 seq_num: u8,
209) -> Result<Vec<u8>> {
210 build_auth_phase_two_payload_with_context_with_seq(
211 user,
212 encrypted,
213 driver_name,
214 version_num,
215 connect_string,
216 seq_num,
217 &[],
218 )
219}
220
221pub fn build_auth_phase_two_payload_with_context_with_seq(
222 user: &str,
223 encrypted: &crate::crypto::EncryptedPassword,
224 driver_name: &str,
225 version_num: u32,
226 connect_string: &str,
227 seq_num: u8,
228 app_context: &[(String, String, String)],
229) -> Result<Vec<u8>> {
230 build_auth_phase_two_payload_with_proxy_with_seq(
231 user,
232 encrypted,
233 driver_name,
234 version_num,
235 connect_string,
236 seq_num,
237 app_context,
238 None,
239 None,
240 ClientCapabilities::default().ttc_field_version,
241 )
242}
243
244#[allow(clippy::too_many_arguments)]
254pub fn build_auth_phase_two_payload_with_proxy_with_seq(
255 user: &str,
256 encrypted: &crate::crypto::EncryptedPassword,
257 driver_name: &str,
258 version_num: u32,
259 connect_string: &str,
260 seq_num: u8,
261 app_context: &[(String, String, String)],
262 proxy_user: Option<&str>,
263 edition: Option<&str>,
264 ttc_field_version: u8,
265) -> Result<Vec<u8>> {
266 let mut writer = TtcWriter::new();
267 writer.write_function_code_with_seq(TNS_FUNC_AUTH_PHASE_TWO, seq_num);
268 if version_gates::writes_pipeline_token(ttc_field_version) {
269 writer.write_ub8(0);
270 }
271 let mut num_pairs = 6u32;
272 if encrypted.speedy_key.is_some() {
273 num_pairs += 1;
274 }
275 if proxy_user.is_some() {
276 num_pairs += 1;
277 }
278 if !connect_string.is_empty() {
279 num_pairs += 1;
280 }
281 if edition.is_some() {
282 num_pairs += 1;
283 }
284 let app_context_pairs =
285 app_context
286 .len()
287 .checked_mul(3)
288 .ok_or(ProtocolError::InvalidPacketLength {
289 length: app_context.len(),
290 minimum: 0,
291 })?;
292 num_pairs +=
293 u32::try_from(app_context_pairs).map_err(|_| ProtocolError::InvalidPacketLength {
294 length: app_context.len(),
295 minimum: 0,
296 })?;
297 write_auth_header(
298 &mut writer,
299 user,
300 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_WITH_PASSWORD,
301 num_pairs,
302 )?;
303 if let Some(proxy_user) = proxy_user {
304 write_key_value(&mut writer, "PROXY_CLIENT_NAME", proxy_user, 0)?;
305 }
306 write_key_value(&mut writer, "AUTH_SESSKEY", &encrypted.session_key, 1)?;
307 if let Some(speedy_key) = &encrypted.speedy_key {
308 write_key_value(&mut writer, "AUTH_PBKDF2_SPEEDY_KEY", speedy_key, 0)?;
309 }
310 write_key_value(&mut writer, "AUTH_PASSWORD", &encrypted.password, 0)?;
311 write_key_value(&mut writer, "SESSION_CLIENT_CHARSET", "873", 0)?;
312 write_key_value(&mut writer, "SESSION_CLIENT_DRIVER_NAME", driver_name, 0)?;
313 write_key_value(
314 &mut writer,
315 "SESSION_CLIENT_VERSION",
316 &version_num.to_string(),
317 0,
318 )?;
319 write_key_value(
320 &mut writer,
321 "AUTH_ALTER_SESSION",
322 "ALTER SESSION SET TIME_ZONE='+00:00'\0",
323 1,
324 )?;
325 if let Some(edition) = edition {
329 write_key_value(&mut writer, "AUTH_ORA_EDITION", edition, 0)?;
330 }
331 for (namespace, name, value) in app_context {
332 write_key_value(&mut writer, "AUTH_APPCTX_NSPACE\0", namespace, 0)?;
333 write_key_value(&mut writer, "AUTH_APPCTX_ATTR\0", name, 0)?;
334 write_key_value(&mut writer, "AUTH_APPCTX_VALUE\0", value, 0)?;
335 }
336 if !connect_string.is_empty() {
337 write_key_value(&mut writer, "AUTH_CONNECT_STRING", connect_string, 0)?;
338 }
339 Ok(writer.into_bytes())
340}
341
342pub fn build_change_password_payload_with_seq(
347 user: &str,
348 encoded_password: &str,
349 encoded_newpassword: &str,
350 seq_num: u8,
351 ttc_field_version: u8,
352) -> Result<Vec<u8>> {
353 let mut writer = TtcWriter::new();
354 writer.write_function_code_with_seq(TNS_FUNC_AUTH_PHASE_TWO, seq_num);
355 if version_gates::writes_pipeline_token(ttc_field_version) {
356 writer.write_ub8(0);
357 }
358 write_auth_header(
359 &mut writer,
360 user,
361 TNS_AUTH_MODE_WITH_PASSWORD | TNS_AUTH_MODE_CHANGE_PASSWORD,
362 2,
363 )?;
364 write_key_value(&mut writer, "AUTH_PASSWORD", encoded_password, 0)?;
365 write_key_value(&mut writer, "AUTH_NEWPASSWORD", encoded_newpassword, 0)?;
366 Ok(writer.into_bytes())
367}
368
369pub fn parse_auth_response(payload: &[u8]) -> Result<AuthResponse> {
370 parse_auth_response_with_limits(payload, ProtocolLimits::DEFAULT)
371}
372
373pub fn classic_connect_response_is_complete(
395 payload: &[u8],
396 limits: ProtocolLimits,
397) -> Result<bool> {
398 let Ok(mut reader) = TtcReader::with_limits(payload, limits) else {
399 return Ok(false);
400 };
401 while reader.remaining() > 0 {
402 let message_type = reader.read_u8()?;
403 let terminal = match message_type {
404 TNS_MSG_TYPE_PROTOCOL => match skip_protocol_message(&mut reader) {
405 Ok(_) => true,
406 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
407 Err(err) => return Err(err),
408 },
409 TNS_MSG_TYPE_DATA_TYPES => match skip_data_types_response(&mut reader) {
410 Ok(()) => true,
411 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
412 Err(err) => return Err(err),
413 },
414 TNS_MSG_TYPE_PARAMETER => match parse_return_parameters(&mut reader) {
415 Ok(_) => false,
416 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
417 Err(err) => return Err(err),
418 },
419 TNS_MSG_TYPE_STATUS => {
420 let complete = reader.read_ub4().and_then(|_| reader.read_ub2());
421 match complete {
422 Ok(_) => true,
423 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
424 Err(err) => return Err(err),
425 }
426 }
427 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => match skip_server_side_piggyback(&mut reader) {
428 Ok(_) => false,
429 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
430 Err(err) => return Err(err),
431 },
432 TNS_MSG_TYPE_END_OF_RESPONSE => true,
433 TNS_MSG_TYPE_ERROR => match parse_server_error(&mut reader, 13) {
434 Ok(_) | Err(ProtocolError::ServerError(_)) => true,
435 Err(ProtocolError::TtcDecode(_)) => return Ok(false),
436 Err(err) => return Err(err),
437 },
438 _ => {
439 return Err(ProtocolError::UnknownMessageType {
440 message_type,
441 position: reader.position().saturating_sub(1),
442 })
443 }
444 };
445 if terminal {
446 return Ok(true);
447 }
448 }
449 Ok(false)
450}
451
452pub fn parse_auth_response_with_limits(
453 payload: &[u8],
454 limits: ProtocolLimits,
455) -> Result<AuthResponse> {
456 let mut reader = TtcReader::with_limits(payload, limits)?;
457 let mut response = AuthResponse::default();
458 while reader.remaining() > 0 {
459 let message_type = reader.read_u8()?;
460 match message_type {
461 TNS_MSG_TYPE_PROTOCOL => {
462 if let Some(capabilities) = skip_protocol_message(&mut reader)? {
463 response.capabilities = Some(capabilities);
464 }
465 }
466 TNS_MSG_TYPE_DATA_TYPES => skip_data_types_response(&mut reader)?,
467 TNS_MSG_TYPE_PARAMETER => {
468 let mut parsed = parse_return_parameters(&mut reader)?;
469 response.session_data.append(&mut parsed.session_data);
470 if parsed.verifier_type.is_some() {
471 response.verifier_type = parsed.verifier_type;
472 }
473 }
474 TNS_MSG_TYPE_STATUS => {
475 let _call_status = reader.read_ub4()?;
476 let _seq = reader.read_ub2()?;
477 }
478 TNS_MSG_TYPE_SERVER_SIDE_PIGGYBACK => {
479 let _ = skip_server_side_piggyback(&mut reader)?;
480 }
481 TNS_MSG_TYPE_END_OF_RESPONSE => break,
482 TNS_MSG_TYPE_ERROR => {
483 if let Some(message) = parse_server_error(&mut reader, 13)? {
484 return Err(ProtocolError::ServerError(message));
485 }
486 }
487 _ => {
488 return Err(ProtocolError::UnknownMessageType {
489 message_type,
490 position: reader.position().saturating_sub(1),
491 })
492 }
493 }
494 }
495 Ok(response)
496}
497
498pub(crate) fn write_auth_header(
499 writer: &mut TtcWriter,
500 user: &str,
501 auth_mode: u32,
502 num_pairs: u32,
503) -> Result<()> {
504 let user_bytes = user.as_bytes();
505 writer.write_u8(u8::from(!user_bytes.is_empty()));
506 writer.write_ub4(u32::try_from(user_bytes.len()).map_err(|_| {
507 ProtocolError::InvalidPacketLength {
508 length: user_bytes.len(),
509 minimum: 0,
510 }
511 })?);
512 writer.write_ub4(auth_mode);
513 writer.write_u8(1);
514 writer.write_ub4(num_pairs);
515 writer.write_u8(1);
516 writer.write_u8(1);
517 if !user_bytes.is_empty() {
518 writer.write_bytes_with_length(user_bytes)?;
519 }
520 Ok(())
521}
522
523pub(crate) fn write_key_value(
524 writer: &mut TtcWriter,
525 key: &str,
526 value: &str,
527 flags: u32,
528) -> Result<()> {
529 writer.write_str_two_lengths(key)?;
530 writer.write_str_two_lengths(value)?;
531 writer.write_ub4(flags);
532 Ok(())
533}
534
535pub(crate) fn parse_return_parameters(reader: &mut TtcReader<'_>) -> Result<AuthResponse> {
536 let num_params = reader.read_ub2()?;
537 reader
538 .limits()
539 .check_length_prefixed_elements(usize::from(num_params))?;
540 let mut response = AuthResponse::default();
541 for _ in 0..num_params {
542 let key = reader
543 .read_string_with_length()?
544 .ok_or(ProtocolError::TtcDecode("missing auth response key"))?;
545 let value = reader.read_string_with_length()?.unwrap_or_default();
546 if key == "AUTH_VFR_DATA" {
547 response.verifier_type = Some(reader.read_ub4()?);
548 } else {
549 let _flags = reader.read_ub4()?;
550 }
551 response.session_data.insert(key, value);
552 }
553 Ok(response)
554}
555
556#[cfg(test)]
557mod token_auth_tests {
558 use super::*;
559
560 fn read_ub4(bytes: &[u8], pos: &mut usize) -> u32 {
562 let len = bytes[*pos] as usize;
563 *pos += 1;
564 let mut value = 0u32;
565 for _ in 0..len {
566 value = (value << 8) | u32::from(bytes[*pos]);
567 *pos += 1;
568 }
569 value
570 }
571
572 fn contains(haystack: &[u8], needle: &[u8]) -> bool {
573 haystack.windows(needle.len()).any(|w| w == needle)
574 }
575
576 #[test]
581 fn token_message_carries_auth_token_not_password() {
582 let mut out = Vec::new();
583 append_auth_phase_two_token(
584 &mut out,
585 "scott",
586 "HEADER.PAYLOAD.SIG",
587 "drv",
588 300_000_000,
589 "cs",
590 None,
591 )
592 .unwrap();
593
594 assert_eq!(out[0], TNS_MSG_TYPE_FUNCTION);
596 assert_eq!(out[1], TNS_FUNC_AUTH_PHASE_TWO);
597 assert_eq!(out[3], 1, "user is present");
599 let mut pos = 4;
600 assert_eq!(read_ub4(&out, &mut pos), 5, "user length = len(\"scott\")");
601 assert_eq!(
602 read_ub4(&out, &mut pos),
603 TNS_AUTH_MODE_LOGON,
604 "token auth uses LOGON only — never the WITH_PASSWORD bit"
605 );
606 assert_eq!(out[pos], 1); pos += 1;
608 assert_eq!(
609 read_ub4(&out, &mut pos),
610 6,
611 "AUTH_TOKEN + 4 session pairs + AUTH_CONNECT_STRING"
612 );
613
614 assert!(contains(&out, b"AUTH_TOKEN"));
615 assert!(
616 contains(&out, b"HEADER.PAYLOAD.SIG"),
617 "the token value is sent"
618 );
619 assert!(contains(&out, b"AUTH_CONNECT_STRING"));
620 assert!(
621 !contains(&out, b"AUTH_PASSWORD") && !contains(&out, b"AUTH_SESSKEY"),
622 "token auth must not send any password material"
623 );
624 }
625
626 #[test]
629 fn token_message_pair_count_without_connect_string() {
630 let mut out = Vec::new();
631 append_auth_phase_two_token(&mut out, "u", "tok", "drv", 1, "", None).unwrap();
632 let mut pos = 4;
633 let _user_len = read_ub4(&out, &mut pos);
634 let _auth_mode = read_ub4(&out, &mut pos);
635 pos += 1; assert_eq!(read_ub4(&out, &mut pos), 5, "AUTH_TOKEN + 4 session pairs");
637 assert!(!contains(&out, b"AUTH_CONNECT_STRING"));
638 }
639
640 #[test]
645 fn token_message_carries_iam_proof_of_possession() {
646 let mut out = Vec::new();
647 append_auth_phase_two_token_with_pop(
648 &mut out,
649 "OMCP_IAM",
650 "HEADER.PAYLOAD.SIG",
651 "drv",
652 1,
653 "",
654 None,
655 Some(TokenPop {
656 header: "date: Fri, 17 Jul 2026 07:00:00 GMT\n(request-target): svc\nhost: h:1522",
657 signature: "c2lnbmF0dXJl",
658 }),
659 )
660 .unwrap();
661 let mut pos = 4;
662 let _user_len = read_ub4(&out, &mut pos);
663 assert_eq!(
664 read_ub4(&out, &mut pos),
665 TNS_AUTH_MODE_LOGON | TNS_AUTH_MODE_IAM_TOKEN,
666 "IAM proof-of-possession sets the IAM_TOKEN mode bit alongside LOGON"
667 );
668 pos += 1; assert_eq!(
670 read_ub4(&out, &mut pos),
671 7,
672 "AUTH_TOKEN + 4 session pairs + AUTH_HEADER + AUTH_SIGNATURE"
673 );
674 assert!(contains(&out, b"AUTH_TOKEN"));
675 assert!(contains(&out, b"AUTH_HEADER"));
676 assert!(contains(&out, b"AUTH_SIGNATURE"));
677 assert!(
678 contains(&out, b"(request-target): svc"),
679 "header is sent verbatim"
680 );
681 assert!(contains(&out, b"c2lnbmF0dXJl"), "base64 signature is sent");
682 }
683
684 #[test]
688 fn token_message_carries_edition() {
689 let mut out = Vec::new();
690 append_auth_phase_two_token(&mut out, "u", "tok", "drv", 1, "", Some("E_TEST")).unwrap();
691 let mut pos = 4;
692 let _user_len = read_ub4(&out, &mut pos);
693 let _auth_mode = read_ub4(&out, &mut pos);
694 pos += 1; assert_eq!(
696 read_ub4(&out, &mut pos),
697 6,
698 "AUTH_TOKEN + 4 session pairs + AUTH_ORA_EDITION"
699 );
700 assert!(contains(&out, b"AUTH_ORA_EDITION"));
701 assert!(contains(&out, b"E_TEST"), "the edition value is sent");
702
703 let mut out2 = Vec::new();
705 append_auth_phase_two_token(&mut out2, "u", "tok", "drv", 1, "cs", Some("E_TEST")).unwrap();
706 let mut p = 4;
707 let _ = read_ub4(&out2, &mut p);
708 let _ = read_ub4(&out2, &mut p);
709 p += 1;
710 assert_eq!(read_ub4(&out2, &mut p), 7, "+ AUTH_CONNECT_STRING");
711 }
712
713 #[test]
716 fn token_message_proxy_only_writes_proxy_client_name_before_token() {
717 let mut out = Vec::new();
718 append_auth_phase_two_token_with_pop_and_proxy(
719 &mut out,
720 "", "tok",
722 "drv",
723 1,
724 "",
725 None,
726 None,
727 Some("proxy_only"),
728 )
729 .unwrap();
730 assert_eq!(out[3], 0, "has_user must be 0 for empty base user");
732 let mut pos = 4;
733 let user_len = read_ub4(&out, &mut pos);
734 assert_eq!(user_len, 0);
735 let _auth_mode = read_ub4(&out, &mut pos);
736 pos += 1; assert_eq!(
738 read_ub4(&out, &mut pos),
739 6,
740 "PROXY_CLIENT_NAME + AUTH_TOKEN + 4 session pairs"
741 );
742 assert!(contains(&out, b"PROXY_CLIENT_NAME"));
743 assert!(contains(&out, b"proxy_only"));
744 assert!(contains(&out, b"AUTH_TOKEN"));
745 let proxy_at = out
747 .windows(b"PROXY_CLIENT_NAME".len())
748 .position(|w| w == b"PROXY_CLIENT_NAME")
749 .expect("PROXY_CLIENT_NAME present");
750 let token_at = out
751 .windows(b"AUTH_TOKEN".len())
752 .position(|w| w == b"AUTH_TOKEN")
753 .expect("AUTH_TOKEN present");
754 assert!(
755 proxy_at < token_at,
756 "reference order is PROXY_CLIENT_NAME before AUTH_TOKEN"
757 );
758 }
759}