]>
Commit | Line | Data |
---|---|---|
9c89cd13 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
9c89cd13 AJ |
3 | * |
4 | * Squid software is distributed under GPLv2+ license and includes | |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
7 | */ | |
8 | ||
94439e4e | 9 | /* |
9c89cd13 | 10 | * Inspired by previous work by Andrew Doran <ad@interlude.eu.org>. |
94439e4e | 11 | */ |
f7f3304a | 12 | #include "squid.h" |
94439e4e | 13 | |
074d6a40 | 14 | #include <cstring> |
9b7c14a5 | 15 | #include <ctime> |
85b08f12 | 16 | #include <random> |
32d002cb | 17 | #if HAVE_STRINGS_H |
cf17b739 | 18 | #include <strings.h> |
19 | #endif | |
20 | ||
7c16470c | 21 | #include "ntlmauth/ntlmauth.h" |
f53969cc | 22 | #include "util.h" /* for base64-related stuff */ |
d434f297 | 23 | |
75aa769b AJ |
24 | /* ************************************************************************* */ |
25 | /* DEBUG functions */ | |
26 | /* ************************************************************************* */ | |
27 | ||
dac46b89 | 28 | /** Dumps NTLM flags to standard error for debugging purposes */ |
94439e4e | 29 | void |
09aabd84 | 30 | ntlm_dump_ntlmssp_flags(uint32_t flags) |
94439e4e | 31 | { |
32 | fprintf(stderr, "flags: %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", | |
1dcf61eb AJ |
33 | (flags & NTLM_NEGOTIATE_UNICODE ? "Unicode " : ""), |
34 | (flags & NTLM_NEGOTIATE_ASCII ? "ASCII " : ""), | |
35 | (flags & NTLM_NEGOTIATE_REQUEST_TARGET ? "ReqTgt " : ""), | |
36 | (flags & NTLM_NEGOTIATE_REQUEST_SIGN ? "ReqSign " : ""), | |
37 | (flags & NTLM_NEGOTIATE_REQUEST_SEAL ? "ReqSeal " : ""), | |
38 | (flags & NTLM_NEGOTIATE_DATAGRAM_STYLE ? "Dgram " : ""), | |
39 | (flags & NTLM_NEGOTIATE_USE_LM ? "UseLM " : ""), | |
40 | (flags & NTLM_NEGOTIATE_USE_NETWARE ? "UseNW " : ""), | |
41 | (flags & NTLM_NEGOTIATE_USE_NTLM ? "UseNTLM " : ""), | |
42 | (flags & NTLM_NEGOTIATE_DOMAIN_SUPPLIED ? "HaveDomain " : ""), | |
43 | (flags & NTLM_NEGOTIATE_WORKSTATION_SUPPLIED ? "HaveWKS " : ""), | |
44 | (flags & NTLM_NEGOTIATE_THIS_IS_LOCAL_CALL ? "LocalCall " : ""), | |
45 | (flags & NTLM_NEGOTIATE_ALWAYS_SIGN ? "AlwaysSign " : ""), | |
46 | (flags & NTLM_CHALLENGE_TARGET_IS_DOMAIN ? "Tgt_is_domain" : ""), | |
47 | (flags & NTLM_CHALLENGE_TARGET_IS_SERVER ? "Tgt_is_server " : ""), | |
48 | (flags & NTLM_CHALLENGE_TARGET_IS_SHARE ? "Tgt_is_share " : ""), | |
49 | (flags & NTLM_REQUEST_INIT_RESPONSE ? "Req_init_response " : ""), | |
50 | (flags & NTLM_REQUEST_ACCEPT_RESPONSE ? "Req_accept_response " : ""), | |
51 | (flags & NTLM_REQUEST_NON_NT_SESSION_KEY ? "Req_nonnt_sesskey " : "") | |
26ac0430 | 52 | ); |
94439e4e | 53 | } |
75aa769b AJ |
54 | |
55 | /* ************************************************************************* */ | |
56 | /* Packet and Payload handling functions */ | |
57 | /* ************************************************************************* */ | |
58 | ||
59 | /** | |
1dcf61eb AJ |
60 | * Check the validity of a decoded NTLM packet. |
61 | * | |
1e37143c FC |
62 | * \retval NtlmError::None Packet is okay |
63 | * \retval NtlmError::BlobError Packet is not even an NTLMSSP packet at all. | |
64 | * \retval NtlmError::ProtocolError Packet is not the expected type. | |
75aa769b | 65 | */ |
1e37143c FC |
66 | NtlmError |
67 | ntlm_validate_packet(const ntlmhdr *hdr, const int32_t type) | |
75aa769b AJ |
68 | { |
69 | /* | |
70 | * Must be the correct security package and request type. | |
71 | * The 8 bytes compared includes the ASCII 'NUL'. | |
72 | */ | |
73 | if (memcmp(hdr->signature, "NTLMSSP", 8) != 0) { | |
74 | fprintf(stderr, "ntlmCheckHeader: bad header signature\n"); | |
1e37143c | 75 | return NtlmError::BlobError; |
75aa769b AJ |
76 | } |
77 | if (type == NTLM_ANY) | |
1e37143c | 78 | return NtlmError::None; |
75aa769b | 79 | |
b13b5d03 | 80 | if ((int32_t)le32toh(hdr->type) != type) { |
75aa769b | 81 | /* don't report this error - it's ok as we do a if() around this function */ |
31e19066 | 82 | debug("ntlm_validate_packet: type is %d, wanted %d\n", le32toh(hdr->type), type); |
1e37143c | 83 | return NtlmError::ProtocolError; |
75aa769b | 84 | } |
1e37143c | 85 | return NtlmError::None; |
75aa769b | 86 | } |
94439e4e | 87 | |
dac46b89 AJ |
88 | /** |
89 | * Fetches a string from the authentication packet. | |
75aa769b | 90 | * The lstring data-part may point to inside the packet itself or a temporary static buffer. |
94439e4e | 91 | * It's up to the user to memcpy() that if the value needs to |
dac46b89 AJ |
92 | * be used in any way that requires a tailing \0. (can check whether the |
93 | * value is there though, in that case lstring.length == -1). | |
75aa769b | 94 | * |
1dcf61eb | 95 | * String may be either ASCII or UNICODE depending on whether flags contains NTLM_NEGOTIATE_ASCII |
94439e4e | 96 | */ |
97 | lstring | |
09aabd84 | 98 | ntlm_fetch_string(const ntlmhdr *packet, const int32_t packet_size, const strhdr * str, const uint32_t flags) |
94439e4e | 99 | { |
75aa769b | 100 | static char buf[NTLM_MAX_FIELD_LENGTH]; |
94439e4e | 101 | lstring rv; |
31e19066 | 102 | char *d; |
94439e4e | 103 | |
aee3523a | 104 | rv.str = nullptr; |
ec419c3a | 105 | rv.l = -1; |
94439e4e | 106 | |
9c78b0e0 AJ |
107 | int16_t l = le16toh(str->len); |
108 | int32_t o = le32toh(str->offset); | |
31e19066 | 109 | // debug("ntlm_fetch_string(plength=%d,l=%d,o=%d)\n",packet_size,l,o); |
94439e4e | 110 | |
8fda067b AJ |
111 | if (l < 0 || l > NTLM_MAX_FIELD_LENGTH) { |
112 | debug("ntlm_fetch_string: insane string length (pkt-sz: %d, fetch len: %d, offset: %d)\n", packet_size,l,o); | |
26ac0430 | 113 | return rv; |
94439e4e | 114 | } |
8fda067b AJ |
115 | else if (o <= 0 || o > packet_size) { |
116 | debug("ntlm_fetch_string: insane string offset (pkt-sz: %d, fetch len: %d, offset: %d)\n", packet_size,l,o); | |
117 | return rv; | |
118 | } | |
119 | else if (l > packet_size - o) { | |
120 | debug("ntlm_fetch_string: truncated string data (pkt-sz: %d, fetch len: %d, offset: %d)\n", packet_size,l,o); | |
121 | return rv; | |
122 | } | |
123 | ||
75aa769b | 124 | rv.str = (char *)packet + o; |
ec419c3a | 125 | rv.l = 0; |
1dcf61eb | 126 | if ((flags & NTLM_NEGOTIATE_ASCII) == 0) { |
75aa769b | 127 | /* UNICODE string */ |
31e19066 | 128 | unsigned short *s = (unsigned short *)rv.str; |
75aa769b AJ |
129 | rv.str = d = buf; |
130 | ||
9c78b0e0 AJ |
131 | for (uint32_t len = (l>>1); len; ++s, --len) { |
132 | uint16_t c = le16toh(*s); | |
75aa769b AJ |
133 | if (c > 254 || c == '\0') { |
134 | fprintf(stderr, "ntlmssp: bad unicode: %04x\n", c); | |
135 | return rv; | |
136 | } | |
9c78b0e0 | 137 | *d = static_cast<char>(c&0xFF); |
f207fe64 | 138 | ++d; |
742a021b | 139 | ++rv.l; |
75aa769b AJ |
140 | } |
141 | } else { | |
142 | /* ASCII/OEM string */ | |
31e19066 | 143 | char *sc = rv.str; |
75aa769b | 144 | |
742a021b | 145 | for (; l>=0; ++sc, --l) { |
75aa769b AJ |
146 | if (*sc == '\0' || !xisprint(*sc)) { |
147 | fprintf(stderr, "ntlmssp: bad ascii: %04x\n", *sc); | |
148 | return rv; | |
149 | } | |
742a021b | 150 | ++rv.l; |
75aa769b AJ |
151 | } |
152 | } | |
94439e4e | 153 | |
154 | return rv; | |
155 | } | |
156 | ||
dac46b89 | 157 | /** |
2f8abb64 | 158 | * Adds something to the payload. The caller must guarantee that |
94439e4e | 159 | * there is enough space in the payload string to accommodate the |
160 | * added value. | |
161 | * payload_length and hdr will be modified as a side-effect. | |
94439e4e | 162 | */ |
163 | void | |
75aa769b AJ |
164 | ntlm_add_to_payload(const ntlmhdr *packet_hdr, |
165 | char *payload, | |
166 | int *payload_length, | |
167 | strhdr * hdr, | |
168 | const char *toadd, | |
9c78b0e0 | 169 | const uint16_t toadd_length) |
94439e4e | 170 | { |
94439e4e | 171 | int l = (*payload_length); |
172 | memcpy(payload + l, toadd, toadd_length); | |
173 | ||
f9576890 | 174 | hdr->len = htole16(toadd_length); |
175 | hdr->maxlen = htole16(toadd_length); | |
9c78b0e0 AJ |
176 | const off_t o = l + reinterpret_cast<const ntlmhdr *>(payload) - packet_hdr; |
177 | hdr->offset = htole32(o & 0xFFFFFFFF); | |
94439e4e | 178 | (*payload_length) += toadd_length; |
179 | } | |
180 | ||
75aa769b AJ |
181 | /* ************************************************************************* */ |
182 | /* Negotiate Packet functions */ | |
183 | /* ************************************************************************* */ | |
184 | ||
1dcf61eb | 185 | // ? |
75aa769b | 186 | |
75aa769b AJ |
187 | /* ************************************************************************* */ |
188 | /* Challenge Packet functions */ | |
189 | /* ************************************************************************* */ | |
190 | ||
632d8053 | 191 | /* |
85b08f12 | 192 | * Generates a challenge request nonce. |
75aa769b AJ |
193 | */ |
194 | void | |
195 | ntlm_make_nonce(char *nonce) | |
196 | { | |
aee3523a | 197 | static std::mt19937 mt(time(nullptr)); |
09835feb | 198 | static std::uniform_int_distribution<uint8_t> dist; |
75aa769b | 199 | |
85b08f12 AJ |
200 | for (int i = 0; i < NTLM_NONCE_LEN; ++i) |
201 | nonce[i] = static_cast<char>(dist(mt) & 0xFF); | |
75aa769b AJ |
202 | } |
203 | ||
75aa769b AJ |
204 | /** |
205 | * Prepares a challenge packet to be sent to the client | |
206 | * \note domain should be upper_case | |
207 | */ | |
208 | void | |
209 | ntlm_make_challenge(ntlm_challenge *ch, | |
ced8def3 | 210 | const char *domain, const char *, |
75aa769b | 211 | const char *challenge_nonce, const int challenge_nonce_len, |
09aabd84 | 212 | const uint32_t flags) |
94439e4e | 213 | { |
94439e4e | 214 | int pl = 0; |
f53969cc SM |
215 | memset(ch, 0, sizeof(ntlm_challenge)); /* reset */ |
216 | memcpy(ch->hdr.signature, "NTLMSSP", 8); /* set the signature */ | |
217 | ch->hdr.type = htole32(NTLM_CHALLENGE); /* this is a challenge */ | |
aee3523a | 218 | if (domain != nullptr) { |
9c78b0e0 AJ |
219 | // silently truncate the domain if it exceeds 2^16-1 bytes. |
220 | // NTLM packets normally expect 2^8 bytes of domain. | |
221 | const uint16_t dlen = strlen(domain) & 0xFFFF; | |
222 | ntlm_add_to_payload(&ch->hdr, ch->payload, &pl, &ch->target, domain, dlen); | |
75aa769b AJ |
223 | } |
224 | ch->flags = htole32(flags); | |
f53969cc | 225 | ch->context_low = 0; /* check this out */ |
75aa769b AJ |
226 | ch->context_high = 0; |
227 | memcpy(ch->challenge, challenge_nonce, challenge_nonce_len); | |
228 | } | |
229 | ||
230 | /* ************************************************************************* */ | |
231 | /* Authenticate Packet functions */ | |
232 | /* ************************************************************************* */ | |
233 | ||
234 | /** | |
235 | * Unpack the strings in an NTLM authentication response from client. | |
236 | * The caller is responsible for initializing the user and domain buffers | |
237 | * this function will only insert data if the packet contains any. Otherwise | |
238 | * the buffers will be left untouched. | |
239 | * | |
1e37143c FC |
240 | * \retval NtlmError::None username present, maybe also domain. |
241 | * \retval NtlmError::ProtocolError packet type is not an authentication packet. | |
242 | * \retval NtlmError::LoginEror no username. | |
243 | * \retval NtlmError::BlobError domain field is apparently larger than the packet. | |
75aa769b | 244 | */ |
1e37143c | 245 | NtlmError |
75aa769b AJ |
246 | ntlm_unpack_auth(const ntlm_authenticate *auth, char *user, char *domain, const int32_t size) |
247 | { | |
75aa769b AJ |
248 | lstring rv; |
249 | ||
1e37143c | 250 | if (ntlm_validate_packet(&auth->hdr, NTLM_AUTHENTICATE) != NtlmError::None) { |
31e19066 | 251 | fprintf(stderr, "ntlm_unpack_auth: header check fails\n"); |
1e37143c | 252 | return NtlmError::ProtocolError; |
75aa769b | 253 | } |
31e19066 AJ |
254 | debug("ntlm_unpack_auth: size of %d\n", size); |
255 | debug("ntlm_unpack_auth: flg %08x\n", auth->flags); | |
256 | debug("ntlm_unpack_auth: lmr o(%d) l(%d)\n", le32toh(auth->lmresponse.offset), auth->lmresponse.len); | |
257 | debug("ntlm_unpack_auth: ntr o(%d) l(%d)\n", le32toh(auth->ntresponse.offset), auth->ntresponse.len); | |
258 | debug("ntlm_unpack_auth: dom o(%d) l(%d)\n", le32toh(auth->domain.offset), auth->domain.len); | |
259 | debug("ntlm_unpack_auth: usr o(%d) l(%d)\n", le32toh(auth->user.offset), auth->user.len); | |
260 | debug("ntlm_unpack_auth: wst o(%d) l(%d)\n", le32toh(auth->workstation.offset), auth->workstation.len); | |
261 | debug("ntlm_unpack_auth: key o(%d) l(%d)\n", le32toh(auth->sessionkey.offset), auth->sessionkey.len); | |
75aa769b AJ |
262 | |
263 | rv = ntlm_fetch_string(&auth->hdr, size, &auth->domain, auth->flags); | |
264 | if (rv.l > 0) { | |
31e19066 | 265 | memcpy(domain, rv.str, rv.l); |
75aa769b | 266 | domain[rv.l] = '\0'; |
31e19066 | 267 | debug("ntlm_unpack_auth: Domain '%s' (len=%d).\n", domain, rv.l); |
75aa769b | 268 | } |
ea205436 | 269 | if (rv.l >= size) { |
9e167fa2 | 270 | debug("ntlm_unpack_auth: Domain length %d too big for %d byte packet.\n", rv.l, size); |
1e37143c | 271 | return NtlmError::BlobError; |
ea205436 | 272 | } |
75aa769b AJ |
273 | |
274 | rv = ntlm_fetch_string(&auth->hdr, size, &auth->user, auth->flags); | |
275 | if (rv.l > 0) { | |
31e19066 | 276 | memcpy(user, rv.str, rv.l); |
75aa769b | 277 | user[rv.l] = '\0'; |
31e19066 | 278 | debug("ntlm_unpack_auth: Username '%s' (len=%d).\n", user, rv.l); |
75aa769b | 279 | } else |
1e37143c | 280 | return NtlmError::LoginEror; |
75aa769b | 281 | |
1e37143c | 282 | return NtlmError::None; |
94439e4e | 283 | } |
f53969cc | 284 |