sspi_bobbobbio/ntlm/messages/client/
negotiate.rs

1use std::io;
2
3use byteorder::{LittleEndian, WriteBytesExt};
4
5use crate::ntlm::messages::{MessageFields, MessageTypes, NTLM_SIGNATURE, NTLM_VERSION_SIZE};
6use crate::ntlm::{NegotiateFlags, NegotiateMessage, Ntlm, NtlmState};
7use crate::SecurityStatus;
8
9const HEADER_SIZE: usize = 32;
10const NEGO_MESSAGE_OFFSET: usize = HEADER_SIZE + NTLM_VERSION_SIZE;
11
12struct NegotiateMessageFields {
13    domain_name: MessageFields,
14    workstation: MessageFields,
15}
16
17impl NegotiateMessageFields {
18    pub fn new(offset: u32, workstation: Option<Vec<u8>>) -> Self {
19        let mut domain_name = MessageFields::new();
20        let mut workstation = MessageFields::with_buffer(workstation.unwrap_or_default());
21
22        domain_name.buffer_offset = offset;
23        workstation.buffer_offset = domain_name.buffer_offset + domain_name.buffer.len() as u32;
24
25        NegotiateMessageFields {
26            domain_name,
27            workstation,
28        }
29    }
30
31    pub fn data_len(&self) -> usize {
32        self.workstation.buffer_offset as usize + self.workstation.buffer.len()
33    }
34}
35
36fn check_state(state: NtlmState) -> crate::Result<()> {
37    if state != NtlmState::Negotiate {
38        Err(crate::Error::new(
39            crate::ErrorKind::OutOfSequence,
40            String::from("Write negotiate was fired but the state is not a Negotiate"),
41        ))
42    } else {
43        Ok(())
44    }
45}
46
47pub fn write_negotiate(context: &mut Ntlm, mut transport: impl io::Write) -> crate::Result<SecurityStatus> {
48    check_state(context.state)?;
49
50    let negotiate_flags = get_flags(context);
51    let message_fields = NegotiateMessageFields::new(
52        NEGO_MESSAGE_OFFSET as u32,
53        context
54            .config
55            .workstation
56            .as_ref()
57            .map(|workstation| workstation.as_bytes().to_vec()),
58    );
59
60    let mut buffer = Vec::with_capacity(message_fields.data_len());
61
62    write_header(negotiate_flags, context.version.as_ref(), &message_fields, &mut buffer)?;
63    write_payload(&message_fields, &mut buffer)?;
64    context.flags = negotiate_flags;
65
66    let message = buffer;
67
68    transport.write_all(message.as_slice())?;
69    transport.flush()?;
70
71    context.negotiate_message = Some(NegotiateMessage::new(message));
72    context.state = NtlmState::Challenge;
73
74    Ok(crate::SecurityStatus::ContinueNeeded)
75}
76
77fn get_flags(context: &Ntlm) -> NegotiateFlags {
78    // NTLMv2
79    let mut flags = NegotiateFlags::NTLM_SSP_NEGOTIATE56
80        | NegotiateFlags::NTLM_SSP_NEGOTIATE_LM_KEY
81        | NegotiateFlags::NTLM_SSP_NEGOTIATE_OEM
82    // ASC_REQ_CONFIDENTIALITY, ISC_REQ_CONFIDENTIALITY always set in the nla
83        | NegotiateFlags::NTLM_SSP_NEGOTIATE_SEAL
84    // other flags
85        | NegotiateFlags::NTLM_SSP_NEGOTIATE_KEY_EXCH
86        | NegotiateFlags::NTLM_SSP_NEGOTIATE128
87        | NegotiateFlags::NTLM_SSP_NEGOTIATE_EXTENDED_SESSION_SECURITY
88        | NegotiateFlags::NTLM_SSP_NEGOTIATE_ALWAYS_SIGN
89        | NegotiateFlags::NTLM_SSP_NEGOTIATE_NTLM
90        | NegotiateFlags::NTLM_SSP_NEGOTIATE_SIGN
91        | NegotiateFlags::NTLM_SSP_NEGOTIATE_REQUEST_TARGET
92        | NegotiateFlags::NTLM_SSP_NEGOTIATE_UNICODE
93        | NegotiateFlags::NTLM_SSP_NEGOTIATE_VERSION;
94
95    if context.config().workstation.is_some() {
96        flags |= NegotiateFlags::NTLM_SSP_NEGOTIATE_WORKSTATION_SUPPLIED;
97    }
98
99    flags
100}
101
102fn write_header(
103    negotiate_flags: NegotiateFlags,
104    version: &[u8],
105    message_fields: &NegotiateMessageFields,
106    mut buffer: impl io::Write,
107) -> io::Result<()> {
108    buffer.write_all(NTLM_SIGNATURE)?; // signature 8 bytes
109    buffer.write_u32::<LittleEndian>(MessageTypes::Negotiate as u32)?; // message type 4 bytes
110    buffer.write_u32::<LittleEndian>(negotiate_flags.bits())?; // negotiate flags 4 bytes
111    message_fields.domain_name.write_to(&mut buffer)?; // domain name 8 bytes
112    message_fields.workstation.write_to(&mut buffer)?; // workstation 8 bytes
113    buffer.write_all(version)?;
114
115    Ok(())
116}
117
118fn write_payload(message_fields: &NegotiateMessageFields, mut buffer: impl io::Write) -> io::Result<()> {
119    message_fields.domain_name.write_buffer_to(&mut buffer)?;
120    message_fields.workstation.write_buffer_to(&mut buffer)?;
121
122    Ok(())
123}