]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/ntlm/SSPI/ntlm_sspi_auth.cc
Bug 5428: Warn if pkg-config is not found (#1902)
[thirdparty/squid.git] / src / auth / ntlm / SSPI / ntlm_sspi_auth.cc
1 /*
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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
9 /*
10 * ntlm_sspi_auth: helper for NTLM Authentication for Squid Cache
11 *
12 * (C)2002,2005 Guido Serassio - Acme Consulting S.r.l.
13 *
14 * Authors:
15 * Guido Serassio <guido.serassio@acmeconsulting.it>
16 * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
17 *
18 * With contributions from others mentioned in the change history section
19 * below.
20 *
21 * Based on previous work of Francesco Chemolli and Robert Collins.
22 *
23 * Dependencies: Windows NT4 SP4 and later.
24 *
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 2 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 *
39 * History:
40 *
41 * Version 1.22
42 * 29-10-2005 Guido Serassio
43 * Updated for Negotiate auth support.
44 * Version 1.21
45 * 21-02-2004 Guido Serassio
46 * Removed control of use of NTLM NEGOTIATE packet from
47 * command line, now the support is automatic.
48 * Version 1.20
49 * 30-11-2003 Guido Serassio
50 * Added support for NTLM local calls.
51 * Added control of use of NTLM NEGOTIATE packet from
52 * command line.
53 * Updated documentation.
54 * Version 1.10
55 * 07-09-2003 Guido Serassio
56 * Now is true NTLM authenticator.
57 * More debug info.
58 * Updated documentation.
59 * Version 1.0
60 * 29-06-2002 Guido Serassio
61 * First release.
62 *
63 *
64 */
65
66 /************* CONFIGURATION ***************/
67
68 #define FAIL_DEBUG 0
69
70 /************* END CONFIGURATION ***************/
71
72 //typedef unsigned char uchar;
73
74 #include "squid.h"
75 #include "base64.h"
76 #include "helper/protocol_defines.h"
77 #include "ntlmauth/ntlmauth.h"
78 #include "ntlmauth/support_bits.cci"
79 #include "sspi/sspwin32.h"
80 #include "util.h"
81
82 #include <cctype>
83 #include <lm.h>
84 #if HAVE_GETOPT_H
85 #include <getopt.h>
86 #endif
87
88 int NTLM_packet_debug_enabled = 0;
89 static int have_challenge;
90 char * NTAllowedGroup;
91 char * NTDisAllowedGroup;
92 int UseDisallowedGroup = 0;
93 int UseAllowedGroup = 0;
94
95 #if FAIL_DEBUG
96 int fail_debug_enabled = 0;
97 #endif
98
99 /* returns 1 on success, 0 on failure */
100 static int
101 Valid_Group(char *UserName, char *Group)
102 {
103 int result = FALSE;
104 WCHAR wszUserName[UNLEN+1]; // Unicode user name
105 WCHAR wszGroup[GNLEN+1]; // Unicode Group
106
107 LPLOCALGROUP_USERS_INFO_0 pBuf = nullptr;
108 LPLOCALGROUP_USERS_INFO_0 pTmpBuf;
109 DWORD dwLevel = 0;
110 DWORD dwFlags = LG_INCLUDE_INDIRECT;
111 DWORD dwPrefMaxLen = -1;
112 DWORD dwEntriesRead = 0;
113 DWORD dwTotalEntries = 0;
114 NET_API_STATUS nStatus;
115 DWORD i;
116 DWORD dwTotalCount = 0;
117
118 /* Convert ANSI User Name and Group to Unicode */
119
120 MultiByteToWideChar(CP_ACP, 0, UserName,
121 strlen(UserName) + 1, wszUserName,
122 sizeof(wszUserName) / sizeof(wszUserName[0]));
123 MultiByteToWideChar(CP_ACP, 0, Group,
124 strlen(Group) + 1, wszGroup, sizeof(wszGroup) / sizeof(wszGroup[0]));
125
126 /*
127 * Call the NetUserGetLocalGroups function
128 * specifying information level 0.
129 *
130 * The LG_INCLUDE_INDIRECT flag specifies that the
131 * function should also return the names of the local
132 * groups in which the user is indirectly a member.
133 */
134 nStatus = NetUserGetLocalGroups(nullptr,
135 wszUserName,
136 dwLevel,
137 dwFlags,
138 (LPBYTE *) & pBuf, dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries);
139 /*
140 * If the call succeeds,
141 */
142 if (nStatus == NERR_Success) {
143 if ((pTmpBuf = pBuf) != NULL) {
144 for (i = 0; i < dwEntriesRead; ++i) {
145 if (pTmpBuf == NULL) {
146 result = FALSE;
147 break;
148 }
149 if (wcscmp(pTmpBuf->lgrui0_name, wszGroup) == 0) {
150 result = TRUE;
151 break;
152 }
153 ++pTmpBuf;
154 ++dwTotalCount;
155 }
156 }
157 } else
158 result = FALSE;
159 /*
160 * Free the allocated memory.
161 */
162 if (pBuf != NULL)
163 NetApiBufferFree(pBuf);
164 return result;
165 }
166
167 /*
168 * Fills auth with the user's credentials.
169 *
170 * In case of problem returns one of the
171 * codes defined in libntlmauth/ntlmauth.h
172 */
173 static NtlmError
174 ntlm_check_auth(ntlm_authenticate * auth, char *user, char *domain, int auth_length)
175 {
176 char credentials[DNLEN+UNLEN+2]; /* we can afford to waste */
177
178 if (!NTLM_LocalCall) {
179
180 user[0] = '\0';
181 domain[0] = '\0';
182 const auto x = ntlm_unpack_auth(auth, user, domain, auth_length);
183
184 if (x != NtlmError::None)
185 return x;
186
187 if (domain[0] == '\0') {
188 debug("No domain supplied. Returning no-auth\n");
189 return NtlmError::BadRequest;
190 }
191 if (user[0] == '\0') {
192 debug("No username supplied. Returning no-auth\n");
193 return NtlmError::BadRequest;
194 }
195 debug("checking domain: '%s', user: '%s'\n", domain, user);
196
197 } else {
198 debug("checking local user\n");
199 }
200
201 snprintf(credentials, DNLEN+UNLEN+2, "%s\\%s", domain, user);
202
203 const auto rv = SSP_ValidateNTLMCredentials(auth, auth_length, credentials);
204
205 debug("Login attempt had result %d\n", rv);
206
207 if (!rv) { /* failed */
208 return NtlmError::SspiError;
209 }
210
211 if (UseAllowedGroup) {
212 if (!Valid_Group(credentials, NTAllowedGroup)) {
213 debug("User %s not in allowed Group %s\n", credentials, NTAllowedGroup);
214 return NtlmError::BadNtGroup;
215 }
216 }
217 if (UseDisallowedGroup) {
218 if (Valid_Group(credentials, NTDisAllowedGroup)) {
219 debug("User %s is in denied Group %s\n", credentials, NTDisAllowedGroup);
220 return NtlmError::BadNtGroup;
221 }
222 }
223
224 debug("credentials: %s\n", credentials);
225 return NtlmError::None;
226 }
227
228 static void
229 helperfail(const char *reason)
230 {
231 #if FAIL_DEBUG
232 fail_debug_enabled =1;
233 #endif
234 SEND_BH(reason);
235 }
236
237 /*
238 options:
239 -d enable debugging.
240 -v enable verbose NTLM packet debugging.
241 -A can specify a Windows Local Group name allowed to authenticate.
242 -D can specify a Windows Local Group name not allowed to authenticate.
243 */
244 char *my_program_name = nullptr;
245
246 static void
247 usage()
248 {
249 fprintf(stderr,
250 "Usage: %s [-d] [-v] [-A|D LocalUserGroup] [-h]\n"
251 " -d enable debugging.\n"
252 " -v enable verbose NTLM packet debugging.\n"
253 " -A specify a Windows Local Group name allowed to authenticate\n"
254 " -D specify a Windows Local Group name not allowed to authenticate\n"
255 " -h this message\n\n",
256 my_program_name);
257 }
258
259 static void
260 process_options(int argc, char *argv[])
261 {
262 int opt, had_error = 0;
263
264 opterr =0;
265 while (-1 != (opt = getopt(argc, argv, "hdvA:D:"))) {
266 switch (opt) {
267 case 'A':
268 safe_free(NTAllowedGroup);
269 NTAllowedGroup=xstrdup(optarg);
270 UseAllowedGroup = 1;
271 break;
272 case 'D':
273 safe_free(NTDisAllowedGroup);
274 NTDisAllowedGroup=xstrdup(optarg);
275 UseDisallowedGroup = 1;
276 break;
277 case 'd':
278 debug_enabled = 1;
279 break;
280 case 'v':
281 debug_enabled = 1;
282 NTLM_packet_debug_enabled = 1;
283 break;
284 case 'h':
285 usage();
286 exit(EXIT_SUCCESS);
287 case '?':
288 opt = optopt;
289 [[fallthrough]];
290 default:
291 fprintf(stderr, "unknown option: -%c. Exiting\n", opt);
292 usage();
293 had_error = 1;
294 }
295 }
296 if (had_error)
297 exit(EXIT_FAILURE);
298 }
299
300 static bool
301 token_decode(size_t *decodedLen, uint8_t decoded[], const char *buf)
302 {
303 struct base64_decode_ctx ctx;
304 base64_decode_init(&ctx);
305 if (!base64_decode_update(&ctx, decodedLen, decoded, strlen(buf), buf) ||
306 !base64_decode_final(&ctx)) {
307 SEND_BH("message=\"base64 decode failed\"");
308 fprintf(stderr, "ERROR: base64 decoding failed for: '%s'\n", buf);
309 return false;
310 }
311 return true;
312 }
313
314 static int
315 manage_request()
316 {
317 ntlmhdr *fast_header;
318 char buf[HELPER_INPUT_BUFFER];
319 uint8_t decoded[HELPER_INPUT_BUFFER];
320 size_t decodedLen = 0;
321 char helper_command[3];
322 int oversized = 0;
323 char * ErrorMessage;
324 static ntlm_negotiate local_nego;
325 char domain[DNLEN+1];
326 char user[UNLEN+1];
327
328 /* NP: for some reason this helper sometimes needs to accept
329 * from clients that send no negotiate packet. */
330 if (memcpy(local_nego.hdr.signature, "NTLMSSP", 8) != 0) {
331 memset(&local_nego, 0, sizeof(ntlm_negotiate)); /* reset */
332 memcpy(local_nego.hdr.signature, "NTLMSSP", 8); /* set the signature */
333 local_nego.hdr.type = le32toh(NTLM_NEGOTIATE); /* this is a challenge */
334 local_nego.flags = le32toh(NTLM_NEGOTIATE_ALWAYS_SIGN |
335 NTLM_NEGOTIATE_USE_NTLM |
336 NTLM_NEGOTIATE_USE_LM |
337 NTLM_NEGOTIATE_ASCII );
338 }
339
340 do {
341 if (fgets(buf, sizeof(buf), stdin) == NULL)
342 return 0;
343
344 char *c = static_cast<char*>(memchr(buf, '\n', sizeof(buf)));
345 if (c) {
346 if (oversized) {
347 helperfail("message=\"illegal request received\"");
348 fprintf(stderr, "Illegal request received: '%s'\n", buf);
349 return 1;
350 }
351 *c = '\0';
352 } else {
353 fprintf(stderr, "No newline in '%s'\n", buf);
354 oversized = 1;
355 continue;
356 }
357 } while (false);
358
359 if ((strlen(buf) > 3) && NTLM_packet_debug_enabled) {
360 if (!token_decode(&decodedLen, decoded, buf+3))
361 return 1;
362 helper_command[0] = buf[0];
363 helper_command[1] = buf[1];
364 helper_command[2] = '\0';
365 debug("Got '%s' from Squid with data:\n", helper_command);
366 hex_dump(reinterpret_cast<unsigned char*>(decoded), decodedLen);
367 } else
368 debug("Got '%s' from Squid\n", buf);
369 if (memcmp(buf, "YR", 2) == 0) { /* refresh-request */
370 /* figure out what we got */
371 if (strlen(buf) > 3) {
372 if (!decodedLen /* already decoded*/ && !token_decode(&decodedLen, decoded, buf+3))
373 return 1;
374 } else {
375 debug("Negotiate packet not supplied - self generated\n");
376 memcpy(decoded, &local_nego, sizeof(local_nego));
377 decodedLen = sizeof(local_nego);
378 }
379 if ((size_t)decodedLen < sizeof(ntlmhdr)) { /* decoding failure, return error */
380 SEND_ERR("message=\"Packet format error\"");
381 return 1;
382 }
383 /* fast-track-decode request type. */
384 fast_header = (struct _ntlmhdr *) decoded;
385
386 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
387 if (ntlm_validate_packet(fast_header, NTLM_ANY) != NtlmError::None) {
388 SEND_ERR("message=\"Broken authentication packet\"");
389 return 1;
390 }
391 switch (fast_header->type) {
392 case NTLM_NEGOTIATE: {
393 /* Obtain challenge against SSPI */
394 debug("attempting SSPI challenge retrieval\n");
395 char *c = (char *) SSP_MakeChallenge((ntlm_negotiate *) decoded, decodedLen);
396 if (c) {
397 SEND_TT(c);
398 if (NTLM_packet_debug_enabled) {
399 if (!token_decode(&decodedLen, decoded, c))
400 return 1;
401 debug("send 'TT' to squid with data:\n");
402 hex_dump(reinterpret_cast<unsigned char*>(decoded), decodedLen);
403 if (NTLM_LocalCall) {
404 debug("NTLM Local Call detected\n");
405 }
406 }
407 have_challenge = 1;
408 } else
409 helperfail("message=\"can't obtain challenge\"");
410
411 return 1;
412 }
413 /* notreached */
414 case NTLM_CHALLENGE:
415 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
416 return 1;
417 /* notreached */
418 case NTLM_AUTHENTICATE:
419 SEND_ERR("message=\"Got authentication request instead of negotiate request\"");
420 return 1;
421 /* notreached */
422 default:
423 helperfail("message=\"unknown refresh-request packet type\"");
424 return 1;
425 }
426 return 1;
427 }
428 if (memcmp(buf, "KK ", 3) == 0) { /* authenticate-request */
429 if (!have_challenge) {
430 helperfail("message=\"invalid challenge\"");
431 return 1;
432 }
433 /* figure out what we got */
434 if (!decodedLen /* already decoded*/ && !token_decode(&decodedLen, decoded, buf+3))
435 return 1;
436
437 if ((size_t)decodedLen < sizeof(ntlmhdr)) { /* decoding failure, return error */
438 SEND_ERR("message=\"Packet format error\"");
439 return 1;
440 }
441 /* fast-track-decode request type. */
442 fast_header = (struct _ntlmhdr *) decoded;
443
444 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
445 if (ntlm_validate_packet(fast_header, NTLM_ANY) != NtlmError::None) {
446 SEND_ERR("message=\"Broken authentication packet\"");
447 return 1;
448 }
449 switch (fast_header->type) {
450 case NTLM_NEGOTIATE:
451 SEND_ERR("message=\"Invalid negotiation request received\"");
452 return 1;
453 /* notreached */
454 case NTLM_CHALLENGE:
455 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
456 return 1;
457 /* notreached */
458 case NTLM_AUTHENTICATE: {
459 /* check against SSPI */
460 const auto err = ntlm_check_auth((ntlm_authenticate *) decoded, user, domain, decodedLen);
461 have_challenge = 0;
462 if (err != NtlmError::None) {
463 #if FAIL_DEBUG
464 fail_debug_enabled =1;
465 #endif
466 switch (err) {
467 case NtlmError::None:
468 break;
469 case NtlmError::BadNtGroup:
470 SEND_ERR("message=\"Incorrect Group Membership\"");
471 return 1;
472 case NtlmError::BadRequest:
473 SEND_ERR("message=\"Incorrect Request Format\"");
474 return 1;
475 case NtlmError::SspiError:
476 FormatMessage(
477 FORMAT_MESSAGE_ALLOCATE_BUFFER |
478 FORMAT_MESSAGE_FROM_SYSTEM |
479 FORMAT_MESSAGE_IGNORE_INSERTS,
480 nullptr,
481 GetLastError(),
482 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
483 (LPTSTR) &ErrorMessage,
484 0,
485 nullptr);
486 if (ErrorMessage[strlen(ErrorMessage) - 1] == '\n')
487 ErrorMessage[strlen(ErrorMessage) - 1] = '\0';
488 if (ErrorMessage[strlen(ErrorMessage) - 1] == '\r')
489 ErrorMessage[strlen(ErrorMessage) - 1] = '\0';
490 SEND_ERR(ErrorMessage); // TODO update to new syntax
491 LocalFree(ErrorMessage);
492 return 1;
493 default:
494 SEND_ERR("message=\"Unknown Error\"");
495 return 1;
496 }
497 }
498 /* let's lowercase them for our convenience */
499 lc(domain);
500 lc(user);
501 fprintf(stdout, "OK user=\"%s\\%s\"", domain, user);
502 return 1;
503 }
504 default:
505 helperfail("message=\"unknown authentication packet type\"");
506 return 1;
507 }
508 return 1;
509 } else { /* not an auth-request */
510 helperfail("message=\"illegal request received\"");
511 fprintf(stderr, "Illegal request received: '%s'\n", buf);
512 return 1;
513 }
514 helperfail("message=\"detected protocol error\"");
515 return 1;
516 /********* END ********/
517 }
518
519 int
520 main(int argc, char *argv[])
521 {
522 my_program_name = argv[0];
523
524 process_options(argc, argv);
525
526 debug("%s " VERSION " " SQUID_BUILD_INFO " starting up...\n", my_program_name);
527
528 if (LoadSecurityDll(SSP_NTLM, NTLM_PACKAGE_NAME) == NULL) {
529 fprintf(stderr, "FATAL, can't initialize SSPI, exiting.\n");
530 exit(EXIT_FAILURE);
531 }
532 debug("SSPI initialized OK\n");
533
534 atexit(UnloadSecurityDll);
535
536 /* initialize FDescs */
537 setbuf(stdout, nullptr);
538 setbuf(stderr, nullptr);
539
540 while (manage_request()) {
541 /* everything is done within manage_request */
542 }
543 return EXIT_SUCCESS;
544 }
545