]>
Commit | Line | Data |
---|---|---|
94439e4e | 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. | |
26ac0430 | 10 | |
94439e4e | 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 | * | |
94439e4e | 15 | */ |
16 | ||
94439e4e | 17 | #include "config.h" |
18 | #include "ntlmauth.h" | |
30b4b6fc | 19 | #include "ntlm_smb_lm_auth.h" |
f9576890 | 20 | #include "squid_endian.h" |
94439e4e | 21 | #include "util.h" |
6437ac71 | 22 | #include "smbval/smblib-common.h" |
23 | #include "smbval/rfcnb-error.h" | |
94439e4e | 24 | |
5d146f7d | 25 | #include <signal.h> |
5d146f7d | 26 | |
2d70df72 | 27 | /* these are part of rfcnb-priv.h and smblib-priv.h */ |
28 | extern int SMB_Get_Error_Msg(int msg, char *msgbuf, int len); | |
29 | extern int SMB_Get_Last_Error(); | |
30 | extern int SMB_Get_Last_SMB_Err(); | |
6437ac71 | 31 | extern int RFCNB_Get_Last_Error(); |
2d70df72 | 32 | |
6437ac71 | 33 | #include <errno.h> |
2d70df72 | 34 | |
94439e4e | 35 | #define BUFFER_SIZE 10240 |
36 | ||
37 | #if HAVE_STDLIB_H | |
38 | #include <stdlib.h> | |
39 | #endif | |
94439e4e | 40 | #if HAVE_GETOPT_H |
41 | #include <getopt.h> | |
42 | #endif | |
94439e4e | 43 | #ifdef HAVE_STRING_H |
44 | #include <string.h> | |
45 | #endif | |
46 | #ifdef HAVE_CTYPE_H | |
47 | #include <ctype.h> | |
48 | #endif | |
45b635fa | 49 | #ifdef HAVE_UNISTD_H |
50 | #include <unistd.h> | |
51 | #endif | |
2c22dde8 | 52 | #ifdef HAVE_ASSERT_H |
53 | #include <assert.h> | |
54 | #endif | |
94439e4e | 55 | |
2d70df72 | 56 | #ifdef DEBUG |
57 | char error_messages_buffer[BUFFER_SIZE]; | |
58 | #endif | |
59 | ||
5d146f7d | 60 | char load_balance = 0, protocol_pedantic = 0; |
61 | #ifdef NTLM_FAIL_OPEN | |
62 | char last_ditch_enabled = 0; | |
63 | #endif | |
94439e4e | 64 | |
65 | dc *controllers = NULL; | |
66 | int numcontrollers = 0; | |
67 | dc *current_dc; | |
68 | ||
2d70df72 | 69 | char smb_error_buffer[1000]; |
70 | ||
5d146f7d | 71 | /* signal handler to be invoked when the authentication operation |
9bea1d5b | 72 | * times out */ |
73 | static char got_timeout = 0; | |
94439e4e | 74 | static void |
9bea1d5b | 75 | timeout_during_auth(int signum) |
76 | { | |
77 | dc_disconnect(); | |
94439e4e | 78 | } |
79 | ||
80 | /* makes a null-terminated string upper-case. Changes CONTENTS! */ | |
81 | static void | |
82 | uc(char *string) | |
83 | { | |
84 | char *p = string, c; | |
85 | while ((c = *p)) { | |
26ac0430 AJ |
86 | *p = xtoupper(c); |
87 | p++; | |
94439e4e | 88 | } |
89 | } | |
90 | ||
91 | /* makes a null-terminated string lower-case. Changes CONTENTS! */ | |
92 | static void | |
93 | lc(char *string) | |
94 | { | |
95 | char *p = string, c; | |
96 | while ((c = *p)) { | |
26ac0430 AJ |
97 | *p = xtolower(c); |
98 | p++; | |
94439e4e | 99 | } |
100 | } | |
101 | ||
2d70df72 | 102 | |
103 | void | |
104 | send_bh_or_ld(char *bhmessage, ntlm_authenticate * failedauth, int authlen) | |
105 | { | |
5d146f7d | 106 | #ifdef NTLM_FAIL_OPEN |
13bf0a40 | 107 | char *creds = NULL; |
2d70df72 | 108 | if (last_ditch_enabled) { |
26ac0430 AJ |
109 | creds = fetch_credentials(failedauth, authlen); |
110 | if (creds) { | |
111 | lc(creds); | |
112 | SEND2("LD %s", creds); | |
113 | } else { | |
114 | SEND("NA last-ditch on, but no credentials"); | |
115 | } | |
2d70df72 | 116 | } else { |
5d146f7d | 117 | #endif |
26ac0430 | 118 | SEND2("BH %s", bhmessage); |
5d146f7d | 119 | #ifdef NTLM_FAIL_OPEN |
2d70df72 | 120 | } |
5d146f7d | 121 | #endif |
2d70df72 | 122 | } |
123 | ||
94439e4e | 124 | /* |
125 | * options: | |
126 | * -b try load-balancing the domain-controllers | |
127 | * -f fail-over to another DC if DC connection fails. | |
5d146f7d | 128 | * DEPRECATED and VERBOSELY IGNORED. This is on by default now. |
2d70df72 | 129 | * -l last-ditch-mode |
94439e4e | 130 | * domain\controller ... |
131 | */ | |
a88de9ce | 132 | char *my_program_name = NULL; |
133 | ||
134 | void | |
135 | usage() | |
136 | { | |
137 | fprintf(stderr, | |
26ac0430 AJ |
138 | "%s usage:\n%s [-b] [-f] [-d] [-l] domain\\controller [domain\\controller ...]\n" |
139 | "-b enables load-balancing among controllers\n" | |
140 | "-f enables failover among controllers (DEPRECATED and always active)\n" | |
141 | "-l changes behavior on domain controller failyures to last-ditch.\n" | |
142 | "-d enables debugging statements if DEBUG was defined at build-time.\n\n" | |
143 | "You MUST specify at least one Domain Controller.\n" | |
144 | "You can use either \\ or / as separator between the domain name \n" | |
145 | "and the controller name\n", | |
146 | my_program_name, my_program_name); | |
a88de9ce | 147 | } |
148 | ||
3c641669 | 149 | char debug_enabled=0; |
a88de9ce | 150 | |
94439e4e | 151 | void |
152 | process_options(int argc, char *argv[]) | |
153 | { | |
154 | int opt, j, had_error = 0; | |
155 | dc *new_dc = NULL, *last_dc = NULL; | |
3c641669 | 156 | while (-1 != (opt = getopt(argc, argv, "bfld"))) { |
26ac0430 AJ |
157 | switch (opt) { |
158 | case 'b': | |
159 | load_balance = 1; | |
160 | break; | |
161 | case 'f': | |
162 | fprintf(stderr, | |
163 | "WARNING. The -f flag is DEPRECATED and always active.\n"); | |
164 | break; | |
5d146f7d | 165 | #ifdef NTLM_FAIL_OPEN |
26ac0430 AJ |
166 | case 'l': |
167 | last_ditch_enabled = 1; | |
168 | break; | |
5d146f7d | 169 | #endif |
26ac0430 AJ |
170 | case 'd': |
171 | debug_enabled=1; | |
172 | break; | |
173 | default: | |
174 | fprintf(stderr, "unknown option: -%c. Exiting\n", opt); | |
175 | usage(); | |
176 | had_error = 1; | |
177 | } | |
94439e4e | 178 | } |
179 | if (had_error) | |
26ac0430 | 180 | exit(1); |
94439e4e | 181 | /* Okay, now begin filling controllers up */ |
182 | /* we can avoid memcpy-ing, and just reuse argv[] */ | |
183 | for (j = optind; j < argc; j++) { | |
26ac0430 AJ |
184 | char *d, *c; |
185 | /* d will not be freed in case of non-error. Since we don't reconfigure, | |
186 | * it's going to live as long as the process anyways */ | |
187 | d = malloc(strlen(argv[j]) + 1); | |
188 | strcpy(d, argv[j]); | |
189 | debug("Adding domain-controller %s\n", d); | |
190 | if (NULL == (c = strchr(d, '\\')) && NULL == (c = strchr(d, '/'))) { | |
191 | fprintf(stderr, "Couldn't grok domain-controller %s\n", d); | |
192 | free(d); | |
193 | continue; | |
194 | } | |
195 | /* more than one delimiter is not allowed */ | |
196 | if (NULL != strchr(c + 1, '\\') || NULL != strchr(c + 1, '/')) { | |
197 | fprintf(stderr, "Broken domain-controller %s\n", d); | |
198 | free(d); | |
199 | continue; | |
200 | } | |
201 | *c++ = '\0'; | |
202 | new_dc = (dc *) malloc(sizeof(dc)); | |
203 | if (!new_dc) { | |
204 | fprintf(stderr, "Malloc error while parsing DC options\n"); | |
205 | free(d); | |
206 | continue; | |
207 | } | |
208 | /* capitalize */ | |
209 | uc(c); | |
210 | uc(d); | |
211 | numcontrollers++; | |
212 | new_dc->domain = d; | |
213 | new_dc->controller = c; | |
214 | new_dc->dead = 0; | |
215 | if (controllers == NULL) { /* first controller */ | |
216 | controllers = new_dc; | |
217 | last_dc = new_dc; | |
218 | } else { | |
219 | last_dc->next = new_dc; /* can't be null */ | |
220 | last_dc = new_dc; | |
221 | } | |
94439e4e | 222 | } |
223 | if (numcontrollers == 0) { | |
26ac0430 AJ |
224 | fprintf(stderr, "You must specify at least one domain-controller!\n"); |
225 | usage(); | |
226 | exit(1); | |
94439e4e | 227 | } |
228 | last_dc->next = controllers; /* close the queue, now it's circular */ | |
229 | } | |
230 | ||
231 | /* tries connecting to the domain controllers in the "controllers" ring, | |
232 | * with failover if the adequate option is specified. | |
233 | */ | |
234 | const char * | |
235 | obtain_challenge() | |
236 | { | |
237 | int j = 0; | |
60d096f4 | 238 | const char *ch = NULL; |
94439e4e | 239 | for (j = 0; j < numcontrollers; j++) { |
26ac0430 AJ |
240 | debug("obtain_challenge: selecting %s\\%s (attempt #%d)\n", |
241 | current_dc->domain, current_dc->controller, j + 1); | |
242 | if (current_dc->dead != 0) { | |
243 | if (time(NULL) - current_dc->dead >= DEAD_DC_RETRY_INTERVAL) { | |
244 | /* mark helper as retry-worthy if it's so. */ | |
245 | debug("Reviving DC\n"); | |
246 | current_dc->dead = 0; | |
247 | } else { /* skip it */ | |
248 | debug("Skipping it\n"); | |
249 | continue; | |
250 | } | |
251 | } | |
252 | /* else branch. Here we KNOW that the DC is fine */ | |
253 | debug("attempting challenge retrieval\n"); | |
254 | ch = make_challenge(current_dc->domain, current_dc->controller); | |
255 | debug("make_challenge retuned %p\n", ch); | |
256 | if (ch) { | |
257 | debug("Got it\n"); | |
258 | return ch; /* All went OK, returning */ | |
259 | } | |
260 | /* Huston, we've got a problem. Take this DC out of the loop */ | |
261 | debug("Marking DC as DEAD\n"); | |
262 | current_dc->dead = time(NULL); | |
263 | /* Try with the next */ | |
264 | debug("moving on to next controller\n"); | |
265 | current_dc = current_dc->next; | |
94439e4e | 266 | } |
5d146f7d | 267 | /* all DCs failed. */ |
94439e4e | 268 | return NULL; |
269 | } | |
270 | ||
2d70df72 | 271 | |
94439e4e | 272 | void |
273 | manage_request() | |
274 | { | |
275 | ntlmhdr *fast_header; | |
2d70df72 | 276 | char buf[BUFFER_SIZE]; |
94439e4e | 277 | const char *ch; |
db03754a | 278 | char *ch2, *decoded, *cred = NULL; |
94439e4e | 279 | int plen; |
280 | ||
6437ac71 | 281 | if (fgets(buf, BUFFER_SIZE, stdin) == NULL) { |
26ac0430 AJ |
282 | fprintf(stderr, "fgets() failed! dying..... errno=%d (%s)\n", errno, |
283 | strerror(errno)); | |
284 | exit(1); /* BIIG buffer */ | |
6437ac71 | 285 | } |
286 | debug("managing request\n"); | |
94439e4e | 287 | ch2 = memchr(buf, '\n', BUFFER_SIZE); /* safer against overrun than strchr */ |
288 | if (ch2) { | |
26ac0430 AJ |
289 | *ch2 = '\0'; /* terminate the string at newline. */ |
290 | ch = ch2; | |
94439e4e | 291 | } |
292 | debug("ntlm authenticator. Got '%s' from Squid\n", buf); | |
293 | ||
294 | if (memcmp(buf, "KK ", 3) == 0) { /* authenticate-request */ | |
26ac0430 AJ |
295 | /* figure out what we got */ |
296 | decoded = base64_decode(buf + 3); | |
297 | /* Note: we don't need to manage memory at this point, since | |
298 | * base64_decode returns a pointer to static storage. | |
299 | */ | |
300 | ||
301 | if (!decoded) { /* decoding failure, return error */ | |
302 | SEND("NA Packet format error, couldn't base64-decode"); | |
303 | return; | |
304 | } | |
305 | /* fast-track-decode request type. */ | |
306 | fast_header = (struct _ntlmhdr *) decoded; | |
307 | ||
308 | /* sanity-check: it IS a NTLMSSP packet, isn't it? */ | |
309 | if (memcmp(fast_header->signature, "NTLMSSP", 8) != 0) { | |
310 | SEND("NA Broken authentication packet"); | |
311 | return; | |
312 | } | |
313 | switch (le32toh(fast_header->type)) { | |
314 | case NTLM_NEGOTIATE: | |
315 | SEND("NA Invalid negotiation request received"); | |
316 | return; | |
317 | /* notreached */ | |
318 | case NTLM_CHALLENGE: | |
319 | SEND | |
320 | ("NA Got a challenge. We refuse to have our authority disputed"); | |
321 | return; | |
322 | /* notreached */ | |
323 | case NTLM_AUTHENTICATE: | |
324 | /* check against the DC */ | |
325 | plen = strlen(buf) * 3 / 4; /* we only need it here. Optimization */ | |
326 | signal(SIGALRM, timeout_during_auth); | |
327 | alarm(30); | |
328 | cred = ntlm_check_auth((ntlm_authenticate *) decoded, plen); | |
329 | alarm(0); | |
330 | signal(SIGALRM, SIG_DFL); | |
331 | if (got_timeout != 0) { | |
332 | fprintf(stderr, "ntlm-auth[%ld]: Timeout during authentication.\n", (long)getpid()); | |
333 | SEND("BH Timeout during authentication"); | |
334 | got_timeout = 0; | |
335 | return; | |
336 | } | |
337 | if (cred == NULL) { | |
338 | int smblib_err, smb_errorclass, smb_errorcode, nb_error; | |
339 | if (ntlm_errno == NTLM_LOGON_ERROR) { /* hackish */ | |
340 | SEND("NA Logon Failure"); | |
341 | return; | |
342 | } | |
343 | /* there was an error. We have two errno's to look at. | |
344 | * libntlmssp's erno is insufficient, we'll have to look at | |
345 | * the actual SMB library error codes, to acually figure | |
346 | * out what's happening. The thing has braindamaged interfacess..*/ | |
347 | smblib_err = SMB_Get_Last_Error(); | |
348 | smb_errorclass = SMBlib_Error_Class(SMB_Get_Last_SMB_Err()); | |
349 | smb_errorcode = SMBlib_Error_Code(SMB_Get_Last_SMB_Err()); | |
350 | nb_error = RFCNB_Get_Last_Error(); | |
351 | debug("No creds. SMBlib error %d, SMB error class %d, SMB error code %d, NB error %d\n", | |
352 | smblib_err, smb_errorclass, smb_errorcode, nb_error); | |
353 | /* Should I use smblib_err? Actually it seems I can do as well | |
354 | * without it.. */ | |
355 | if (nb_error != 0) { /* netbios-level error */ | |
356 | send_bh_or_ld("NetBios error!", | |
357 | (ntlm_authenticate *) decoded, plen); | |
358 | fprintf(stderr, "NetBios error code %d (%s)\n", nb_error, | |
359 | RFCNB_Error_Strings[abs(nb_error)]); | |
360 | return; | |
361 | } | |
362 | switch (smb_errorclass) { | |
363 | case SMBC_SUCCESS: | |
364 | debug("Huh? Got a SMB success code but could check auth.."); | |
365 | SEND("NA Authentication failed"); | |
366 | /* | |
367 | * send_bh_or_ld("SMB success, but no creds. Internal error?", | |
368 | * (ntlm_authenticate *) decoded, plen); | |
369 | */ | |
370 | return; | |
371 | case SMBC_ERRDOS: | |
372 | /*this is the most important one for errors */ | |
373 | debug("DOS error\n"); | |
374 | switch (smb_errorcode) { | |
375 | /* two categories matter to us: those which could be | |
376 | * server errors, and those which are auth errors */ | |
377 | case SMBD_noaccess: /* 5 */ | |
378 | SEND("NA Access denied"); | |
379 | return; | |
380 | case SMBD_badformat: | |
381 | SEND("NA bad format in authentication packet"); | |
382 | return; | |
383 | case SMBD_badaccess: | |
384 | SEND("NA Bad access request"); | |
385 | return; | |
386 | case SMBD_baddata: | |
387 | SEND("NA Bad Data"); | |
388 | return; | |
389 | default: | |
390 | send_bh_or_ld("DOS Error", | |
391 | (ntlm_authenticate *) decoded, plen); | |
392 | return; | |
393 | } | |
394 | case SMBC_ERRSRV: /* server errors */ | |
395 | debug("Server error"); | |
396 | switch (smb_errorcode) { | |
397 | /* mostly same as above */ | |
398 | case SMBV_badpw: | |
399 | SEND("NA Bad password"); | |
400 | return; | |
401 | case SMBV_access: | |
402 | SEND("NA Server access error"); | |
403 | return; | |
404 | default: | |
405 | send_bh_or_ld("Server Error", | |
406 | (ntlm_authenticate *) decoded, plen); | |
407 | return; | |
408 | } | |
409 | case SMBC_ERRHRD: /* hardware errors don't really matter */ | |
410 | send_bh_or_ld("Domain Controller Hardware error", | |
411 | (ntlm_authenticate *) decoded, plen); | |
412 | return; | |
413 | case SMBC_ERRCMD: | |
414 | send_bh_or_ld("Domain Controller Command Error", | |
415 | (ntlm_authenticate *) decoded, plen); | |
416 | return; | |
417 | } | |
418 | } | |
db03754a | 419 | assert(cred != NULL); |
26ac0430 AJ |
420 | lc(cred); /* let's lowercase them for our convenience */ |
421 | SEND2("AF %s", cred); | |
422 | return; | |
423 | default: | |
424 | SEND("BH unknown authentication packet type"); | |
425 | return; | |
426 | } | |
94439e4e | 427 | |
428 | ||
26ac0430 | 429 | return; |
94439e4e | 430 | } |
431 | if (memcmp(buf, "YR", 2) == 0) { /* refresh-request */ | |
26ac0430 AJ |
432 | dc_disconnect(); |
433 | ch = obtain_challenge(); | |
434 | /* Robert says we can afford to wait forever. I'll trust him on this | |
435 | * one */ | |
436 | while (ch == NULL) { | |
437 | sleep(30); | |
438 | ch = obtain_challenge(); | |
439 | } | |
440 | SEND2("TT %s", ch); | |
441 | return; | |
94439e4e | 442 | } |
443 | SEND("BH Helper detected protocol error"); | |
444 | return; | |
26ac0430 | 445 | /********* END ********/ |
94439e4e | 446 | |
447 | ||
448 | } | |
449 | ||
450 | int | |
451 | main(int argc, char *argv[]) | |
452 | { | |
453 | ||
2d70df72 | 454 | debug("ntlm_auth build " __DATE__ ", " __TIME__ " starting up...\n"); |
6437ac71 | 455 | #ifdef DEBUG |
456 | debug("changing dir to /tmp\n"); | |
457 | chdir("/tmp"); | |
458 | #endif | |
94439e4e | 459 | |
a88de9ce | 460 | my_program_name = argv[0]; |
94439e4e | 461 | process_options(argc, argv); |
462 | ||
463 | debug("options processed OK\n"); | |
464 | ||
465 | /* initialize FDescs */ | |
466 | setbuf(stdout, NULL); | |
467 | setbuf(stderr, NULL); | |
468 | ||
469 | /* select the first domain controller we're going to use */ | |
470 | current_dc = controllers; | |
471 | if (load_balance != 0 && numcontrollers > 1) { | |
26ac0430 AJ |
472 | int n; |
473 | pid_t pid = getpid(); | |
474 | n = pid % numcontrollers; | |
475 | debug("load balancing. Selected controller #%d\n", n); | |
476 | while (n > 0) { | |
477 | current_dc = current_dc->next; | |
478 | n--; | |
479 | } | |
94439e4e | 480 | } |
481 | while (1) { | |
26ac0430 | 482 | manage_request(); |
94439e4e | 483 | } |
484 | return 0; | |
485 | } |