2 * Copyright (C) 1996-2017 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"
94 int NTLM_packet_debug_enabled
= 0;
95 static int have_challenge
;
96 char * NTAllowedGroup
;
97 char * NTDisAllowedGroup
;
98 int UseDisallowedGroup
= 0;
99 int UseAllowedGroup
= 0;
102 int fail_debug_enabled
= 0;
105 /* returns 1 on success, 0 on failure */
107 Valid_Group(char *UserName
, char *Group
)
110 WCHAR wszUserName
[UNLEN
+1]; // Unicode user name
111 WCHAR wszGroup
[GNLEN
+1]; // Unicode Group
113 LPLOCALGROUP_USERS_INFO_0 pBuf
= NULL
;
114 LPLOCALGROUP_USERS_INFO_0 pTmpBuf
;
116 DWORD dwFlags
= LG_INCLUDE_INDIRECT
;
117 DWORD dwPrefMaxLen
= -1;
118 DWORD dwEntriesRead
= 0;
119 DWORD dwTotalEntries
= 0;
120 NET_API_STATUS nStatus
;
122 DWORD dwTotalCount
= 0;
124 /* Convert ANSI User Name and Group to Unicode */
126 MultiByteToWideChar(CP_ACP
, 0, UserName
,
127 strlen(UserName
) + 1, wszUserName
,
128 sizeof(wszUserName
) / sizeof(wszUserName
[0]));
129 MultiByteToWideChar(CP_ACP
, 0, Group
,
130 strlen(Group
) + 1, wszGroup
, sizeof(wszGroup
) / sizeof(wszGroup
[0]));
133 * Call the NetUserGetLocalGroups function
134 * specifying information level 0.
136 * The LG_INCLUDE_INDIRECT flag specifies that the
137 * function should also return the names of the local
138 * groups in which the user is indirectly a member.
140 nStatus
= NetUserGetLocalGroups(NULL
,
144 (LPBYTE
*) & pBuf
, dwPrefMaxLen
, &dwEntriesRead
, &dwTotalEntries
);
146 * If the call succeeds,
148 if (nStatus
== NERR_Success
) {
149 if ((pTmpBuf
= pBuf
) != NULL
) {
150 for (i
= 0; i
< dwEntriesRead
; ++i
) {
151 if (pTmpBuf
== NULL
) {
155 if (wcscmp(pTmpBuf
->lgrui0_name
, wszGroup
) == 0) {
166 * Free the allocated memory.
169 NetApiBufferFree(pBuf
);
173 char * AllocStrFromLSAStr(LSA_UNICODE_STRING LsaStr
)
176 static char * target
;
178 len
= LsaStr
.Length
/sizeof(WCHAR
) + 1;
180 /* allocate buffer for str + null termination */
182 target
= (char *)xmalloc(len
);
186 /* copy unicode buffer */
187 WideCharToMultiByte(CP_ACP
, 0, LsaStr
.Buffer
, LsaStr
.Length
, target
, len
, NULL
, NULL
);
189 /* add null termination */
190 target
[len
-1] = '\0';
194 char * GetDomainName(void)
197 LSA_HANDLE PolicyHandle
;
198 LSA_OBJECT_ATTRIBUTES ObjectAttributes
;
200 PPOLICY_PRIMARY_DOMAIN_INFO ppdiDomainInfo
;
201 PWKSTA_INFO_100 pwkiWorkstationInfo
;
203 char * DomainName
= NULL
;
206 * Always initialize the object attributes to all zeroes.
208 memset(&ObjectAttributes
, '\0', sizeof(ObjectAttributes
));
211 * You need the local workstation name. Use NetWkstaGetInfo at level
212 * 100 to retrieve a WKSTA_INFO_100 structure.
214 * The wki100_computername field contains a pointer to a UNICODE
215 * string containing the local computer name.
217 netret
= NetWkstaGetInfo(NULL
, 100, (LPBYTE
*)&pwkiWorkstationInfo
);
218 if (netret
== NERR_Success
) {
220 * We have the workstation name in:
221 * pwkiWorkstationInfo->wki100_computername
223 * Next, open the policy object for the local system using
224 * the LsaOpenPolicy function.
226 status
= LsaOpenPolicy(
229 GENERIC_READ
| POLICY_VIEW_LOCAL_INFORMATION
,
237 debug("OpenPolicy Error: %ld\n", status
);
241 * You have a handle to the policy object. Now, get the
242 * domain information using LsaQueryInformationPolicy.
244 status
= LsaQueryInformationPolicy(PolicyHandle
,
245 PolicyPrimaryDomainInformation
,
246 (void **)&ppdiDomainInfo
);
248 debug("LsaQueryInformationPolicy Error: %ld\n", status
);
251 /* Get name in useable format */
252 DomainName
= AllocStrFromLSAStr(ppdiDomainInfo
->Name
);
255 * Check the Sid pointer, if it is null, the
256 * workstation is either a stand-alone computer
257 * or a member of a workgroup.
259 if (ppdiDomainInfo
->Sid
) {
262 * Member of a domain. Display it in debug mode.
264 debug("Member of Domain %s\n",DomainName
);
272 * Clean up all the memory buffers created by the LSA and
275 NetApiBufferFree(pwkiWorkstationInfo
);
276 LsaFreeMemory((LPVOID
)ppdiDomainInfo
);
278 debug("NetWkstaGetInfo Error: %ld\n", netret
);
283 * Fills auth with the user's credentials.
285 * In case of problem returns one of the
286 * codes defined in libntlmauth/ntlmauth.h
289 ntlm_check_auth(ntlm_authenticate
* auth
, char *user
, char *domain
, int auth_length
)
293 char credentials
[DNLEN
+UNLEN
+2]; /* we can afford to waste */
295 if (!NTLM_LocalCall
) {
299 x
= ntlm_unpack_auth(auth
, user
, domain
, auth_length
);
301 if (x
!= NTLM_ERR_NONE
)
304 if (domain
[0] == '\0') {
305 debug("No domain supplied. Returning no-auth\n");
306 return NTLM_BAD_REQUEST
;
308 if (user
[0] == '\0') {
309 debug("No username supplied. Returning no-auth\n");
310 return NTLM_BAD_REQUEST
;
312 debug("checking domain: '%s', user: '%s'\n", domain
, user
);
315 debug("checking local user\n");
317 snprintf(credentials
, DNLEN
+UNLEN
+2, "%s\\%s", domain
, user
);
319 rv
= SSP_ValidateNTLMCredentials(auth
, auth_length
, credentials
);
321 debug("Login attempt had result %d\n", rv
);
323 if (!rv
) { /* failed */
324 return NTLM_SSPI_ERROR
;
327 if (UseAllowedGroup
) {
328 if (!Valid_Group(credentials
, NTAllowedGroup
)) {
329 debug("User %s not in allowed Group %s\n", credentials
, NTAllowedGroup
);
330 return NTLM_BAD_NTGROUP
;
333 if (UseDisallowedGroup
) {
334 if (Valid_Group(credentials
, NTDisAllowedGroup
)) {
335 debug("User %s is in denied Group %s\n", credentials
, NTDisAllowedGroup
);
336 return NTLM_BAD_NTGROUP
;
340 debug("credentials: %s\n", credentials
);
341 return NTLM_ERR_NONE
;
345 helperfail(const char *reason
)
348 fail_debug_enabled
=1;
356 -v enable verbose NTLM packet debugging.
357 -A can specify a Windows Local Group name allowed to authenticate.
358 -D can specify a Windows Local Group name not allowed to authenticate.
360 char *my_program_name
= NULL
;
366 "Usage: %s [-d] [-v] [-A|D LocalUserGroup] [-h]\n"
367 " -d enable debugging.\n"
368 " -v enable verbose NTLM packet debugging.\n"
369 " -A specify a Windows Local Group name allowed to authenticate\n"
370 " -D specify a Windows Local Group name not allowed to authenticate\n"
371 " -h this message\n\n",
376 process_options(int argc
, char *argv
[])
378 int opt
, had_error
= 0;
381 while (-1 != (opt
= getopt(argc
, argv
, "hdvA:D:"))) {
384 safe_free(NTAllowedGroup
);
385 NTAllowedGroup
=xstrdup(optarg
);
389 safe_free(NTDisAllowedGroup
);
390 NTDisAllowedGroup
=xstrdup(optarg
);
391 UseDisallowedGroup
= 1;
398 NTLM_packet_debug_enabled
= 1;
405 /* fall thru to default */
407 fprintf(stderr
, "unknown option: -%c. Exiting\n", opt
);
417 token_decode(size_t *decodedLen
, uint8_t decoded
[], const char *buf
)
419 struct base64_decode_ctx ctx
;
420 base64_decode_init(&ctx
);
421 if (!base64_decode_update(&ctx
, decodedLen
, decoded
, strlen(buf
), reinterpret_cast<const uint8_t*>(buf
)) ||
422 !base64_decode_final(&ctx
)) {
423 SEND_BH("message=\"base64 decode failed\"");
424 fprintf(stderr
, "ERROR: base64 decoding failed for: '%s'\n", buf
);
433 ntlmhdr
*fast_header
;
434 char buf
[HELPER_INPUT_BUFFER
];
435 uint8_t decoded
[HELPER_INPUT_BUFFER
];
436 size_t decodedLen
= 0;
437 char helper_command
[3];
440 static ntlm_negotiate local_nego
;
441 char domain
[DNLEN
+1];
444 /* NP: for some reason this helper sometimes needs to accept
445 * from clients that send no negotiate packet. */
446 if (memcpy(local_nego
.hdr
.signature
, "NTLMSSP", 8) != 0) {
447 memset(&local_nego
, 0, sizeof(ntlm_negotiate
)); /* reset */
448 memcpy(local_nego
.hdr
.signature
, "NTLMSSP", 8); /* set the signature */
449 local_nego
.hdr
.type
= le32toh(NTLM_NEGOTIATE
); /* this is a challenge */
450 local_nego
.flags
= le32toh(NTLM_NEGOTIATE_ALWAYS_SIGN
|
451 NTLM_NEGOTIATE_USE_NTLM
|
452 NTLM_NEGOTIATE_USE_LM
|
453 NTLM_NEGOTIATE_ASCII
);
457 if (fgets(buf
, sizeof(buf
), stdin
) == NULL
)
460 char *c
= static_cast<char*>(memchr(buf
, '\n', sizeof(buf
)));
463 helperfail("messge=\"illegal request received\"");
464 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
469 fprintf(stderr
, "No newline in '%s'\n", buf
);
475 if ((strlen(buf
) > 3) && NTLM_packet_debug_enabled
) {
476 if (!token_decode(&decodedLen
, decoded
, buf
+3))
478 strncpy(helper_command
, buf
, 2);
479 debug("Got '%s' from Squid with data:\n", helper_command
);
480 hex_dump(reinterpret_cast<unsigned char*>(decoded
), decodedLen
);
482 debug("Got '%s' from Squid\n", buf
);
483 if (memcmp(buf
, "YR", 2) == 0) { /* refresh-request */
484 /* figure out what we got */
485 if (strlen(buf
) > 3) {
486 if (!decodedLen
/* already decoded*/ && !token_decode(&decodedLen
, decoded
, buf
+3))
489 debug("Negotiate packet not supplied - self generated\n");
490 memcpy(decoded
, &local_nego
, sizeof(local_nego
));
491 decodedLen
= sizeof(local_nego
);
493 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
494 SEND_ERR("message=\"Packet format error\"");
497 /* fast-track-decode request type. */
498 fast_header
= (struct _ntlmhdr
*) decoded
;
500 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
501 if (ntlm_validate_packet(fast_header
, NTLM_ANY
) != NTLM_ERR_NONE
) {
502 SEND_ERR("message=\"Broken authentication packet\"");
505 switch (fast_header
->type
) {
506 case NTLM_NEGOTIATE
: {
507 /* Obtain challenge against SSPI */
508 debug("attempting SSPI challenge retrieval\n");
509 char *c
= (char *) SSP_MakeChallenge((ntlm_negotiate
*) decoded
, decodedLen
);
512 if (NTLM_packet_debug_enabled
) {
513 if (!token_decode(&decodedLen
, decoded
, c
))
515 debug("send 'TT' to squid with data:\n");
516 hex_dump(reinterpret_cast<unsigned char*>(decoded
), decodedLen
);
517 if (NTLM_LocalCall
) {
518 debug("NTLM Local Call detected\n");
523 helperfail("message=\"can't obtain challenge\"");
529 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
532 case NTLM_AUTHENTICATE
:
533 SEND_ERR("message=\"Got authentication request instead of negotiate request\"");
537 helperfail("message=\"unknown refresh-request packet type\"");
542 if (memcmp(buf
, "KK ", 3) == 0) { /* authenticate-request */
543 if (!have_challenge
) {
544 helperfail("message=\"invalid challenge\"");
547 /* figure out what we got */
548 if (!decodedLen
/* already decoded*/ && !token_decode(&decodedLen
, decoded
, buf
+3))
551 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
552 SEND_ERR("message=\"Packet format error\"");
555 /* fast-track-decode request type. */
556 fast_header
= (struct _ntlmhdr
*) decoded
;
558 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
559 if (ntlm_validate_packet(fast_header
, NTLM_ANY
) != NTLM_ERR_NONE
) {
560 SEND_ERR("message=\"Broken authentication packet\"");
563 switch (fast_header
->type
) {
565 SEND_ERR("message=\"Invalid negotiation request received\"");
569 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
572 case NTLM_AUTHENTICATE
: {
573 /* check against SSPI */
574 int err
= ntlm_check_auth((ntlm_authenticate
*) decoded
, user
, domain
, decodedLen
);
576 if (err
!= NTLM_ERR_NONE
) {
578 fail_debug_enabled
=1;
583 case NTLM_BAD_NTGROUP
:
584 SEND_ERR("message=\"Incorrect Group Membership\"");
586 case NTLM_BAD_REQUEST
:
587 SEND_ERR("message=\"Incorrect Request Format\"");
589 case NTLM_SSPI_ERROR
:
591 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
592 FORMAT_MESSAGE_FROM_SYSTEM
|
593 FORMAT_MESSAGE_IGNORE_INSERTS
,
596 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
597 (LPTSTR
) &ErrorMessage
,
600 if (ErrorMessage
[strlen(ErrorMessage
) - 1] == '\n')
601 ErrorMessage
[strlen(ErrorMessage
) - 1] = '\0';
602 if (ErrorMessage
[strlen(ErrorMessage
) - 1] == '\r')
603 ErrorMessage
[strlen(ErrorMessage
) - 1] = '\0';
604 SEND_ERR(ErrorMessage
); // TODO update to new syntax
605 LocalFree(ErrorMessage
);
608 SEND_ERR("message=\"Unknown Error\"");
612 /* let's lowercase them for our convenience */
615 fprintf(stdout
, "OK user=\"%s\\%s\"", domain
, user
);
619 helperfail("message=\"unknown authentication packet type\"");
623 } else { /* not an auth-request */
624 helperfail("message=\"illegal request received\"");
625 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
628 helperfail("message=\"detected protocol error\"");
630 /********* END ********/
634 main(int argc
, char *argv
[])
636 my_program_name
= argv
[0];
638 process_options(argc
, argv
);
640 debug("%s " VERSION
" " SQUID_BUILD_INFO
" starting up...\n", my_program_name
);
642 if (LoadSecurityDll(SSP_NTLM
, NTLM_PACKAGE_NAME
) == NULL
) {
643 fprintf(stderr
, "FATAL, can't initialize SSPI, exiting.\n");
646 debug("SSPI initialized OK\n");
648 atexit(UnloadSecurityDll
);
650 /* initialize FDescs */
651 setbuf(stdout
, NULL
);
652 setbuf(stderr
, NULL
);
654 while (manage_request()) {
655 /* everything is done within manage_request */