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 * (C) 2000 Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it>
11 * Distributed freely under the terms of the GNU General Public License,
12 * version 2 or later. See the file COPYING for licensing details
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
26 #include "compat/debug.h"
27 #include "helper/protocol_defines.h"
28 #include "ntlmauth/ntlmauth.h"
29 #include "ntlmauth/support_bits.cci"
30 #include "rfcnb/rfcnb.h"
31 #include "smblib/smblib.h"
50 /************* CONFIGURATION ***************/
52 #define DEAD_DC_RETRY_INTERVAL 30
54 /************* END CONFIGURATION ***************/
56 /* A couple of harmless helper macros */
57 #define SEND(X) debug("sending '%s' to squid\n",X); printf(X "\n");
59 #define SEND2(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
60 #define SEND3(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
62 /* no gcc, no debugging. varargs macros are a gcc extension */
67 const char *make_challenge(char *domain
, char *controller
);
68 char *ntlm_check_auth(ntlm_authenticate
* auth
, int auth_length
);
69 void dc_disconnect(void);
71 int is_dc_ok(char *domain
, char *domain_controller
);
73 typedef struct _dc dc
;
77 time_t dead
; /* 0 if it's alive, otherwise time of death */
83 void process_options(int argc
, char *argv
[]);
84 const char * obtain_challenge(void);
85 void manage_request(void);
87 #define ENCODED_PASS_LEN 24
88 #define MAX_USERNAME_LEN 255
89 #define MAX_DOMAIN_LEN 255
90 #define MAX_PASSWD_LEN 31
92 static unsigned char challenge
[NTLM_NONCE_LEN
];
93 static unsigned char lmencoded_empty_pass
[ENCODED_PASS_LEN
],
94 ntencoded_empty_pass
[ENCODED_PASS_LEN
];
95 SMB_Handle_Type handle
= NULL
;
97 static char credentials
[MAX_USERNAME_LEN
+MAX_DOMAIN_LEN
+2]; /* we can afford to waste */
98 static char my_domain
[100], my_domain_controller
[100];
99 static char errstr
[1001];
101 char error_messages_buffer
[NTLM_BLOB_BUFFER_SIZE
];
103 char load_balance
= 0, protocol_pedantic
= 0;
104 dc
*controllers
= NULL
;
105 int numcontrollers
= 0;
107 char smb_error_buffer
[1000];
109 /* Disconnects from the DC. A reconnection will be done upon the next request
115 SMB_Discon(handle
, 0);
122 return (handle
!= NULL
);
125 /* Tries to connect to a DC. Returns 0 on failure, 1 on OK */
127 is_dc_ok(char *domain
, char *domain_controller
)
129 SMB_Handle_Type h
= SMB_Connect_Server(NULL
, domain_controller
, domain
);
136 /* returns 0 on success, > 0 on failure */
138 init_challenge(char *domain
, char *domain_controller
)
142 if (handle
!= NULL
) {
145 debug("Connecting to server %s domain %s\n", domain_controller
, domain
);
146 handle
= SMB_Connect_Server(NULL
, domain_controller
, domain
);
147 smberr
= SMB_Get_Last_Error();
148 SMB_Get_Error_Msg(smberr
, errstr
, 1000);
150 if (handle
== NULL
) { /* couldn't connect */
151 debug("Couldn't connect to SMB Server. Error:%s\n", errstr
);
154 if (SMB_Negotiate(handle
, SMB_Prots
) < 0) { /* An error */
155 debug("Error negotiating protocol with SMB Server\n");
156 SMB_Discon(handle
, 0);
160 if (handle
->Security
== 0) { /* share-level security, unusable */
161 debug("SMB Server uses share-level security .. we need user security.\n");
162 SMB_Discon(handle
, 0);
166 memcpy(challenge
, handle
->Encrypt_Key
, NTLM_NONCE_LEN
);
167 SMBencrypt((unsigned char *)"",challenge
,lmencoded_empty_pass
);
168 SMBNTencrypt((unsigned char *)"",challenge
,ntencoded_empty_pass
);
173 make_challenge(char *domain
, char *domain_controller
)
175 /* trying to circumvent some strange problem with pointers in SMBLib */
176 /* Ugly as hell, but the lib is going to be dropped... */
177 strncpy(my_domain
, domain
, sizeof(my_domain
)-1);
178 my_domain
[sizeof(my_domain
)-1] = '\0';
179 strncpy(my_domain_controller
, domain_controller
, sizeof(my_domain_controller
)-1);
180 my_domain_controller
[sizeof(my_domain_controller
)-1] = '\0';
182 if (init_challenge(my_domain
, my_domain_controller
) > 0) {
187 uint32_t flags
= NTLM_REQUEST_NON_NT_SESSION_KEY
|
188 NTLM_CHALLENGE_TARGET_IS_DOMAIN
|
189 NTLM_NEGOTIATE_ALWAYS_SIGN
|
190 NTLM_NEGOTIATE_USE_NTLM
|
191 NTLM_NEGOTIATE_USE_LM
|
192 NTLM_NEGOTIATE_ASCII
;
193 ntlm_make_challenge(&chal
, my_domain
, my_domain_controller
, (char *)challenge
, NTLM_NONCE_LEN
, flags
);
195 size_t len
= sizeof(chal
) - sizeof(chal
.payload
) + le16toh(chal
.target
.maxlen
);
196 // for lack of a good NTLM token size limit, allow up to what the helper input can be
197 // validations later will expect to be limited to that size.
198 static char b64buf
[HELPER_INPUT_BUFFER
-10]; /* 10 for other line fields, delimiters and terminator */
199 if (base64_encode_len(len
) < sizeof(b64buf
)-1) {
200 debug("base64 encoding of the token challenge will exceed %" PRIuSIZE
" bytes", sizeof(b64buf
));
204 struct base64_encode_ctx ctx
;
205 base64_encode_init(&ctx
);
206 size_t blen
= base64_encode_update(&ctx
, b64buf
, len
, reinterpret_cast<const uint8_t *>(&chal
));
207 blen
+= base64_encode_final(&ctx
, b64buf
+blen
);
212 /* returns NULL on failure, or a pointer to
213 * the user's credentials (domain\\username)
214 * upon success. WARNING. It's pointing to static storage.
215 * In case of problem sets as side-effect ntlm_errno to one of the
216 * codes defined in ntlm.h
219 ntlm_check_auth(ntlm_authenticate
* auth
, int auth_length
)
222 char pass
[MAX_PASSWD_LEN
+1];
223 char *domain
= credentials
;
227 if (handle
== NULL
) { /*if null we aren't connected, but it shouldn't happen */
228 debug("Weird, we've been disconnected\n");
229 ntlm_errno
= NTLM_ERR_NOT_CONNECTED
;
233 /* debug("fetching domain\n"); */
234 tmp
= ntlm_fetch_string(&(auth
->hdr
), auth_length
, &auth
->domain
, auth
->flags
);
235 if (tmp
.str
== NULL
|| tmp
.l
== 0) {
236 debug("No domain supplied. Returning no-auth\n");
237 ntlm_errno
= NTLM_ERR_LOGON
;
240 if (tmp
.l
> MAX_DOMAIN_LEN
) {
241 debug("Domain string exceeds %d bytes, rejecting\n", MAX_DOMAIN_LEN
);
242 ntlm_errno
= NTLM_ERR_LOGON
;
245 memcpy(domain
, tmp
.str
, tmp
.l
);
246 user
= domain
+ tmp
.l
;
250 /* debug("fetching user name\n"); */
251 tmp
= ntlm_fetch_string(&(auth
->hdr
), auth_length
, &auth
->user
, auth
->flags
);
252 if (tmp
.str
== NULL
|| tmp
.l
== 0) {
253 debug("No username supplied. Returning no-auth\n");
254 ntlm_errno
= NTLM_ERR_LOGON
;
257 if (tmp
.l
> MAX_USERNAME_LEN
) {
258 debug("Username string exceeds %d bytes, rejecting\n", MAX_USERNAME_LEN
);
259 ntlm_errno
= NTLM_ERR_LOGON
;
262 memcpy(user
, tmp
.str
, tmp
.l
);
263 *(user
+ tmp
.l
) = '\0';
265 // grab the *response blobs. these are fixed length 24 bytes of binary
266 const ntlmhdr
*packet
= &(auth
->hdr
);
268 const strhdr
* str
= &auth
->lmresponse
;
270 int16_t len
= le16toh(str
->len
);
271 int32_t offset
= le32toh(str
->offset
);
273 if (len
!= ENCODED_PASS_LEN
|| offset
+ len
> auth_length
|| offset
== 0) {
274 debug("LM response: insane data (pkt-sz: %d, fetch len: %d, offset: %d)\n", auth_length
, len
, offset
);
275 ntlm_errno
= NTLM_ERR_LOGON
;
278 tmp
.str
= (char *)packet
+ offset
;
281 if (tmp
.l
> MAX_PASSWD_LEN
) {
282 debug("Password string exceeds %d bytes, rejecting\n", MAX_PASSWD_LEN
);
283 ntlm_errno
= NTLM_ERR_LOGON
;
287 /* Authenticating against the NT response doesn't seem to work... in SMB LM helper. */
288 memcpy(pass
, tmp
.str
, tmp
.l
);
289 pass
[min(MAX_PASSWD_LEN
,tmp
.l
)] = '\0';
291 debug("Empty LM pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
292 user
,lmencoded_empty_pass
,tmp
.str
,tmp
.l
);
293 if (memcmp(tmp
.str
,lmencoded_empty_pass
,ENCODED_PASS_LEN
)==0) {
294 fprintf(stderr
,"Empty LM password supplied for user %s\\%s. "
295 "No-auth\n",domain
,user
);
296 ntlm_errno
=NTLM_ERR_LOGON
;
300 /* still fetch the NT response and check validity against empty password */
302 const strhdr
* str
= &auth
->ntresponse
;
303 int16_t len
= le16toh(str
->len
);
304 // NT response field may be absent. that is okay.
306 int32_t offset
= le32toh(str
->offset
);
308 if (len
!= ENCODED_PASS_LEN
|| offset
+ len
> auth_length
|| offset
== 0) {
309 debug("NT response: insane data (pkt-sz: %d, fetch len: %d, offset: %d)\n", auth_length
, len
, offset
);
310 ntlm_errno
= NTLM_ERR_LOGON
;
313 tmp
.str
= (char *)packet
+ offset
;
316 debug("Empty NT pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
317 user
,ntencoded_empty_pass
,tmp
.str
,tmp
.l
);
318 if (memcmp(tmp
.str
,lmencoded_empty_pass
,ENCODED_PASS_LEN
)==0) {
319 fprintf(stderr
,"ERROR: Empty NT password supplied for user %s\\%s. No-auth\n", domain
, user
);
320 ntlm_errno
= NTLM_ERR_LOGON
;
326 debug("checking domain: '%s', user: '%s', pass='%s'\n", domain
, user
, pass
);
328 rv
= SMB_Logon_Server(handle
, user
, pass
, domain
, 1);
329 debug("Login attempt had result %d\n", rv
);
331 if (rv
!= NTLM_ERR_NONE
) { /* failed */
335 *(user
- 1) = '\\'; /* hack. Performing, but ugly. */
337 debug("credentials: %s\n", credentials
);
341 extern "C" void timeout_during_auth(int signum
);
343 static char got_timeout
= 0;
344 /** signal handler to be invoked when the authentication operation
347 timeout_during_auth(int)
354 * -b try load-balancing the domain-controllers
355 * -f fail-over to another DC if DC connection fails.
356 * DEPRECATED and VERBOSELY IGNORED. This is on by default now.
358 * domain\controller ...
360 char *my_program_name
= NULL
;
366 "%s usage:\n%s [-b] [-f] [-d] [-l] domain\\controller [domain\\controller ...]\n"
367 "-b enables load-balancing among controllers\n"
368 "-f enables failover among controllers (DEPRECATED and always active)\n"
369 "-d enables debugging statements if DEBUG was defined at build-time.\n\n"
370 "You MUST specify at least one Domain Controller.\n"
371 "You can use either \\ or / as separator between the domain name \n"
372 "and the controller name\n",
373 my_program_name
, my_program_name
);
376 /* int debug_enabled=0; defined in libcompat */
379 process_options(int argc
, char *argv
[])
381 int opt
, j
, had_error
= 0;
382 dc
*new_dc
= NULL
, *last_dc
= NULL
;
383 while (-1 != (opt
= getopt(argc
, argv
, "bfld"))) {
390 "WARNING. The -f flag is DEPRECATED and always active.\n");
396 fprintf(stderr
, "unknown option: -%c. Exiting\n", opt
);
403 /* Okay, now begin filling controllers up */
404 /* we can avoid memcpy-ing, and just reuse argv[] */
405 for (j
= optind
; j
< argc
; ++j
) {
407 /* d will not be freed in case of non-error. Since we don't reconfigure,
408 * it's going to live as long as the process anyways */
409 d
= static_cast<char*>(xmalloc(strlen(argv
[j
]) + 1));
411 debug("Adding domain-controller %s\n", d
);
412 if (NULL
== (c
= strchr(d
, '\\')) && NULL
== (c
= strchr(d
, '/'))) {
413 fprintf(stderr
, "Couldn't grok domain-controller %s\n", d
);
417 /* more than one delimiter is not allowed */
418 if (NULL
!= strchr(c
+ 1, '\\') || NULL
!= strchr(c
+ 1, '/')) {
419 fprintf(stderr
, "Broken domain-controller %s\n", d
);
425 new_dc
= static_cast<dc
*>(xmalloc(sizeof(dc
)));
427 fprintf(stderr
, "Malloc error while parsing DC options\n");
436 new_dc
->controller
= c
;
438 if (controllers
== NULL
) { /* first controller */
439 controllers
= new_dc
;
442 last_dc
->next
= new_dc
; /* can't be null */
446 if (numcontrollers
== 0) {
447 fprintf(stderr
, "You must specify at least one domain-controller!\n");
451 last_dc
->next
= controllers
; /* close the queue, now it's circular */
455 * tries connecting to the domain controllers in the "controllers" ring,
456 * with failover if the adequate option is specified.
462 const char *ch
= NULL
;
463 for (j
= 0; j
< numcontrollers
; ++j
) {
464 debug("obtain_challenge: selecting %s\\%s (attempt #%d)\n",
465 current_dc
->domain
, current_dc
->controller
, j
+ 1);
466 if (current_dc
->dead
!= 0) {
467 if (time(NULL
) - current_dc
->dead
>= DEAD_DC_RETRY_INTERVAL
) {
468 /* mark helper as retry-worthy if it's so. */
469 debug("Reviving DC\n");
470 current_dc
->dead
= 0;
471 } else { /* skip it */
472 debug("Skipping it\n");
476 /* else branch. Here we KNOW that the DC is fine */
477 debug("attempting challenge retrieval\n");
478 ch
= make_challenge(current_dc
->domain
, current_dc
->controller
);
479 debug("make_challenge retuned %p\n", ch
);
482 return ch
; /* All went OK, returning */
484 /* Huston, we've got a problem. Take this DC out of the loop */
485 debug("Marking DC as DEAD\n");
486 current_dc
->dead
= time(NULL
);
487 /* Try with the next */
488 debug("moving on to next controller\n");
489 current_dc
= current_dc
->next
;
491 /* all DCs failed. */
498 ntlmhdr
*fast_header
;
499 char buf
[NTLM_BLOB_BUFFER_SIZE
];
500 char decoded
[NTLM_BLOB_BUFFER_SIZE
];
501 char *ch2
, *cred
= NULL
;
503 if (fgets(buf
, NTLM_BLOB_BUFFER_SIZE
, stdin
) == NULL
) {
504 fprintf(stderr
, "fgets() failed! dying..... errno=%d (%s)\n", errno
,
506 exit(EXIT_FAILURE
); /* BIIG buffer */
508 debug("managing request\n");
509 ch2
= (char*)memchr(buf
, '\n', NTLM_BLOB_BUFFER_SIZE
); /* safer against overrun than strchr */
511 *ch2
= '\0'; /* terminate the string at newline. */
513 debug("ntlm authenticator. Got '%s' from Squid\n", buf
);
515 if (memcmp(buf
, "KK ", 3) == 0) { /* authenticate-request */
516 /* figure out what we got */
517 struct base64_decode_ctx ctx
;
518 base64_decode_init(&ctx
);
521 if (!base64_decode_update(&ctx
, &dstLen
, reinterpret_cast<uint8_t*>(decoded
), strlen(buf
)-3, buf
+3) ||
522 !base64_decode_final(&ctx
)) {
523 SEND("NA Packet format error, couldn't base64-decode");
528 if ((size_t)decodedLen
< sizeof(ntlmhdr
)) { /* decoding failure, return error */
529 SEND("NA Packet format error, truncated packet header.");
532 /* fast-track-decode request type. */
533 fast_header
= (ntlmhdr
*) decoded
;
535 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
536 if (ntlm_validate_packet(fast_header
, NTLM_ANY
) < 0) {
537 SEND("NA Broken authentication packet");
540 switch (le32toh(fast_header
->type
)) {
542 SEND("NA Invalid negotiation request received");
546 SEND("NA Got a challenge. We refuse to have our authority disputed");
549 case NTLM_AUTHENTICATE
:
550 /* check against the DC */
551 signal(SIGALRM
, timeout_during_auth
);
553 cred
= ntlm_check_auth((ntlm_authenticate
*) decoded
, decodedLen
);
555 signal(SIGALRM
, SIG_DFL
);
556 if (got_timeout
!= 0) {
557 fprintf(stderr
, "ntlm-auth[%ld]: Timeout during authentication.\n", (long)getpid());
558 SEND("BH Timeout during authentication");
563 int smblib_err
, smb_errorclass
, smb_errorcode
, nb_error
;
564 if (ntlm_errno
== NTLM_ERR_LOGON
) { /* hackish */
565 SEND("NA Logon Failure");
568 /* there was an error. We have two errno's to look at.
569 * libntlmssp's erno is insufficient, we'll have to look at
570 * the actual SMB library error codes, to actually figure
571 * out what's happening. The thing has braindamaged interfacess..*/
572 smblib_err
= SMB_Get_Last_Error();
573 smb_errorclass
= SMBlib_Error_Class(SMB_Get_Last_SMB_Err());
574 smb_errorcode
= SMBlib_Error_Code(SMB_Get_Last_SMB_Err());
575 nb_error
= RFCNB_Get_Last_Error();
576 debug("No creds. SMBlib error %d, SMB error class %d, SMB error code %d, NB error %d\n",
577 smblib_err
, smb_errorclass
, smb_errorcode
, nb_error
);
578 /* Should I use smblib_err? Actually it seems I can do as well
580 if (nb_error
!= 0) { /* netbios-level error */
581 SEND("BH NetBios error!");
582 fprintf(stderr
, "NetBios error code %d (%s)\n", nb_error
,
583 RFCNB_Error_Strings
[abs(nb_error
)]);
586 switch (smb_errorclass
) {
588 debug("Huh? Got a SMB success code but could check auth..");
589 SEND("NA Authentication failed");
592 /*this is the most important one for errors */
593 debug("DOS error\n");
594 switch (smb_errorcode
) {
595 /* two categories matter to us: those which could be
596 * server errors, and those which are auth errors */
597 case SMBD_noaccess
: /* 5 */
598 SEND("NA Access denied");
601 SEND("NA bad format in authentication packet");
604 SEND("NA Bad access request");
610 SEND("BH DOS Error");
613 case SMBC_ERRSRV
: /* server errors */
614 debug("Server error");
615 switch (smb_errorcode
) {
616 /* mostly same as above */
618 SEND("NA Bad password");
621 SEND("NA Server access error");
624 SEND("BH Server Error");
627 case SMBC_ERRHRD
: /* hardware errors don't really matter */
628 SEND("BH Domain Controller Hardware error");
631 SEND("BH Domain Controller Command Error");
634 SEND("BH unknown internal error.");
638 lc(cred
); /* let's lowercase them for our convenience */
639 SEND2("AF %s", cred
);
642 SEND("BH unknown authentication packet type");
648 if (memcmp(buf
, "YR", 2) == 0) { /* refresh-request */
650 const char *ch
= obtain_challenge();
651 /* Robert says we can afford to wait forever. I'll trust him on this
655 ch
= obtain_challenge();
660 SEND("BH Helper detected protocol error");
662 /********* END ********/
667 main(int argc
, char *argv
[])
669 debug("%s " VERSION
" " SQUID_BUILD_INFO
" starting up...\n", argv
[0]);
671 my_program_name
= argv
[0];
672 process_options(argc
, argv
);
674 debug("options processed OK\n");
676 /* initialize FDescs */
677 setbuf(stdout
, NULL
);
678 setbuf(stderr
, NULL
);
680 /* select the first domain controller we're going to use */
681 current_dc
= controllers
;
682 if (load_balance
!= 0 && numcontrollers
> 1) {
684 pid_t pid
= getpid();
685 n
= pid
% numcontrollers
;
686 debug("load balancing. Selected controller #%d\n", n
);
688 current_dc
= current_dc
->next
;