2 * Copyright (C) 1996-2014 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 "helpers/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
);
419 ntlmhdr
*fast_header
;
420 char buf
[HELPER_INPUT_BUFFER
];
421 char decoded
[HELPER_INPUT_BUFFER
];
423 char helper_command
[3];
426 static ntlm_negotiate local_nego
;
427 char domain
[DNLEN
+1];
430 /* NP: for some reason this helper sometimes needs to accept
431 * from clients that send no negotiate packet. */
432 if (memcpy(local_nego
.hdr
.signature
, "NTLMSSP", 8) != 0) {
433 memset(&local_nego
, 0, sizeof(ntlm_negotiate
)); /* reset */
434 memcpy(local_nego
.hdr
.signature
, "NTLMSSP", 8); /* set the signature */
435 local_nego
.hdr
.type
= le32toh(NTLM_NEGOTIATE
); /* this is a challenge */
436 local_nego
.flags
= le32toh(NTLM_NEGOTIATE_ALWAYS_SIGN
|
437 NTLM_NEGOTIATE_USE_NTLM
|
438 NTLM_NEGOTIATE_USE_LM
|
439 NTLM_NEGOTIATE_ASCII
);
443 if (fgets(buf
, sizeof(buf
), stdin
) == NULL
)
446 char *c
= static_cast<char*>(memchr(buf
, '\n', sizeof(buf
)));
449 helperfail("messge=\"illegal request received\"");
450 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
455 fprintf(stderr
, "No newline in '%s'\n", buf
);
461 if ((strlen(buf
) > 3) && NTLM_packet_debug_enabled
) {
462 decodedLen
= base64_decode(decoded
, sizeof(decoded
), buf
+3);
463 strncpy(helper_command
, buf
, 2);
464 debug("Got '%s' from Squid with data:\n", helper_command
);
465 hex_dump(reinterpret_cast<unsigned char*>(decoded
), decodedLen
);
467 debug("Got '%s' from Squid\n", buf
);
468 if (memcmp(buf
, "YR", 2) == 0) { /* refresh-request */
469 /* figure out what we got */
471 decodedLen
= base64_decode(decoded
, sizeof(decoded
), buf
+3);
473 debug("Negotiate packet not supplied - self generated\n");
474 memcpy(decoded
, &local_nego
, sizeof(local_nego
));
475 decodedLen
= sizeof(local_nego
);
477 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
478 SEND_ERR("message=\"Packet format error, couldn't base64-decode\"");
481 /* fast-track-decode request type. */
482 fast_header
= (struct _ntlmhdr
*) decoded
;
484 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
485 if (ntlm_validate_packet(fast_header
, NTLM_ANY
) != NTLM_ERR_NONE
) {
486 SEND_ERR("message=\"Broken authentication packet\"");
489 switch (fast_header
->type
) {
490 case NTLM_NEGOTIATE
: {
491 /* Obtain challenge against SSPI */
492 debug("attempting SSPI challenge retrieval\n");
493 char *c
= (char *) SSP_MakeChallenge((ntlm_negotiate
*) decoded
, decodedLen
);
496 if (NTLM_packet_debug_enabled
) {
497 decodedLen
= base64_decode(decoded
, sizeof(decoded
), c
);
498 debug("send 'TT' to squid with data:\n");
499 hex_dump(reinterpret_cast<unsigned char*>(decoded
), decodedLen
);
500 if (NTLM_LocalCall
) {
501 debug("NTLM Local Call detected\n");
506 helperfail("message=\"can't obtain challenge\"");
512 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
515 case NTLM_AUTHENTICATE
:
516 SEND_ERR("message=\"Got authentication request instead of negotiate request\"");
520 helperfail("message=\"unknown refresh-request packet type\"");
525 if (memcmp(buf
, "KK ", 3) == 0) { /* authenticate-request */
526 if (!have_challenge
) {
527 helperfail("message=\"invalid challenge\"");
530 /* figure out what we got */
531 decodedLen
= base64_decode(decoded
, sizeof(decoded
), buf
+3);
533 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
534 SEND_ERR("message=\"Packet format error, couldn't base64-decode\"");
537 /* fast-track-decode request type. */
538 fast_header
= (struct _ntlmhdr
*) decoded
;
540 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
541 if (ntlm_validate_packet(fast_header
, NTLM_ANY
) != NTLM_ERR_NONE
) {
542 SEND_ERR("message=\"Broken authentication packet\"");
545 switch (fast_header
->type
) {
547 SEND_ERR("message=\"Invalid negotiation request received\"");
551 SEND_ERR("message=\"Got a challenge. We refuse to have our authority disputed\"");
554 case NTLM_AUTHENTICATE
: {
555 /* check against SSPI */
556 int err
= ntlm_check_auth((ntlm_authenticate
*) decoded
, user
, domain
, decodedLen
);
558 if (err
!= NTLM_ERR_NONE
) {
560 fail_debug_enabled
=1;
565 case NTLM_BAD_NTGROUP
:
566 SEND_ERR("message=\"Incorrect Group Membership\"");
568 case NTLM_BAD_REQUEST
:
569 SEND_ERR("message=\"Incorrect Request Format\"");
571 case NTLM_SSPI_ERROR
:
573 FORMAT_MESSAGE_ALLOCATE_BUFFER
|
574 FORMAT_MESSAGE_FROM_SYSTEM
|
575 FORMAT_MESSAGE_IGNORE_INSERTS
,
578 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
579 (LPTSTR
) &ErrorMessage
,
582 if (ErrorMessage
[strlen(ErrorMessage
) - 1] == '\n')
583 ErrorMessage
[strlen(ErrorMessage
) - 1] = '\0';
584 if (ErrorMessage
[strlen(ErrorMessage
) - 1] == '\r')
585 ErrorMessage
[strlen(ErrorMessage
) - 1] = '\0';
586 SEND_ERR(ErrorMessage
); // TODO update to new syntax
587 LocalFree(ErrorMessage
);
590 SEND_ERR("message=\"Unknown Error\"");
594 /* let's lowercase them for our convenience */
597 fprintf(stdout
, "OK user=\"%s\\%s\"", domain
, user
);
601 helperfail("message=\"unknown authentication packet type\"");
605 } else { /* not an auth-request */
606 helperfail("message=\"illegal request received\"");
607 fprintf(stderr
, "Illegal request received: '%s'\n", buf
);
610 helperfail("message=\"detected protocol error\"");
612 /********* END ********/
616 main(int argc
, char *argv
[])
618 my_program_name
= argv
[0];
620 process_options(argc
, argv
);
622 debug("%s build " __DATE__
", " __TIME__
" starting up...\n", my_program_name
);
624 if (LoadSecurityDll(SSP_NTLM
, NTLM_PACKAGE_NAME
) == NULL
) {
625 fprintf(stderr
, "FATAL, can't initialize SSPI, exiting.\n");
628 debug("SSPI initialized OK\n");
630 atexit(UnloadSecurityDll
);
632 /* initialize FDescs */
633 setbuf(stdout
, NULL
);
634 setbuf(stderr
, NULL
);
636 while (manage_request()) {
637 /* everything is done within manage_request */