]>
Commit | Line | Data |
---|---|---|
9c89cd13 | 1 | /* |
bf95c10a | 2 | * Copyright (C) 1996-2022 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 | * | |
62 | * \retval NTLM_ERR_NONE Packet is okay | |
63 | * \retval NTLM_ERR_BLOB Packet is not even an NTLMSSP packet at all. | |
64 | * \retval NTLM_ERR_PROTOCOL Packet is not the expected type. | |
75aa769b AJ |
65 | */ |
66 | int | |
b13b5d03 | 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"); | |
1dcf61eb | 75 | return NTLM_ERR_BLOB; |
75aa769b AJ |
76 | } |
77 | if (type == NTLM_ANY) | |
1dcf61eb | 78 | return NTLM_ERR_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); |
1dcf61eb | 83 | return NTLM_ERR_PROTOCOL; |
75aa769b | 84 | } |
1dcf61eb | 85 | return NTLM_ERR_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 | |
ec419c3a EV |
104 | rv.str = NULL; |
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 | |
75aa769b | 111 | if (l < 0 || l > NTLM_MAX_FIELD_LENGTH || o + l > packet_size || o == 0) { |
31e19066 | 112 | debug("ntlm_fetch_string: insane data (pkt-sz: %d, fetch len: %d, offset: %d)\n", packet_size,l,o); |
26ac0430 | 113 | return rv; |
94439e4e | 114 | } |
75aa769b | 115 | rv.str = (char *)packet + o; |
ec419c3a | 116 | rv.l = 0; |
1dcf61eb | 117 | if ((flags & NTLM_NEGOTIATE_ASCII) == 0) { |
75aa769b | 118 | /* UNICODE string */ |
31e19066 | 119 | unsigned short *s = (unsigned short *)rv.str; |
75aa769b AJ |
120 | rv.str = d = buf; |
121 | ||
9c78b0e0 AJ |
122 | for (uint32_t len = (l>>1); len; ++s, --len) { |
123 | uint16_t c = le16toh(*s); | |
75aa769b AJ |
124 | if (c > 254 || c == '\0') { |
125 | fprintf(stderr, "ntlmssp: bad unicode: %04x\n", c); | |
126 | return rv; | |
127 | } | |
9c78b0e0 | 128 | *d = static_cast<char>(c&0xFF); |
f207fe64 | 129 | ++d; |
742a021b | 130 | ++rv.l; |
75aa769b AJ |
131 | } |
132 | } else { | |
133 | /* ASCII/OEM string */ | |
31e19066 | 134 | char *sc = rv.str; |
75aa769b | 135 | |
742a021b | 136 | for (; l>=0; ++sc, --l) { |
75aa769b AJ |
137 | if (*sc == '\0' || !xisprint(*sc)) { |
138 | fprintf(stderr, "ntlmssp: bad ascii: %04x\n", *sc); | |
139 | return rv; | |
140 | } | |
742a021b | 141 | ++rv.l; |
75aa769b AJ |
142 | } |
143 | } | |
94439e4e | 144 | |
145 | return rv; | |
146 | } | |
147 | ||
dac46b89 | 148 | /** |
2f8abb64 | 149 | * Adds something to the payload. The caller must guarantee that |
94439e4e | 150 | * there is enough space in the payload string to accommodate the |
151 | * added value. | |
152 | * payload_length and hdr will be modified as a side-effect. | |
94439e4e | 153 | */ |
154 | void | |
75aa769b AJ |
155 | ntlm_add_to_payload(const ntlmhdr *packet_hdr, |
156 | char *payload, | |
157 | int *payload_length, | |
158 | strhdr * hdr, | |
159 | const char *toadd, | |
9c78b0e0 | 160 | const uint16_t toadd_length) |
94439e4e | 161 | { |
94439e4e | 162 | int l = (*payload_length); |
163 | memcpy(payload + l, toadd, toadd_length); | |
164 | ||
f9576890 | 165 | hdr->len = htole16(toadd_length); |
166 | hdr->maxlen = htole16(toadd_length); | |
9c78b0e0 AJ |
167 | const off_t o = l + reinterpret_cast<const ntlmhdr *>(payload) - packet_hdr; |
168 | hdr->offset = htole32(o & 0xFFFFFFFF); | |
94439e4e | 169 | (*payload_length) += toadd_length; |
170 | } | |
171 | ||
75aa769b AJ |
172 | /* ************************************************************************* */ |
173 | /* Negotiate Packet functions */ | |
174 | /* ************************************************************************* */ | |
175 | ||
1dcf61eb | 176 | // ? |
75aa769b | 177 | |
75aa769b AJ |
178 | /* ************************************************************************* */ |
179 | /* Challenge Packet functions */ | |
180 | /* ************************************************************************* */ | |
181 | ||
632d8053 | 182 | /* |
85b08f12 | 183 | * Generates a challenge request nonce. |
75aa769b AJ |
184 | */ |
185 | void | |
186 | ntlm_make_nonce(char *nonce) | |
187 | { | |
85b08f12 | 188 | static std::mt19937 mt(time(0)); |
8ed8fa40 | 189 | static xuniform_int_distribution<uint8_t> dist; |
75aa769b | 190 | |
85b08f12 AJ |
191 | for (int i = 0; i < NTLM_NONCE_LEN; ++i) |
192 | nonce[i] = static_cast<char>(dist(mt) & 0xFF); | |
75aa769b AJ |
193 | } |
194 | ||
75aa769b AJ |
195 | /** |
196 | * Prepares a challenge packet to be sent to the client | |
197 | * \note domain should be upper_case | |
198 | */ | |
199 | void | |
200 | ntlm_make_challenge(ntlm_challenge *ch, | |
ced8def3 | 201 | const char *domain, const char *, |
75aa769b | 202 | const char *challenge_nonce, const int challenge_nonce_len, |
09aabd84 | 203 | const uint32_t flags) |
94439e4e | 204 | { |
94439e4e | 205 | int pl = 0; |
f53969cc SM |
206 | memset(ch, 0, sizeof(ntlm_challenge)); /* reset */ |
207 | memcpy(ch->hdr.signature, "NTLMSSP", 8); /* set the signature */ | |
208 | ch->hdr.type = htole32(NTLM_CHALLENGE); /* this is a challenge */ | |
75aa769b | 209 | if (domain != NULL) { |
9c78b0e0 AJ |
210 | // silently truncate the domain if it exceeds 2^16-1 bytes. |
211 | // NTLM packets normally expect 2^8 bytes of domain. | |
212 | const uint16_t dlen = strlen(domain) & 0xFFFF; | |
213 | ntlm_add_to_payload(&ch->hdr, ch->payload, &pl, &ch->target, domain, dlen); | |
75aa769b AJ |
214 | } |
215 | ch->flags = htole32(flags); | |
f53969cc | 216 | ch->context_low = 0; /* check this out */ |
75aa769b AJ |
217 | ch->context_high = 0; |
218 | memcpy(ch->challenge, challenge_nonce, challenge_nonce_len); | |
219 | } | |
220 | ||
221 | /* ************************************************************************* */ | |
222 | /* Authenticate Packet functions */ | |
223 | /* ************************************************************************* */ | |
224 | ||
225 | /** | |
226 | * Unpack the strings in an NTLM authentication response from client. | |
227 | * The caller is responsible for initializing the user and domain buffers | |
228 | * this function will only insert data if the packet contains any. Otherwise | |
229 | * the buffers will be left untouched. | |
230 | * | |
f53969cc SM |
231 | * \retval NTLM_ERR_NONE username present, maybe also domain. |
232 | * \retval NTLM_ERR_PROTOCOL packet type is not an authentication packet. | |
233 | * \retval NTLM_ERR_LOGON no username. | |
234 | * \retval NTLM_ERR_BLOB domain field is apparently larger than the packet. | |
75aa769b AJ |
235 | */ |
236 | int | |
237 | ntlm_unpack_auth(const ntlm_authenticate *auth, char *user, char *domain, const int32_t size) | |
238 | { | |
75aa769b AJ |
239 | lstring rv; |
240 | ||
241 | if (ntlm_validate_packet(&auth->hdr, NTLM_AUTHENTICATE)) { | |
31e19066 | 242 | fprintf(stderr, "ntlm_unpack_auth: header check fails\n"); |
1dcf61eb | 243 | return NTLM_ERR_PROTOCOL; |
75aa769b | 244 | } |
31e19066 AJ |
245 | debug("ntlm_unpack_auth: size of %d\n", size); |
246 | debug("ntlm_unpack_auth: flg %08x\n", auth->flags); | |
247 | debug("ntlm_unpack_auth: lmr o(%d) l(%d)\n", le32toh(auth->lmresponse.offset), auth->lmresponse.len); | |
248 | debug("ntlm_unpack_auth: ntr o(%d) l(%d)\n", le32toh(auth->ntresponse.offset), auth->ntresponse.len); | |
249 | debug("ntlm_unpack_auth: dom o(%d) l(%d)\n", le32toh(auth->domain.offset), auth->domain.len); | |
250 | debug("ntlm_unpack_auth: usr o(%d) l(%d)\n", le32toh(auth->user.offset), auth->user.len); | |
251 | debug("ntlm_unpack_auth: wst o(%d) l(%d)\n", le32toh(auth->workstation.offset), auth->workstation.len); | |
252 | debug("ntlm_unpack_auth: key o(%d) l(%d)\n", le32toh(auth->sessionkey.offset), auth->sessionkey.len); | |
75aa769b AJ |
253 | |
254 | rv = ntlm_fetch_string(&auth->hdr, size, &auth->domain, auth->flags); | |
255 | if (rv.l > 0) { | |
31e19066 | 256 | memcpy(domain, rv.str, rv.l); |
75aa769b | 257 | domain[rv.l] = '\0'; |
31e19066 | 258 | debug("ntlm_unpack_auth: Domain '%s' (len=%d).\n", domain, rv.l); |
75aa769b | 259 | } |
ea205436 | 260 | if (rv.l >= size) { |
9e167fa2 | 261 | debug("ntlm_unpack_auth: Domain length %d too big for %d byte packet.\n", rv.l, size); |
1dcf61eb | 262 | return NTLM_ERR_BLOB; |
ea205436 | 263 | } |
75aa769b AJ |
264 | |
265 | rv = ntlm_fetch_string(&auth->hdr, size, &auth->user, auth->flags); | |
266 | if (rv.l > 0) { | |
31e19066 | 267 | memcpy(user, rv.str, rv.l); |
75aa769b | 268 | user[rv.l] = '\0'; |
31e19066 | 269 | debug("ntlm_unpack_auth: Username '%s' (len=%d).\n", user, rv.l); |
75aa769b | 270 | } else |
1dcf61eb | 271 | return NTLM_ERR_LOGON; |
75aa769b | 272 | |
1dcf61eb | 273 | return NTLM_ERR_NONE; |
94439e4e | 274 | } |
f53969cc | 275 |