]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/ntlm_auth/smb_lm/ntlm_smb_lm_auth.cc
Cleanup: un-wrap C++ header includes
[thirdparty/squid.git] / helpers / ntlm_auth / smb_lm / ntlm_smb_lm_auth.cc
1 /*
2 * (C) 2000 Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it>
3 * Distributed freely under the terms of the GNU General Public License,
4 * version 2. See the file COPYING for licensing details
5 *
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
10
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
14 *
15 */
16 #include "squid.h"
17 #include "base64.h"
18 #include "compat/debug.h"
19 #include "ntlmauth/ntlmauth.h"
20 #include "ntlmauth/support_bits.cci"
21 #include "rfcnb/rfcnb.h"
22 #include "smblib/smblib.h"
23
24 #include <cassert>
25 #include <cctype>
26 #include <cerrno>
27 #include <csignal>
28 #include <cstdlib>
29 #include <cstring>
30 #include <ctime>
31 #if HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #if HAVE_GETOPT_H
35 #include <getopt.h>
36 #endif
37 #if HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40
41 /************* CONFIGURATION ***************/
42
43 #define DEAD_DC_RETRY_INTERVAL 30
44
45 /************* END CONFIGURATION ***************/
46
47 /* A couple of harmless helper macros */
48 #define SEND(X) debug("sending '%s' to squid\n",X); printf(X "\n");
49 #ifdef __GNUC__
50 #define SEND2(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
51 #define SEND3(X,Y...) debug("sending '" X "' to squid\n",Y); printf(X "\n",Y);
52 #else
53 /* no gcc, no debugging. varargs macros are a gcc extension */
54 #define SEND2 printf
55 #define SEND3 printf
56 #endif
57
58 const char *make_challenge(char *domain, char *controller);
59 char *ntlm_check_auth(ntlm_authenticate * auth, int auth_length);
60 void dc_disconnect(void);
61 int connectedp(void);
62 int is_dc_ok(char *domain, char *domain_controller);
63
64 typedef struct _dc dc;
65 struct _dc {
66 char *domain;
67 char *controller;
68 time_t dead; /* 0 if it's alive, otherwise time of death */
69 dc *next;
70 };
71
72 /* local functions */
73 void usage(void);
74 void process_options(int argc, char *argv[]);
75 const char * obtain_challenge(void);
76 void manage_request(void);
77
78 #define ENCODED_PASS_LEN 24
79 #define MAX_USERNAME_LEN 255
80 #define MAX_DOMAIN_LEN 255
81 #define MAX_PASSWD_LEN 31
82
83 static unsigned char challenge[NTLM_NONCE_LEN];
84 static unsigned char lmencoded_empty_pass[ENCODED_PASS_LEN],
85 ntencoded_empty_pass[ENCODED_PASS_LEN];
86 SMB_Handle_Type handle = NULL;
87 int ntlm_errno;
88 static char credentials[MAX_USERNAME_LEN+MAX_DOMAIN_LEN+2]; /* we can afford to waste */
89 static char my_domain[100], my_domain_controller[100];
90 static char errstr[1001];
91 #if DEBUG
92 char error_messages_buffer[NTLM_BLOB_BUFFER_SIZE];
93 #endif
94 char load_balance = 0, protocol_pedantic = 0;
95 dc *controllers = NULL;
96 int numcontrollers = 0;
97 dc *current_dc;
98 char smb_error_buffer[1000];
99
100 /* Disconnects from the DC. A reconnection will be done upon the next request
101 */
102 void
103 dc_disconnect()
104 {
105 if (handle != NULL)
106 SMB_Discon(handle, 0);
107 handle = NULL;
108 }
109
110 int
111 connectedp()
112 {
113 return (handle != NULL);
114 }
115
116 /* Tries to connect to a DC. Returns 0 on failure, 1 on OK */
117 int
118 is_dc_ok(char *domain, char *domain_controller)
119 {
120 SMB_Handle_Type h = SMB_Connect_Server(NULL, domain_controller, domain);
121 if (h == NULL)
122 return 0;
123 SMB_Discon(h, 0);
124 return 1;
125 }
126
127 /* returns 0 on success, > 0 on failure */
128 static int
129 init_challenge(char *domain, char *domain_controller)
130 {
131 int smberr;
132
133 if (handle != NULL) {
134 return 0;
135 }
136 debug("Connecting to server %s domain %s\n", domain_controller, domain);
137 handle = SMB_Connect_Server(NULL, domain_controller, domain);
138 smberr = SMB_Get_Last_Error();
139 SMB_Get_Error_Msg(smberr, errstr, 1000);
140
141 if (handle == NULL) { /* couldn't connect */
142 debug("Couldn't connect to SMB Server. Error:%s\n", errstr);
143 return 1;
144 }
145 if (SMB_Negotiate(handle, SMB_Prots) < 0) { /* An error */
146 debug("Error negotiating protocol with SMB Server\n");
147 SMB_Discon(handle, 0);
148 handle = NULL;
149 return 2;
150 }
151 if (handle->Security == 0) { /* share-level security, unuseable */
152 debug("SMB Server uses share-level security .. we need user security.\n");
153 SMB_Discon(handle, 0);
154 handle = NULL;
155 return 3;
156 }
157 memcpy(challenge, handle->Encrypt_Key, NTLM_NONCE_LEN);
158 SMBencrypt((unsigned char *)"",challenge,lmencoded_empty_pass);
159 SMBNTencrypt((unsigned char *)"",challenge,ntencoded_empty_pass);
160 return 0;
161 }
162
163 const char *
164 make_challenge(char *domain, char *domain_controller)
165 {
166 /* trying to circumvent some strange problem wih pointers in SMBLib */
167 /* Ugly as hell, but the lib is going to be dropped... */
168 strncpy(my_domain, domain, sizeof(my_domain)-1);
169 my_domain[sizeof(my_domain)-1] = '\0';
170 strncpy(my_domain_controller, domain_controller, sizeof(my_domain_controller)-1);
171 my_domain_controller[sizeof(my_domain_controller)-1] = '\0';
172
173 if (init_challenge(my_domain, my_domain_controller) > 0) {
174 return NULL;
175 }
176 ntlm_challenge chal;
177 uint32_t flags = NTLM_REQUEST_NON_NT_SESSION_KEY |
178 NTLM_CHALLENGE_TARGET_IS_DOMAIN |
179 NTLM_NEGOTIATE_ALWAYS_SIGN |
180 NTLM_NEGOTIATE_USE_NTLM |
181 NTLM_NEGOTIATE_USE_LM |
182 NTLM_NEGOTIATE_ASCII;
183 ntlm_make_challenge(&chal, my_domain, my_domain_controller, (char *)challenge, NTLM_NONCE_LEN, flags);
184 int len = sizeof(chal) - sizeof(chal.payload) + le16toh(chal.target.maxlen);
185 return base64_encode_bin((char *)&chal, len);
186 }
187
188 /* returns NULL on failure, or a pointer to
189 * the user's credentials (domain\\username)
190 * upon success. WARNING. It's pointing to static storage.
191 * In case of problem sets as side-effect ntlm_errno to one of the
192 * codes defined in ntlm.h
193 */
194 char *
195 ntlm_check_auth(ntlm_authenticate * auth, int auth_length)
196 {
197 int rv;
198 char pass[MAX_PASSWD_LEN+1];
199 char *domain = credentials;
200 char *user;
201 lstring tmp;
202
203 if (handle == NULL) { /*if null we aren't connected, but it shouldn't happen */
204 debug("Weird, we've been disconnected\n");
205 ntlm_errno = NTLM_ERR_NOT_CONNECTED;
206 return NULL;
207 }
208
209 /* debug("fetching domain\n"); */
210 tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->domain, auth->flags);
211 if (tmp.str == NULL || tmp.l == 0) {
212 debug("No domain supplied. Returning no-auth\n");
213 ntlm_errno = NTLM_ERR_LOGON;
214 return NULL;
215 }
216 if (tmp.l > MAX_DOMAIN_LEN) {
217 debug("Domain string exceeds %d bytes, rejecting\n", MAX_DOMAIN_LEN);
218 ntlm_errno = NTLM_ERR_LOGON;
219 return NULL;
220 }
221 memcpy(domain, tmp.str, tmp.l);
222 user = domain + tmp.l;
223 *user = '\0';
224 ++user;
225
226 /* debug("fetching user name\n"); */
227 tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->user, auth->flags);
228 if (tmp.str == NULL || tmp.l == 0) {
229 debug("No username supplied. Returning no-auth\n");
230 ntlm_errno = NTLM_ERR_LOGON;
231 return NULL;
232 }
233 if (tmp.l > MAX_USERNAME_LEN) {
234 debug("Username string exceeds %d bytes, rejecting\n", MAX_USERNAME_LEN);
235 ntlm_errno = NTLM_ERR_LOGON;
236 return NULL;
237 }
238 memcpy(user, tmp.str, tmp.l);
239 *(user + tmp.l) = '\0';
240
241 /* Authenticating against the NT response doesn't seem to work... */
242 tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->lmresponse, auth->flags);
243 if (tmp.str == NULL || tmp.l == 0) {
244 fprintf(stderr, "No auth at all. Returning no-auth\n");
245 ntlm_errno = NTLM_ERR_LOGON;
246 return NULL;
247 }
248 if (tmp.l > MAX_PASSWD_LEN) {
249 debug("Password string exceeds %d bytes, rejecting\n", MAX_PASSWD_LEN);
250 ntlm_errno = NTLM_ERR_LOGON;
251 return NULL;
252 }
253
254 memcpy(pass, tmp.str, tmp.l);
255 pass[min(MAX_PASSWD_LEN,tmp.l)] = '\0';
256
257 #if 1
258 debug("Empty LM pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
259 user,lmencoded_empty_pass,tmp.str,tmp.l);
260 if (memcmp(tmp.str,lmencoded_empty_pass,ENCODED_PASS_LEN)==0) {
261 fprintf(stderr,"Empty LM password supplied for user %s\\%s. "
262 "No-auth\n",domain,user);
263 ntlm_errno=NTLM_ERR_LOGON;
264 return NULL;
265 }
266
267 tmp = ntlm_fetch_string(&(auth->hdr), auth_length, &auth->ntresponse, auth->flags);
268 if (tmp.str != NULL && tmp.l != 0) {
269 debug("Empty NT pass detection: user: '%s', ours:'%s', his: '%s' (length: %d)\n",
270 user,ntencoded_empty_pass,tmp.str,tmp.l);
271 if (memcmp(tmp.str,lmencoded_empty_pass,ENCODED_PASS_LEN)==0) {
272 fprintf(stderr,"ERROR: Empty NT password supplied for user %s\\%s. No-auth\n", domain, user);
273 ntlm_errno = NTLM_ERR_LOGON;
274 return NULL;
275 }
276 }
277 #endif
278
279 /* TODO: check against empty password!!!!! */
280
281 debug("checking domain: '%s', user: '%s', pass='%s'\n", domain, user, pass);
282
283 rv = SMB_Logon_Server(handle, user, pass, domain, 1);
284 debug("Login attempt had result %d\n", rv);
285
286 if (rv != NTLM_ERR_NONE) { /* failed */
287 ntlm_errno = rv;
288 return NULL;
289 }
290 *(user - 1) = '\\'; /* hack. Performing, but ugly. */
291
292 debug("credentials: %s\n", credentials);
293 return credentials;
294 }
295
296 extern "C" void timeout_during_auth(int signum);
297
298 static char got_timeout = 0;
299 /** signal handler to be invoked when the authentication operation
300 * times out */
301 void
302 timeout_during_auth(int signum)
303 {
304 dc_disconnect();
305 }
306
307 /*
308 * options:
309 * -b try load-balancing the domain-controllers
310 * -f fail-over to another DC if DC connection fails.
311 * DEPRECATED and VERBOSELY IGNORED. This is on by default now.
312 * -l last-ditch-mode
313 * domain\controller ...
314 */
315 char *my_program_name = NULL;
316
317 void
318 usage()
319 {
320 fprintf(stderr,
321 "%s usage:\n%s [-b] [-f] [-d] [-l] domain\\controller [domain\\controller ...]\n"
322 "-b enables load-balancing among controllers\n"
323 "-f enables failover among controllers (DEPRECATED and always active)\n"
324 "-d enables debugging statements if DEBUG was defined at build-time.\n\n"
325 "You MUST specify at least one Domain Controller.\n"
326 "You can use either \\ or / as separator between the domain name \n"
327 "and the controller name\n",
328 my_program_name, my_program_name);
329 }
330
331 /* int debug_enabled=0; defined in libcompat */
332
333 void
334 process_options(int argc, char *argv[])
335 {
336 int opt, j, had_error = 0;
337 dc *new_dc = NULL, *last_dc = NULL;
338 while (-1 != (opt = getopt(argc, argv, "bfld"))) {
339 switch (opt) {
340 case 'b':
341 load_balance = 1;
342 break;
343 case 'f':
344 fprintf(stderr,
345 "WARNING. The -f flag is DEPRECATED and always active.\n");
346 break;
347 case 'd':
348 debug_enabled=1;
349 break;
350 default:
351 fprintf(stderr, "unknown option: -%c. Exiting\n", opt);
352 usage();
353 had_error = 1;
354 }
355 }
356 if (had_error)
357 exit(1);
358 /* Okay, now begin filling controllers up */
359 /* we can avoid memcpy-ing, and just reuse argv[] */
360 for (j = optind; j < argc; ++j) {
361 char *d, *c;
362 /* d will not be freed in case of non-error. Since we don't reconfigure,
363 * it's going to live as long as the process anyways */
364 d = (char*)malloc(strlen(argv[j]) + 1);
365 strcpy(d, argv[j]);
366 debug("Adding domain-controller %s\n", d);
367 if (NULL == (c = strchr(d, '\\')) && NULL == (c = strchr(d, '/'))) {
368 fprintf(stderr, "Couldn't grok domain-controller %s\n", d);
369 free(d);
370 continue;
371 }
372 /* more than one delimiter is not allowed */
373 if (NULL != strchr(c + 1, '\\') || NULL != strchr(c + 1, '/')) {
374 fprintf(stderr, "Broken domain-controller %s\n", d);
375 free(d);
376 continue;
377 }
378 *c= '\0';
379 ++c;
380 new_dc = (dc *) malloc(sizeof(dc));
381 if (!new_dc) {
382 fprintf(stderr, "Malloc error while parsing DC options\n");
383 free(d);
384 continue;
385 }
386 /* capitalize */
387 uc(c);
388 uc(d);
389 ++numcontrollers;
390 new_dc->domain = d;
391 new_dc->controller = c;
392 new_dc->dead = 0;
393 if (controllers == NULL) { /* first controller */
394 controllers = new_dc;
395 last_dc = new_dc;
396 } else {
397 last_dc->next = new_dc; /* can't be null */
398 last_dc = new_dc;
399 }
400 }
401 if (numcontrollers == 0) {
402 fprintf(stderr, "You must specify at least one domain-controller!\n");
403 usage();
404 exit(1);
405 }
406 last_dc->next = controllers; /* close the queue, now it's circular */
407 }
408
409 /**
410 * tries connecting to the domain controllers in the "controllers" ring,
411 * with failover if the adequate option is specified.
412 */
413 const char *
414 obtain_challenge()
415 {
416 int j = 0;
417 const char *ch = NULL;
418 for (j = 0; j < numcontrollers; ++j) {
419 debug("obtain_challenge: selecting %s\\%s (attempt #%d)\n",
420 current_dc->domain, current_dc->controller, j + 1);
421 if (current_dc->dead != 0) {
422 if (time(NULL) - current_dc->dead >= DEAD_DC_RETRY_INTERVAL) {
423 /* mark helper as retry-worthy if it's so. */
424 debug("Reviving DC\n");
425 current_dc->dead = 0;
426 } else { /* skip it */
427 debug("Skipping it\n");
428 continue;
429 }
430 }
431 /* else branch. Here we KNOW that the DC is fine */
432 debug("attempting challenge retrieval\n");
433 ch = make_challenge(current_dc->domain, current_dc->controller);
434 debug("make_challenge retuned %p\n", ch);
435 if (ch) {
436 debug("Got it\n");
437 return ch; /* All went OK, returning */
438 }
439 /* Huston, we've got a problem. Take this DC out of the loop */
440 debug("Marking DC as DEAD\n");
441 current_dc->dead = time(NULL);
442 /* Try with the next */
443 debug("moving on to next controller\n");
444 current_dc = current_dc->next;
445 }
446 /* all DCs failed. */
447 return NULL;
448 }
449
450 void
451 manage_request()
452 {
453 ntlmhdr *fast_header;
454 char buf[NTLM_BLOB_BUFFER_SIZE];
455 char decoded[NTLM_BLOB_BUFFER_SIZE];
456 const char *ch;
457 char *ch2, *cred = NULL;
458
459 if (fgets(buf, NTLM_BLOB_BUFFER_SIZE, stdin) == NULL) {
460 fprintf(stderr, "fgets() failed! dying..... errno=%d (%s)\n", errno,
461 strerror(errno));
462 exit(1); /* BIIG buffer */
463 }
464 debug("managing request\n");
465 ch2 = (char*)memchr(buf, '\n', NTLM_BLOB_BUFFER_SIZE); /* safer against overrun than strchr */
466 if (ch2) {
467 *ch2 = '\0'; /* terminate the string at newline. */
468 ch = ch2;
469 }
470 debug("ntlm authenticator. Got '%s' from Squid\n", buf);
471
472 if (memcmp(buf, "KK ", 3) == 0) { /* authenticate-request */
473 /* figure out what we got */
474 int decodedLen = base64_decode(decoded, sizeof(decoded), buf+3);
475
476 if ((size_t)decodedLen < sizeof(ntlmhdr)) { /* decoding failure, return error */
477 SEND("NA Packet format error, couldn't base64-decode");
478 return;
479 }
480 /* fast-track-decode request type. */
481 fast_header = (ntlmhdr *) decoded;
482
483 /* sanity-check: it IS a NTLMSSP packet, isn't it? */
484 if (ntlm_validate_packet(fast_header, NTLM_ANY) < 0) {
485 SEND("NA Broken authentication packet");
486 return;
487 }
488 switch (le32toh(fast_header->type)) {
489 case NTLM_NEGOTIATE:
490 SEND("NA Invalid negotiation request received");
491 return;
492 /* notreached */
493 case NTLM_CHALLENGE:
494 SEND("NA Got a challenge. We refuse to have our authority disputed");
495 return;
496 /* notreached */
497 case NTLM_AUTHENTICATE:
498 /* check against the DC */
499 signal(SIGALRM, timeout_during_auth);
500 alarm(30);
501 cred = ntlm_check_auth((ntlm_authenticate *) decoded, decodedLen);
502 alarm(0);
503 signal(SIGALRM, SIG_DFL);
504 if (got_timeout != 0) {
505 fprintf(stderr, "ntlm-auth[%ld]: Timeout during authentication.\n", (long)getpid());
506 SEND("BH Timeout during authentication");
507 got_timeout = 0;
508 return;
509 }
510 if (cred == NULL) {
511 int smblib_err, smb_errorclass, smb_errorcode, nb_error;
512 if (ntlm_errno == NTLM_ERR_LOGON) { /* hackish */
513 SEND("NA Logon Failure");
514 return;
515 }
516 /* there was an error. We have two errno's to look at.
517 * libntlmssp's erno is insufficient, we'll have to look at
518 * the actual SMB library error codes, to acually figure
519 * out what's happening. The thing has braindamaged interfacess..*/
520 smblib_err = SMB_Get_Last_Error();
521 smb_errorclass = SMBlib_Error_Class(SMB_Get_Last_SMB_Err());
522 smb_errorcode = SMBlib_Error_Code(SMB_Get_Last_SMB_Err());
523 nb_error = RFCNB_Get_Last_Error();
524 debug("No creds. SMBlib error %d, SMB error class %d, SMB error code %d, NB error %d\n",
525 smblib_err, smb_errorclass, smb_errorcode, nb_error);
526 /* Should I use smblib_err? Actually it seems I can do as well
527 * without it.. */
528 if (nb_error != 0) { /* netbios-level error */
529 SEND("BH NetBios error!");
530 fprintf(stderr, "NetBios error code %d (%s)\n", nb_error,
531 RFCNB_Error_Strings[abs(nb_error)]);
532 return;
533 }
534 switch (smb_errorclass) {
535 case SMBC_SUCCESS:
536 debug("Huh? Got a SMB success code but could check auth..");
537 SEND("NA Authentication failed");
538 return;
539 case SMBC_ERRDOS:
540 /*this is the most important one for errors */
541 debug("DOS error\n");
542 switch (smb_errorcode) {
543 /* two categories matter to us: those which could be
544 * server errors, and those which are auth errors */
545 case SMBD_noaccess: /* 5 */
546 SEND("NA Access denied");
547 return;
548 case SMBD_badformat:
549 SEND("NA bad format in authentication packet");
550 return;
551 case SMBD_badaccess:
552 SEND("NA Bad access request");
553 return;
554 case SMBD_baddata:
555 SEND("NA Bad Data");
556 return;
557 default:
558 SEND("BH DOS Error");
559 return;
560 }
561 case SMBC_ERRSRV: /* server errors */
562 debug("Server error");
563 switch (smb_errorcode) {
564 /* mostly same as above */
565 case SMBV_badpw:
566 SEND("NA Bad password");
567 return;
568 case SMBV_access:
569 SEND("NA Server access error");
570 return;
571 default:
572 SEND("BH Server Error");
573 return;
574 }
575 case SMBC_ERRHRD: /* hardware errors don't really matter */
576 SEND("BH Domain Controller Hardware error");
577 return;
578 case SMBC_ERRCMD:
579 SEND("BH Domain Controller Command Error");
580 return;
581 }
582 SEND("BH unknown internal error.");
583 return;
584 }
585
586 lc(cred); /* let's lowercase them for our convenience */
587 SEND2("AF %s", cred);
588 return;
589 default:
590 SEND("BH unknown authentication packet type");
591 return;
592 }
593 /* notreached */
594 return;
595 }
596 if (memcmp(buf, "YR", 2) == 0) { /* refresh-request */
597 dc_disconnect();
598 ch = obtain_challenge();
599 /* Robert says we can afford to wait forever. I'll trust him on this
600 * one */
601 while (ch == NULL) {
602 sleep(30);
603 ch = obtain_challenge();
604 }
605 SEND2("TT %s", ch);
606 return;
607 }
608 SEND("BH Helper detected protocol error");
609 return;
610 /********* END ********/
611
612 }
613
614 int
615 main(int argc, char *argv[])
616 {
617 debug("ntlm_auth build " __DATE__ ", " __TIME__ " starting up...\n");
618
619 my_program_name = argv[0];
620 process_options(argc, argv);
621
622 debug("options processed OK\n");
623
624 /* initialize FDescs */
625 setbuf(stdout, NULL);
626 setbuf(stderr, NULL);
627
628 /* select the first domain controller we're going to use */
629 current_dc = controllers;
630 if (load_balance != 0 && numcontrollers > 1) {
631 int n;
632 pid_t pid = getpid();
633 n = pid % numcontrollers;
634 debug("load balancing. Selected controller #%d\n", n);
635 while (n > 0) {
636 current_dc = current_dc->next;
637 --n;
638 }
639 }
640 while (1) {
641 manage_request();
642 }
643 /* notreached */
644 return 0;
645 }