2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
10 * ntlm_sspi_auth: helper for NTLM Authentication for Squid Cache
12 * (C)2002,2005 Guido Serassio - Acme Consulting S.r.l.
15 * Guido Serassio <guido.serassio@acmeconsulting.it>
16 * Acme Consulting S.r.l., Italy <http://www.acmeconsulting.it>
18 * With contributions from others mentioned in the change history section
21 * Based on previous work of Francesco Chemolli and Robert Collins.
23 * Dependencies: Windows NT4 SP4 and later.
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.
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.
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.
42 * 29-10-2005 Guido Serassio
43 * Updated for Negotiate auth support.
45 * 21-02-2004 Guido Serassio
46 * Removed control of use of NTLM NEGOTIATE packet from
47 * command line, now the support is automatic.
49 * 30-11-2003 Guido Serassio
50 * Added support for NTLM local calls.
51 * Added control of use of NTLM NEGOTIATE packet from
53 * Updated documentation.
55 * 07-09-2003 Guido Serassio
56 * Now is true NTLM authenticator.
58 * Updated documentation.
60 * 29-06-2002 Guido Serassio
66 /************* CONFIGURATION ***************/
70 /************* END CONFIGURATION ***************/
72 //typedef unsigned char uchar;
76 #include "helper/protocol_defines.h"
77 #include "ntlmauth/ntlmauth.h"
78 #include "ntlmauth/support_bits.cci"
79 #include "sspi/sspwin32.h"
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;
96 int fail_debug_enabled
= 0;
99 /* returns 1 on success, 0 on failure */
101 Valid_Group(char *UserName
, char *Group
)
104 WCHAR wszUserName
[UNLEN
+1]; // Unicode user name
105 WCHAR wszGroup
[GNLEN
+1]; // Unicode Group
107 LPLOCALGROUP_USERS_INFO_0 pBuf
= nullptr;
108 LPLOCALGROUP_USERS_INFO_0 pTmpBuf
;
110 DWORD dwFlags
= LG_INCLUDE_INDIRECT
;
111 DWORD dwPrefMaxLen
= -1;
112 DWORD dwEntriesRead
= 0;
113 DWORD dwTotalEntries
= 0;
114 NET_API_STATUS nStatus
;
116 DWORD dwTotalCount
= 0;
118 /* Convert ANSI User Name and Group to Unicode */
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]));
127 * Call the NetUserGetLocalGroups function
128 * specifying information level 0.
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.
134 nStatus
= NetUserGetLocalGroups(nullptr,
138 (LPBYTE
*) & pBuf
, dwPrefMaxLen
, &dwEntriesRead
, &dwTotalEntries
);
140 * If the call succeeds,
142 if (nStatus
== NERR_Success
) {
143 if ((pTmpBuf
= pBuf
) != NULL
) {
144 for (i
= 0; i
< dwEntriesRead
; ++i
) {
145 if (pTmpBuf
== NULL
) {
149 if (wcscmp(pTmpBuf
->lgrui0_name
, wszGroup
) == 0) {
160 * Free the allocated memory.
163 NetApiBufferFree(pBuf
);
168 * Fills auth with the user's credentials.
170 * In case of problem returns one of the
171 * codes defined in libntlmauth/ntlmauth.h
174 ntlm_check_auth(ntlm_authenticate
* auth
, char *user
, char *domain
, int auth_length
)
176 char credentials
[DNLEN
+UNLEN
+2]; /* we can afford to waste */
178 if (!NTLM_LocalCall
) {
182 const auto x
= ntlm_unpack_auth(auth
, user
, domain
, auth_length
);
184 if (x
!= NtlmError::None
)
187 if (domain
[0] == '\0') {
188 debug("No domain supplied. Returning no-auth\n");
189 return NtlmError::BadRequest
;
191 if (user
[0] == '\0') {
192 debug("No username supplied. Returning no-auth\n");
193 return NtlmError::BadRequest
;
195 debug("checking domain: '%s', user: '%s'\n", domain
, user
);
198 debug("checking local user\n");
201 snprintf(credentials
, DNLEN
+UNLEN
+2, "%s\\%s", domain
, user
);
203 const auto rv
= SSP_ValidateNTLMCredentials(auth
, auth_length
, credentials
);
205 debug("Login attempt had result %d\n", rv
);
207 if (!rv
) { /* failed */
208 return NtlmError::SspiError
;
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
;
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
;
224 debug("credentials: %s\n", credentials
);
225 return NtlmError::None
;
229 helperfail(const char *reason
)
232 fail_debug_enabled
=1;
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.
244 char *my_program_name
= nullptr;
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",
260 process_options(int argc
, char *argv
[])
262 int opt
, had_error
= 0;
265 while (-1 != (opt
= getopt(argc
, argv
, "hdvA:D:"))) {
268 safe_free(NTAllowedGroup
);
269 NTAllowedGroup
=xstrdup(optarg
);
273 safe_free(NTDisAllowedGroup
);
274 NTDisAllowedGroup
=xstrdup(optarg
);
275 UseDisallowedGroup
= 1;
282 NTLM_packet_debug_enabled
= 1;
291 fprintf(stderr
, "unknown option: -%c. Exiting\n", opt
);
301 token_decode(size_t *decodedLen
, uint8_t decoded
[], const char *buf
)
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
);
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];
324 static ntlm_negotiate local_nego
;
325 char domain
[DNLEN
+1];
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
);
341 if (fgets(buf
, sizeof(buf
), stdin
) == NULL
)
344 char *c
= static_cast<char*>(memchr(buf
, '\n', sizeof(buf
)));
347 helperfail("message=\"illegal request received\"");
348 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
353 fprintf(stderr
, "No newline in '%s'\n", buf
);
359 if ((strlen(buf
) > 3) && NTLM_packet_debug_enabled
) {
360 if (!token_decode(&decodedLen
, decoded
, buf
+3))
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
);
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))
375 debug("Negotiate packet not supplied - self generated\n");
376 memcpy(decoded
, &local_nego
, sizeof(local_nego
));
377 decodedLen
= sizeof(local_nego
);
379 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
380 SEND_ERR("message=\"Packet format error\"");
383 /* fast-track-decode request type. */
384 fast_header
= (struct _ntlmhdr
*) decoded
;
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\"");
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
);
398 if (NTLM_packet_debug_enabled
) {
399 if (!token_decode(&decodedLen
, decoded
, c
))
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");
409 helperfail("message=\"can't obtain challenge\"");
415 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
418 case NTLM_AUTHENTICATE
:
419 SEND_ERR("message=\"Got authentication request instead of negotiate request\"");
423 helperfail("message=\"unknown refresh-request packet type\"");
428 if (memcmp(buf
, "KK ", 3) == 0) { /* authenticate-request */
429 if (!have_challenge
) {
430 helperfail("message=\"invalid challenge\"");
433 /* figure out what we got */
434 if (!decodedLen
/* already decoded*/ && !token_decode(&decodedLen
, decoded
, buf
+3))
437 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
438 SEND_ERR("message=\"Packet format error\"");
441 /* fast-track-decode request type. */
442 fast_header
= (struct _ntlmhdr
*) decoded
;
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\"");
449 switch (fast_header
->type
) {
451 SEND_ERR("message=\"Invalid negotiation request received\"");
455 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
458 case NTLM_AUTHENTICATE
: {
459 /* check against SSPI */
460 const auto err
= ntlm_check_auth((ntlm_authenticate
*) decoded
, user
, domain
, decodedLen
);
462 if (err
!= NtlmError::None
) {
464 fail_debug_enabled
=1;
467 case NtlmError::None
:
469 case NtlmError::BadNtGroup
:
470 SEND_ERR("message=\"Incorrect Group Membership\"");
472 case NtlmError::BadRequest
:
473 SEND_ERR("message=\"Incorrect Request Format\"");
475 case NtlmError::SspiError
:
477 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
478 FORMAT_MESSAGE_FROM_SYSTEM
|
479 FORMAT_MESSAGE_IGNORE_INSERTS
,
482 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
483 (LPTSTR
) &ErrorMessage
,
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
);
494 SEND_ERR("message=\"Unknown Error\"");
498 /* let's lowercase them for our convenience */
501 fprintf(stdout
, "OK user=\"%s\\%s\"", domain
, user
);
505 helperfail("message=\"unknown authentication packet type\"");
509 } else { /* not an auth-request */
510 helperfail("message=\"illegal request received\"");
511 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
514 helperfail("message=\"detected protocol error\"");
516 /********* END ********/
520 main(int argc
, char *argv
[])
522 my_program_name
= argv
[0];
524 process_options(argc
, argv
);
526 debug("%s " VERSION
" " SQUID_BUILD_INFO
" starting up...\n", my_program_name
);
528 if (LoadSecurityDll(SSP_NTLM
, NTLM_PACKAGE_NAME
) == NULL
) {
529 fprintf(stderr
, "FATAL, can't initialize SSPI, exiting.\n");
532 debug("SSPI initialized OK\n");
534 atexit(UnloadSecurityDll
);
536 /* initialize FDescs */
537 setbuf(stdout
, nullptr);
538 setbuf(stderr
, nullptr);
540 while (manage_request()) {
541 /* everything is done within manage_request */