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