1use bytes::{BufMut, Bytes, BytesMut};
19
20use crate::codec::write_utf16_string;
21use crate::prelude::*;
22use crate::version::TdsVersion;
23
24pub const LOGIN7_HEADER_SIZE: usize = 94;
26
27#[derive(Debug, Clone, Copy, Default)]
29pub struct OptionFlags1 {
30 pub byte_order_be: bool,
32 pub char_ebcdic: bool,
34 pub float_ieee: bool,
36 pub dump_load_off: bool,
38 pub use_db_notify: bool,
40 pub database_fatal: bool,
42 pub set_lang_warn: bool,
44}
45
46impl OptionFlags1 {
47 #[must_use]
58 pub fn to_byte(&self) -> u8 {
59 let mut flags = 0u8;
60 if self.byte_order_be {
61 flags |= 0x01; }
63 if self.char_ebcdic {
64 flags |= 0x02; }
66 if self.dump_load_off {
69 flags |= 0x10; }
71 if self.use_db_notify {
72 flags |= 0x20; }
74 if self.database_fatal {
75 flags |= 0x40; }
77 if self.set_lang_warn {
78 flags |= 0x80; }
80 flags
81 }
82}
83
84#[derive(Debug, Clone, Copy, Default)]
86pub struct OptionFlags2 {
87 pub language_fatal: bool,
89 pub odbc: bool,
91 pub tran_boundary: bool,
93 pub cache_connect: bool,
95 pub user_type: u8,
97 pub integrated_security: bool,
99}
100
101impl OptionFlags2 {
102 #[must_use]
104 pub fn to_byte(&self) -> u8 {
105 let mut flags = 0u8;
106 if self.language_fatal {
107 flags |= 0x01;
108 }
109 if self.odbc {
110 flags |= 0x02;
111 }
112 if self.tran_boundary {
113 flags |= 0x04;
114 }
115 if self.cache_connect {
116 flags |= 0x08;
117 }
118 flags |= (self.user_type & 0x07) << 4;
119 if self.integrated_security {
120 flags |= 0x80;
121 }
122 flags
123 }
124}
125
126#[derive(Debug, Clone, Copy, Default)]
128pub struct TypeFlags {
129 pub sql_type: u8,
131 pub oledb: bool,
133 pub read_only_intent: bool,
135}
136
137impl TypeFlags {
138 #[must_use]
140 pub fn to_byte(&self) -> u8 {
141 let mut flags = 0u8;
142 flags |= self.sql_type & 0x0F;
143 if self.oledb {
144 flags |= 0x10;
145 }
146 if self.read_only_intent {
147 flags |= 0x20;
148 }
149 flags
150 }
151}
152
153#[derive(Debug, Clone, Copy, Default)]
155pub struct OptionFlags3 {
156 pub change_password: bool,
158 pub user_instance: bool,
160 pub send_yukon_binary_xml: bool,
162 pub unknown_collation_handling: bool,
164 pub extension: bool,
166}
167
168impl OptionFlags3 {
169 #[must_use]
171 pub fn to_byte(&self) -> u8 {
172 let mut flags = 0u8;
173 if self.change_password {
174 flags |= 0x01;
175 }
176 if self.user_instance {
177 flags |= 0x02;
178 }
179 if self.send_yukon_binary_xml {
180 flags |= 0x04;
181 }
182 if self.unknown_collation_handling {
183 flags |= 0x08;
184 }
185 if self.extension {
186 flags |= 0x10;
187 }
188 flags
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
194#[repr(u8)]
195pub enum FeatureId {
196 SessionRecovery = 0x01,
198 FedAuth = 0x02,
200 ColumnEncryption = 0x04,
202 GlobalTransactions = 0x05,
204 AzureSqlSupport = 0x08,
206 DataClassification = 0x09,
208 Utf8Support = 0x0A,
210 AzureSqlDnsCaching = 0x0B,
212 Terminator = 0xFF,
214}
215
216#[derive(Debug, Clone)]
218pub struct Login7 {
219 pub tds_version: TdsVersion,
221 pub packet_size: u32,
223 pub client_prog_version: u32,
225 pub client_pid: u32,
227 pub connection_id: u32,
229 pub option_flags1: OptionFlags1,
231 pub option_flags2: OptionFlags2,
233 pub type_flags: TypeFlags,
235 pub option_flags3: OptionFlags3,
237 pub client_timezone: i32,
239 pub client_lcid: u32,
241 pub hostname: String,
243 pub username: String,
245 pub password: String,
247 pub app_name: String,
249 pub server_name: String,
251 pub unused: String,
253 pub library_name: String,
255 pub language: String,
257 pub database: String,
259 pub client_id: [u8; 6],
261 pub sspi_data: Vec<u8>,
263 pub attach_db_file: String,
265 pub new_password: String,
267 pub features: Vec<FeatureExtension>,
269}
270
271#[derive(Debug, Clone)]
273pub struct FeatureExtension {
274 pub feature_id: FeatureId,
276 pub data: Bytes,
278}
279
280impl Default for Login7 {
281 fn default() -> Self {
282 #[cfg(feature = "std")]
283 let client_pid = std::process::id();
284 #[cfg(not(feature = "std"))]
285 let client_pid = 0;
286
287 Self {
288 tds_version: TdsVersion::V7_4,
289 packet_size: 4096,
290 client_prog_version: 0,
291 client_pid,
292 connection_id: 0,
293 option_flags1: OptionFlags1 {
295 use_db_notify: true,
296 database_fatal: true,
297 ..Default::default()
298 },
299 option_flags2: OptionFlags2 {
300 language_fatal: true,
301 odbc: true,
302 ..Default::default()
303 },
304 type_flags: TypeFlags::default(), option_flags3: OptionFlags3 {
306 unknown_collation_handling: true,
307 ..Default::default()
308 },
309 client_timezone: 0,
310 client_lcid: 0x0409, hostname: String::new(),
312 username: String::new(),
313 password: String::new(),
314 app_name: String::from("rust-mssql-driver"),
315 server_name: String::new(),
316 unused: String::new(),
317 library_name: String::from("rust-mssql-driver"),
318 language: String::new(),
319 database: String::new(),
320 client_id: [0u8; 6],
321 sspi_data: Vec::new(),
322 attach_db_file: String::new(),
323 new_password: String::new(),
324 features: Vec::new(),
325 }
326 }
327}
328
329impl Login7 {
330 #[must_use]
332 pub fn new() -> Self {
333 Self::default()
334 }
335
336 #[must_use]
338 pub fn with_tds_version(mut self, version: TdsVersion) -> Self {
339 self.tds_version = version;
340 self
341 }
342
343 #[must_use]
345 pub fn with_sql_auth(
346 mut self,
347 username: impl Into<String>,
348 password: impl Into<String>,
349 ) -> Self {
350 self.username = username.into();
351 self.password = password.into();
352 self.option_flags2.integrated_security = false;
353 self
354 }
355
356 #[must_use]
358 pub fn with_integrated_auth(mut self, sspi_data: Vec<u8>) -> Self {
359 self.sspi_data = sspi_data;
360 self.option_flags2.integrated_security = true;
361 self
362 }
363
364 #[must_use]
366 pub fn with_database(mut self, database: impl Into<String>) -> Self {
367 self.database = database.into();
368 self
369 }
370
371 #[must_use]
373 pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
374 self.hostname = hostname.into();
375 self
376 }
377
378 #[must_use]
380 pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
381 self.app_name = app_name.into();
382 self
383 }
384
385 #[must_use]
387 pub fn with_server_name(mut self, server_name: impl Into<String>) -> Self {
388 self.server_name = server_name.into();
389 self
390 }
391
392 #[must_use]
394 pub fn with_packet_size(mut self, packet_size: u32) -> Self {
395 self.packet_size = packet_size;
396 self
397 }
398
399 #[must_use]
401 pub fn with_read_only_intent(mut self, read_only: bool) -> Self {
402 self.type_flags.read_only_intent = read_only;
403 self
404 }
405
406 #[must_use]
408 pub fn with_feature(mut self, feature: FeatureExtension) -> Self {
409 self.option_flags3.extension = true;
410 self.features.push(feature);
411 self
412 }
413
414 #[must_use]
416 pub fn encode(&self) -> Bytes {
417 let mut buf = BytesMut::with_capacity(512);
418
419 let mut offset = LOGIN7_HEADER_SIZE as u16;
422
423 let hostname_len = self.hostname.encode_utf16().count() as u16;
425 let username_len = self.username.encode_utf16().count() as u16;
426 let password_len = self.password.encode_utf16().count() as u16;
427 let app_name_len = self.app_name.encode_utf16().count() as u16;
428 let server_name_len = self.server_name.encode_utf16().count() as u16;
429 let unused_len = self.unused.encode_utf16().count() as u16;
430 let library_name_len = self.library_name.encode_utf16().count() as u16;
431 let language_len = self.language.encode_utf16().count() as u16;
432 let database_len = self.database.encode_utf16().count() as u16;
433 let sspi_len = self.sspi_data.len() as u16;
434 let attach_db_len = self.attach_db_file.encode_utf16().count() as u16;
435 let new_password_len = self.new_password.encode_utf16().count() as u16;
436
437 let mut var_data = BytesMut::new();
439
440 let hostname_offset = offset;
442 write_utf16_string(&mut var_data, &self.hostname);
443 offset += hostname_len * 2;
444
445 let username_offset = offset;
447 write_utf16_string(&mut var_data, &self.username);
448 offset += username_len * 2;
449
450 let password_offset = offset;
452 Self::write_obfuscated_password(&mut var_data, &self.password);
453 offset += password_len * 2;
454
455 let app_name_offset = offset;
457 write_utf16_string(&mut var_data, &self.app_name);
458 offset += app_name_len * 2;
459
460 let server_name_offset = offset;
462 write_utf16_string(&mut var_data, &self.server_name);
463 offset += server_name_len * 2;
464
465 let extension_offset = if self.option_flags3.extension {
467 let base = offset
469 + unused_len * 2
470 + library_name_len * 2
471 + language_len * 2
472 + database_len * 2
473 + sspi_len
474 + attach_db_len * 2
475 + new_password_len * 2;
476 var_data.put_u32_le(base as u32);
478 offset += 4;
479 base
480 } else {
481 let unused_offset = offset;
482 write_utf16_string(&mut var_data, &self.unused);
483 offset += unused_len * 2;
484 unused_offset
485 };
486
487 let library_name_offset = offset;
489 write_utf16_string(&mut var_data, &self.library_name);
490 offset += library_name_len * 2;
491
492 let language_offset = offset;
494 write_utf16_string(&mut var_data, &self.language);
495 offset += language_len * 2;
496
497 let database_offset = offset;
499 write_utf16_string(&mut var_data, &self.database);
500 offset += database_len * 2;
501
502 let sspi_offset = offset;
507 var_data.put_slice(&self.sspi_data);
508 offset += sspi_len;
509
510 let attach_db_offset = offset;
512 write_utf16_string(&mut var_data, &self.attach_db_file);
513 offset += attach_db_len * 2;
514
515 let new_password_offset = offset;
517 if !self.new_password.is_empty() {
518 Self::write_obfuscated_password(&mut var_data, &self.new_password);
519 }
520 #[allow(unused_assignments)]
521 {
522 offset += new_password_len * 2;
523 }
524
525 if self.option_flags3.extension {
527 for feature in &self.features {
528 var_data.put_u8(feature.feature_id as u8);
529 var_data.put_u32_le(feature.data.len() as u32);
530 var_data.put_slice(&feature.data);
531 }
532 var_data.put_u8(FeatureId::Terminator as u8);
533 }
534
535 let total_length = LOGIN7_HEADER_SIZE + var_data.len();
537
538 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());
548 buf.put_u8(self.option_flags2.to_byte());
549 buf.put_u8(self.type_flags.to_byte());
550 buf.put_u8(self.option_flags3.to_byte());
551
552 buf.put_i32_le(self.client_timezone); buf.put_u32_le(self.client_lcid); buf.put_u16_le(hostname_offset);
557 buf.put_u16_le(hostname_len);
558 buf.put_u16_le(username_offset);
559 buf.put_u16_le(username_len);
560 buf.put_u16_le(password_offset);
561 buf.put_u16_le(password_len);
562 buf.put_u16_le(app_name_offset);
563 buf.put_u16_le(app_name_len);
564 buf.put_u16_le(server_name_offset);
565 buf.put_u16_le(server_name_len);
566
567 if self.option_flags3.extension {
569 buf.put_u16_le(extension_offset as u16);
570 buf.put_u16_le(4); } else {
572 buf.put_u16_le(extension_offset as u16);
573 buf.put_u16_le(unused_len);
574 }
575
576 buf.put_u16_le(library_name_offset);
577 buf.put_u16_le(library_name_len);
578 buf.put_u16_le(language_offset);
579 buf.put_u16_le(language_len);
580 buf.put_u16_le(database_offset);
581 buf.put_u16_le(database_len);
582
583 buf.put_slice(&self.client_id);
585
586 buf.put_u16_le(sspi_offset);
587 buf.put_u16_le(sspi_len);
588 buf.put_u16_le(attach_db_offset);
589 buf.put_u16_le(attach_db_len);
590 buf.put_u16_le(new_password_offset);
591 buf.put_u16_le(new_password_len);
592
593 buf.put_u32_le(0);
595
596 buf.put_slice(&var_data);
598
599 buf.freeze()
600 }
601
602 fn write_obfuscated_password(dst: &mut impl BufMut, password: &str) {
607 for c in password.encode_utf16() {
608 let low = (c & 0xFF) as u8;
609 let high = ((c >> 8) & 0xFF) as u8;
610
611 let low_enc = low.rotate_right(4) ^ 0xA5;
614 let high_enc = high.rotate_right(4) ^ 0xA5;
615
616 dst.put_u8(low_enc);
617 dst.put_u8(high_enc);
618 }
619 }
620}
621
622#[cfg(test)]
623#[allow(clippy::unwrap_used)]
624mod tests {
625 use super::*;
626
627 #[test]
628 fn test_login7_default() {
629 let login = Login7::new();
630 assert_eq!(login.tds_version, TdsVersion::V7_4);
631 assert_eq!(login.packet_size, 4096);
632 assert!(login.option_flags2.odbc);
633 }
634
635 #[test]
636 fn test_login7_encode() {
637 let login = Login7::new()
638 .with_hostname("TESTHOST")
639 .with_sql_auth("testuser", "testpass")
640 .with_database("testdb")
641 .with_app_name("TestApp");
642
643 let encoded = login.encode();
644
645 assert!(encoded.len() >= LOGIN7_HEADER_SIZE);
647
648 let tds_version = u32::from_le_bytes([encoded[4], encoded[5], encoded[6], encoded[7]]);
650 assert_eq!(tds_version, TdsVersion::V7_4.raw());
651 }
652
653 #[test]
654 fn test_password_obfuscation() {
655 let mut buf = BytesMut::new();
657 Login7::write_obfuscated_password(&mut buf, "a");
658
659 assert_eq!(buf.len(), 2);
664 assert_eq!(buf[0], 0xB3);
665 assert_eq!(buf[1], 0xA5);
666 }
667
668 #[test]
669 fn test_option_flags() {
670 let flags1 = OptionFlags1::default();
671 assert_eq!(flags1.to_byte(), 0x00);
672
673 let flags2 = OptionFlags2 {
674 odbc: true,
675 integrated_security: true,
676 ..Default::default()
677 };
678 assert_eq!(flags2.to_byte(), 0x82);
679
680 let flags3 = OptionFlags3 {
681 extension: true,
682 ..Default::default()
683 };
684 assert_eq!(flags3.to_byte(), 0x10);
685 }
686}