1use bytes::{BufMut, Bytes, BytesMut};
19
20use crate::codec::write_utf16_string;
21use crate::version::TdsVersion;
22
23pub const LOGIN7_HEADER_SIZE: usize = 94;
25
26#[derive(Debug, Clone, Copy, Default)]
28pub struct OptionFlags1 {
29 pub byte_order_be: bool,
31 pub char_ebcdic: bool,
33 pub float_ieee: bool,
35 pub dump_load_off: bool,
37 pub use_db_notify: bool,
39 pub database_fatal: bool,
41 pub set_lang_warn: bool,
43}
44
45impl OptionFlags1 {
46 #[must_use]
57 pub fn to_byte(&self) -> u8 {
58 let mut flags = 0u8;
59 if self.byte_order_be {
60 flags |= 0x01; }
62 if self.char_ebcdic {
63 flags |= 0x02; }
65 if self.dump_load_off {
68 flags |= 0x10; }
70 if self.use_db_notify {
71 flags |= 0x20; }
73 if self.database_fatal {
74 flags |= 0x40; }
76 if self.set_lang_warn {
77 flags |= 0x80; }
79 flags
80 }
81}
82
83#[derive(Debug, Clone, Copy, Default)]
85pub struct OptionFlags2 {
86 pub language_fatal: bool,
88 pub odbc: bool,
90 pub tran_boundary: bool,
92 pub cache_connect: bool,
94 pub user_type: u8,
96 pub integrated_security: bool,
98}
99
100impl OptionFlags2 {
101 #[must_use]
103 pub fn to_byte(&self) -> u8 {
104 let mut flags = 0u8;
105 if self.language_fatal {
106 flags |= 0x01;
107 }
108 if self.odbc {
109 flags |= 0x02;
110 }
111 if self.tran_boundary {
112 flags |= 0x04;
113 }
114 if self.cache_connect {
115 flags |= 0x08;
116 }
117 flags |= (self.user_type & 0x07) << 4;
118 if self.integrated_security {
119 flags |= 0x80;
120 }
121 flags
122 }
123}
124
125#[derive(Debug, Clone, Copy, Default)]
127pub struct TypeFlags {
128 pub sql_type: u8,
130 pub oledb: bool,
132 pub read_only_intent: bool,
134}
135
136impl TypeFlags {
137 #[must_use]
139 pub fn to_byte(&self) -> u8 {
140 let mut flags = 0u8;
141 flags |= self.sql_type & 0x0F;
142 if self.oledb {
143 flags |= 0x10;
144 }
145 if self.read_only_intent {
146 flags |= 0x20;
147 }
148 flags
149 }
150}
151
152#[derive(Debug, Clone, Copy, Default)]
154pub struct OptionFlags3 {
155 pub change_password: bool,
157 pub user_instance: bool,
159 pub send_yukon_binary_xml: bool,
161 pub unknown_collation_handling: bool,
163 pub extension: bool,
165}
166
167impl OptionFlags3 {
168 #[must_use]
170 pub fn to_byte(&self) -> u8 {
171 let mut flags = 0u8;
172 if self.change_password {
173 flags |= 0x01;
174 }
175 if self.user_instance {
176 flags |= 0x02;
177 }
178 if self.send_yukon_binary_xml {
179 flags |= 0x04;
180 }
181 if self.unknown_collation_handling {
182 flags |= 0x08;
183 }
184 if self.extension {
185 flags |= 0x10;
186 }
187 flags
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193#[repr(u8)]
194pub enum FeatureId {
195 SessionRecovery = 0x01,
197 FedAuth = 0x02,
199 ColumnEncryption = 0x04,
201 GlobalTransactions = 0x05,
203 AzureSqlSupport = 0x08,
205 DataClassification = 0x09,
207 Utf8Support = 0x0A,
209 AzureSqlDnsCaching = 0x0B,
211 Terminator = 0xFF,
213}
214
215#[derive(Debug, Clone)]
217pub struct Login7 {
218 pub tds_version: TdsVersion,
220 pub packet_size: u32,
222 pub client_prog_version: u32,
224 pub client_pid: u32,
226 pub connection_id: u32,
228 pub option_flags1: OptionFlags1,
230 pub option_flags2: OptionFlags2,
232 pub type_flags: TypeFlags,
234 pub option_flags3: OptionFlags3,
236 pub client_timezone: i32,
238 pub client_lcid: u32,
240 pub hostname: String,
242 pub username: String,
244 pub password: String,
246 pub app_name: String,
248 pub server_name: String,
250 pub unused: String,
252 pub library_name: String,
254 pub language: String,
256 pub database: String,
258 pub client_id: [u8; 6],
260 pub sspi_data: Vec<u8>,
262 pub attach_db_file: String,
264 pub new_password: String,
266 pub features: Vec<FeatureExtension>,
268}
269
270#[derive(Debug, Clone)]
272pub struct FeatureExtension {
273 pub feature_id: FeatureId,
275 pub data: Bytes,
277}
278
279impl Default for Login7 {
280 fn default() -> Self {
281 #[cfg(feature = "std")]
282 let client_pid = std::process::id();
283 #[cfg(not(feature = "std"))]
284 let client_pid = 0;
285
286 Self {
287 tds_version: TdsVersion::V7_4,
288 packet_size: 4096,
289 client_prog_version: 0,
290 client_pid,
291 connection_id: 0,
292 option_flags1: OptionFlags1 {
294 use_db_notify: true,
295 database_fatal: true,
296 ..Default::default()
297 },
298 option_flags2: OptionFlags2 {
299 language_fatal: true,
300 odbc: true,
301 ..Default::default()
302 },
303 type_flags: TypeFlags::default(), option_flags3: OptionFlags3 {
305 unknown_collation_handling: true,
306 ..Default::default()
307 },
308 client_timezone: 0,
309 client_lcid: 0x0409, hostname: String::new(),
311 username: String::new(),
312 password: String::new(),
313 app_name: String::from("rust-mssql-driver"),
314 server_name: String::new(),
315 unused: String::new(),
316 library_name: String::from("rust-mssql-driver"),
317 language: String::new(),
318 database: String::new(),
319 client_id: [0u8; 6],
320 sspi_data: Vec::new(),
321 attach_db_file: String::new(),
322 new_password: String::new(),
323 features: Vec::new(),
324 }
325 }
326}
327
328impl Login7 {
329 #[must_use]
331 pub fn new() -> Self {
332 Self::default()
333 }
334
335 #[must_use]
337 pub fn with_tds_version(mut self, version: TdsVersion) -> Self {
338 self.tds_version = version;
339 self
340 }
341
342 #[must_use]
344 pub fn with_sql_auth(
345 mut self,
346 username: impl Into<String>,
347 password: impl Into<String>,
348 ) -> Self {
349 self.username = username.into();
350 self.password = password.into();
351 self.option_flags2.integrated_security = false;
352 self
353 }
354
355 #[must_use]
357 pub fn with_integrated_auth(mut self, sspi_data: Vec<u8>) -> Self {
358 self.sspi_data = sspi_data;
359 self.option_flags2.integrated_security = true;
360 self
361 }
362
363 #[must_use]
365 pub fn with_database(mut self, database: impl Into<String>) -> Self {
366 self.database = database.into();
367 self
368 }
369
370 #[must_use]
372 pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
373 self.hostname = hostname.into();
374 self
375 }
376
377 #[must_use]
379 pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
380 self.app_name = app_name.into();
381 self
382 }
383
384 #[must_use]
386 pub fn with_server_name(mut self, server_name: impl Into<String>) -> Self {
387 self.server_name = server_name.into();
388 self
389 }
390
391 #[must_use]
393 pub fn with_packet_size(mut self, packet_size: u32) -> Self {
394 self.packet_size = packet_size;
395 self
396 }
397
398 #[must_use]
400 pub fn with_read_only_intent(mut self, read_only: bool) -> Self {
401 self.type_flags.read_only_intent = read_only;
402 self
403 }
404
405 #[must_use]
407 pub fn with_feature(mut self, feature: FeatureExtension) -> Self {
408 self.option_flags3.extension = true;
409 self.features.push(feature);
410 self
411 }
412
413 #[must_use]
415 pub fn encode(&self) -> Bytes {
416 let mut buf = BytesMut::with_capacity(512);
417
418 let mut offset = LOGIN7_HEADER_SIZE as u16;
421
422 let hostname_len = self.hostname.encode_utf16().count() as u16;
424 let username_len = self.username.encode_utf16().count() as u16;
425 let password_len = self.password.encode_utf16().count() as u16;
426 let app_name_len = self.app_name.encode_utf16().count() as u16;
427 let server_name_len = self.server_name.encode_utf16().count() as u16;
428 let unused_len = self.unused.encode_utf16().count() as u16;
429 let library_name_len = self.library_name.encode_utf16().count() as u16;
430 let language_len = self.language.encode_utf16().count() as u16;
431 let database_len = self.database.encode_utf16().count() as u16;
432 let sspi_len = self.sspi_data.len() as u16;
433 let attach_db_len = self.attach_db_file.encode_utf16().count() as u16;
434 let new_password_len = self.new_password.encode_utf16().count() as u16;
435
436 let mut var_data = BytesMut::new();
438
439 let hostname_offset = offset;
441 write_utf16_string(&mut var_data, &self.hostname);
442 offset += hostname_len * 2;
443
444 let username_offset = offset;
446 write_utf16_string(&mut var_data, &self.username);
447 offset += username_len * 2;
448
449 let password_offset = offset;
451 Self::write_obfuscated_password(&mut var_data, &self.password);
452 offset += password_len * 2;
453
454 let app_name_offset = offset;
456 write_utf16_string(&mut var_data, &self.app_name);
457 offset += app_name_len * 2;
458
459 let server_name_offset = offset;
461 write_utf16_string(&mut var_data, &self.server_name);
462 offset += server_name_len * 2;
463
464 let extension_offset = if self.option_flags3.extension {
466 let base = offset
468 + unused_len * 2
469 + library_name_len * 2
470 + language_len * 2
471 + database_len * 2
472 + sspi_len
473 + attach_db_len * 2
474 + new_password_len * 2;
475 var_data.put_u32_le(base as u32);
477 offset += 4;
478 base
479 } else {
480 let unused_offset = offset;
481 write_utf16_string(&mut var_data, &self.unused);
482 offset += unused_len * 2;
483 unused_offset
484 };
485
486 let library_name_offset = offset;
488 write_utf16_string(&mut var_data, &self.library_name);
489 offset += library_name_len * 2;
490
491 let language_offset = offset;
493 write_utf16_string(&mut var_data, &self.language);
494 offset += language_len * 2;
495
496 let database_offset = offset;
498 write_utf16_string(&mut var_data, &self.database);
499 offset += database_len * 2;
500
501 let sspi_offset = offset;
506 var_data.put_slice(&self.sspi_data);
507 offset += sspi_len;
508
509 let attach_db_offset = offset;
511 write_utf16_string(&mut var_data, &self.attach_db_file);
512 offset += attach_db_len * 2;
513
514 let new_password_offset = offset;
516 if !self.new_password.is_empty() {
517 Self::write_obfuscated_password(&mut var_data, &self.new_password);
518 }
519 #[allow(unused_assignments)]
520 {
521 offset += new_password_len * 2;
522 }
523
524 if self.option_flags3.extension {
526 for feature in &self.features {
527 var_data.put_u8(feature.feature_id as u8);
528 var_data.put_u32_le(feature.data.len() as u32);
529 var_data.put_slice(&feature.data);
530 }
531 var_data.put_u8(FeatureId::Terminator as u8);
532 }
533
534 let total_length = LOGIN7_HEADER_SIZE + var_data.len();
536
537 buf.put_u32_le(total_length as u32); buf.put_u32_le(self.tds_version.raw()); buf.put_u32_le(self.packet_size); buf.put_u32_le(self.client_prog_version); buf.put_u32_le(self.client_pid); buf.put_u32_le(self.connection_id); buf.put_u8(self.option_flags1.to_byte());
547 buf.put_u8(self.option_flags2.to_byte());
548 buf.put_u8(self.type_flags.to_byte());
549 buf.put_u8(self.option_flags3.to_byte());
550
551 buf.put_i32_le(self.client_timezone); buf.put_u32_le(self.client_lcid); buf.put_u16_le(hostname_offset);
556 buf.put_u16_le(hostname_len);
557 buf.put_u16_le(username_offset);
558 buf.put_u16_le(username_len);
559 buf.put_u16_le(password_offset);
560 buf.put_u16_le(password_len);
561 buf.put_u16_le(app_name_offset);
562 buf.put_u16_le(app_name_len);
563 buf.put_u16_le(server_name_offset);
564 buf.put_u16_le(server_name_len);
565
566 if self.option_flags3.extension {
568 buf.put_u16_le(extension_offset as u16);
569 buf.put_u16_le(4); } else {
571 buf.put_u16_le(extension_offset as u16);
572 buf.put_u16_le(unused_len);
573 }
574
575 buf.put_u16_le(library_name_offset);
576 buf.put_u16_le(library_name_len);
577 buf.put_u16_le(language_offset);
578 buf.put_u16_le(language_len);
579 buf.put_u16_le(database_offset);
580 buf.put_u16_le(database_len);
581
582 buf.put_slice(&self.client_id);
584
585 buf.put_u16_le(sspi_offset);
586 buf.put_u16_le(sspi_len);
587 buf.put_u16_le(attach_db_offset);
588 buf.put_u16_le(attach_db_len);
589 buf.put_u16_le(new_password_offset);
590 buf.put_u16_le(new_password_len);
591
592 buf.put_u32_le(0);
594
595 buf.put_slice(&var_data);
597
598 buf.freeze()
599 }
600
601 fn write_obfuscated_password(dst: &mut impl BufMut, password: &str) {
606 for c in password.encode_utf16() {
607 let low = (c & 0xFF) as u8;
608 let high = ((c >> 8) & 0xFF) as u8;
609
610 let low_enc = low.rotate_right(4) ^ 0xA5;
613 let high_enc = high.rotate_right(4) ^ 0xA5;
614
615 dst.put_u8(low_enc);
616 dst.put_u8(high_enc);
617 }
618 }
619}
620
621#[cfg(not(feature = "std"))]
622use alloc::string::String;
623#[cfg(not(feature = "std"))]
624use alloc::vec::Vec;
625
626#[cfg(test)]
627#[allow(clippy::unwrap_used)]
628mod tests {
629 use super::*;
630
631 #[test]
632 fn test_login7_default() {
633 let login = Login7::new();
634 assert_eq!(login.tds_version, TdsVersion::V7_4);
635 assert_eq!(login.packet_size, 4096);
636 assert!(login.option_flags2.odbc);
637 }
638
639 #[test]
640 fn test_login7_encode() {
641 let login = Login7::new()
642 .with_hostname("TESTHOST")
643 .with_sql_auth("testuser", "testpass")
644 .with_database("testdb")
645 .with_app_name("TestApp");
646
647 let encoded = login.encode();
648
649 assert!(encoded.len() >= LOGIN7_HEADER_SIZE);
651
652 let tds_version = u32::from_le_bytes([encoded[4], encoded[5], encoded[6], encoded[7]]);
654 assert_eq!(tds_version, TdsVersion::V7_4.raw());
655 }
656
657 #[test]
658 fn test_password_obfuscation() {
659 let mut buf = BytesMut::new();
661 Login7::write_obfuscated_password(&mut buf, "a");
662
663 assert_eq!(buf.len(), 2);
668 assert_eq!(buf[0], 0xB3);
669 assert_eq!(buf[1], 0xA5);
670 }
671
672 #[test]
673 fn test_option_flags() {
674 let flags1 = OptionFlags1::default();
675 assert_eq!(flags1.to_byte(), 0x00);
676
677 let flags2 = OptionFlags2 {
678 odbc: true,
679 integrated_security: true,
680 ..Default::default()
681 };
682 assert_eq!(flags2.to_byte(), 0x82);
683
684 let flags3 = OptionFlags3 {
685 extension: true,
686 ..Default::default()
687 };
688 assert_eq!(flags3.to_byte(), 0x10);
689 }
690}