]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/negotiate_auth/kerberos/negotiate_kerberos_auth.cc
Compat: Shuffle final squid.h hacks into libcompat
[thirdparty/squid.git] / helpers / negotiate_auth / kerberos / negotiate_kerberos_auth.cc
1 /*
2 * -----------------------------------------------------------------------------
3 *
4 * Author: Markus Moeller (markus_moeller at compuserve.com)
5 *
6 * Copyright (C) 2007 Markus Moeller. All rights reserved.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21 *
22 * As a special exemption, M Moeller gives permission to link this program
23 * with MIT, Heimdal or other GSS/Kerberos libraries, and distribute
24 * the resulting executable, without including the source code for
25 * the Libraries in the source distribution.
26 *
27 * -----------------------------------------------------------------------------
28 */
29 /*
30 * Hosted at http://sourceforge.net/projects/squidkerbauth
31 */
32 #include "config.h"
33 #include "compat/getaddrinfo.h"
34 #include "compat/getnameinfo.h"
35
36 #if HAVE_GSSAPI
37
38 #if HAVE_STRING_H
39 #include <string.h>
40 #endif
41 #if HAVE_STDOI_H
42 #include <stdio.h>
43 #endif
44 #if HAVE_NETDB_H
45 #include <netdb.h>
46 #endif
47 #if HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50 #if HAVE_TIME_H
51 #include <time.h>
52 #endif
53
54 #include "util.h"
55 #include "base64.h"
56
57 #if HAVE_GSSAPI_GSSAPI_H
58 #include <gssapi/gssapi.h>
59 #elif HAVE_GSSAPI_H
60 #include <gssapi.h>
61 #endif /* HAVE_GSSAPI_GSSAPI_H */
62 #if HAVE_GSSAPI_GSSAPI_KRB5_H
63 #include <gssapi/gssapi_krb5.h>
64 #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
65 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
66 #include <gssapi/gssapi_generic.h>
67 #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
68 #ifndef gss_nt_service_name
69 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
70 #endif
71
72 #define PROGRAM "negotiate_kerberos_auth"
73
74 #ifndef MAX_AUTHTOKEN_LEN
75 #define MAX_AUTHTOKEN_LEN 65535
76 #endif
77 #ifndef SQUID_KERB_AUTH_VERSION
78 #define SQUID_KERB_AUTH_VERSION "3.0.2sq"
79 #endif
80
81 int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
82 const char *function, int debug, int log);
83 char *gethost_name(void);
84 static const char *LogTime(void);
85
86 static const unsigned char ntlmProtocol[] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', 0 };
87
88 static const char *
89 LogTime()
90 {
91 struct tm *tm;
92 struct timeval now;
93 static time_t last_t = 0;
94 static char buf[128];
95
96 gettimeofday(&now, NULL);
97 if (now.tv_sec != last_t) {
98 tm = localtime((time_t *) & now.tv_sec);
99 strftime(buf, 127, "%Y/%m/%d %H:%M:%S", tm);
100 last_t = now.tv_sec;
101 }
102 return buf;
103 }
104
105 char *
106 gethost_name(void)
107 {
108 /*
109 char hostname[sysconf(_SC_HOST_NAME_MAX)];
110 */
111 char hostname[1024];
112 struct addrinfo *hres = NULL, *hres_list;
113 int rc, count;
114
115 rc = gethostname(hostname, sysconf(_SC_HOST_NAME_MAX));
116 if (rc) {
117 fprintf(stderr, "%s| %s: ERROR: resolving hostname '%s' failed\n",
118 LogTime(), PROGRAM, hostname);
119 return NULL;
120 }
121 rc = getaddrinfo(hostname, NULL, NULL, &hres);
122 if (rc != 0) {
123 fprintf(stderr,
124 "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
125 LogTime(), PROGRAM, gai_strerror(rc));
126 return NULL;
127 }
128 hres_list = hres;
129 count = 0;
130 while (hres_list) {
131 count++;
132 hres_list = hres_list->ai_next;
133 }
134 rc = getnameinfo(hres->ai_addr, hres->ai_addrlen, hostname,
135 sizeof(hostname), NULL, 0, 0);
136 if (rc != 0) {
137 fprintf(stderr,
138 "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
139 LogTime(), PROGRAM, gai_strerror(rc));
140 freeaddrinfo(hres);
141 return NULL;
142 }
143
144 freeaddrinfo(hres);
145 hostname[sysconf(_SC_HOST_NAME_MAX) - 1] = '\0';
146 return (xstrdup(hostname));
147 }
148
149 int
150 check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
151 const char *function, int debug, int log)
152 {
153 if (GSS_ERROR(major_status)) {
154 OM_uint32 maj_stat, min_stat;
155 OM_uint32 msg_ctx = 0;
156 gss_buffer_desc status_string;
157 char buf[1024];
158 size_t len;
159
160 len = 0;
161 msg_ctx = 0;
162 while (!msg_ctx) {
163 /* convert major status code (GSS-API error) to text */
164 maj_stat = gss_display_status(&min_stat, major_status,
165 GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
166 if (maj_stat == GSS_S_COMPLETE) {
167 if (sizeof(buf) > len + status_string.length + 1) {
168 snprintf(buf + len, (sizeof(buf)-len), "%s", (char *) status_string.value);
169 len += status_string.length;
170 }
171 gss_release_buffer(&min_stat, &status_string);
172 break;
173 }
174 gss_release_buffer(&min_stat, &status_string);
175 }
176 if (sizeof(buf) > len + 2) {
177 snprintf(buf + len, (sizeof(buf)-len), "%s", ". ");
178 len += 2;
179 }
180 msg_ctx = 0;
181 while (!msg_ctx) {
182 /* convert minor status code (underlying routine error) to text */
183 maj_stat = gss_display_status(&min_stat, minor_status,
184 GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
185 if (maj_stat == GSS_S_COMPLETE) {
186 if (sizeof(buf) > len + status_string.length) {
187 snprintf(buf + len,(sizeof(buf)-len), "%s", (char *) status_string.value);
188 len += status_string.length;
189 }
190 gss_release_buffer(&min_stat, &status_string);
191 break;
192 }
193 gss_release_buffer(&min_stat, &status_string);
194 }
195 if (debug)
196 fprintf(stderr, "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM,
197 function, buf);
198 fprintf(stdout, "BH %s failed: %s\n", function, buf);
199 if (log)
200 fprintf(stderr, "%s| %s: INFO: User not authenticated\n", LogTime(),
201 PROGRAM);
202 return (1);
203 }
204 return (0);
205 }
206
207
208
209 int
210 main(int argc, char *const argv[])
211 {
212 char buf[MAX_AUTHTOKEN_LEN];
213 char *c, *p;
214 char *user = NULL;
215 int length = 0;
216 static int err = 0;
217 int opt, debug = 0, log = 0, norealm = 0;
218 OM_uint32 ret_flags = 0, spnego_flag = 0;
219 char *service_name = (char *) "HTTP", *host_name = NULL;
220 char *token = NULL;
221 char *service_principal = NULL;
222 OM_uint32 major_status, minor_status;
223 gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
224 gss_name_t client_name = GSS_C_NO_NAME;
225 gss_name_t server_name = GSS_C_NO_NAME;
226 gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
227 gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
228 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
229 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
230 const unsigned char *kerberosToken = NULL;
231 const unsigned char *spnegoToken = NULL;
232 size_t spnegoTokenLength = 0;
233
234 setbuf(stdout, NULL);
235 setbuf(stdin, NULL);
236
237 while (-1 != (opt = getopt(argc, argv, "dirs:h"))) {
238 switch (opt) {
239 case 'd':
240 debug = 1;
241 break;
242 case 'i':
243 log = 1;
244 break;
245 case 'r':
246 norealm = 1;
247 break;
248 case 's':
249 service_principal = xstrdup(optarg);
250 break;
251 case 'h':
252 fprintf(stderr, "Usage: \n");
253 fprintf(stderr, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
254 fprintf(stderr, "-d full debug\n");
255 fprintf(stderr, "-i informational messages\n");
256 fprintf(stderr, "-r remove realm from username\n");
257 fprintf(stderr, "-s service principal name\n");
258 fprintf(stderr, "-h help\n");
259 fprintf(stderr,
260 "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
261 fprintf(stderr, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
262 exit(0);
263 default:
264 fprintf(stderr, "%s| %s: WARNING: unknown option: -%c.\n", LogTime(),
265 PROGRAM, opt);
266 }
267 }
268
269 if (debug)
270 fprintf(stderr, "%s| %s: INFO: Starting version %s\n", LogTime(), PROGRAM,
271 SQUID_KERB_AUTH_VERSION);
272 if (service_principal && strcasecmp(service_principal, "GSS_C_NO_NAME")) {
273 service.value = service_principal;
274 service.length = strlen((char *) service.value);
275 } else {
276 host_name = gethost_name();
277 if (!host_name) {
278 fprintf(stderr,
279 "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
280 LogTime(), PROGRAM);
281 fprintf(stdout, "BH hostname error\n");
282 exit(-1);
283 }
284 service.value = xmalloc(strlen(service_name) + strlen(host_name) + 2);
285 snprintf((char*)service.value, strlen(service_name) + strlen(host_name) + 2,
286 "%s@%s", service_name, host_name);
287 service.length = strlen((char *) service.value);
288 }
289
290 while (1) {
291 if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
292 if (ferror(stdin)) {
293 if (debug)
294 fprintf(stderr,
295 "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
296 LogTime(), PROGRAM, ferror(stdin),
297 strerror(ferror(stdin)));
298
299 fprintf(stdout, "BH input error\n");
300 exit(1); /* BIIG buffer */
301 }
302 fprintf(stdout, "BH input error\n");
303 exit(0);
304 }
305
306 c = (char*)memchr(buf, '\n', sizeof(buf) - 1);
307 if (c) {
308 *c = '\0';
309 length = c - buf;
310 } else {
311 err = 1;
312 }
313 if (err) {
314 if (debug)
315 fprintf(stderr, "%s| %s: ERROR: Oversized message\n", LogTime(),
316 PROGRAM);
317 fprintf(stdout, "BH Oversized message\n");
318 err = 0;
319 continue;
320 }
321
322 if (debug)
323 fprintf(stderr, "%s| %s: DEBUG: Got '%s' from squid (length: %d).\n",
324 LogTime(), PROGRAM, buf, length);
325
326 if (buf[0] == '\0') {
327 if (debug)
328 fprintf(stderr, "%s| %s: ERROR: Invalid request\n", LogTime(),
329 PROGRAM);
330 fprintf(stdout, "BH Invalid request\n");
331 continue;
332 }
333
334 if (strlen(buf) < 2) {
335 if (debug)
336 fprintf(stderr, "%s| %s: ERROR: Invalid request [%s]\n", LogTime(),
337 PROGRAM, buf);
338 fprintf(stdout, "BH Invalid request\n");
339 continue;
340 }
341
342 if (!strncmp(buf, "QQ", 2)) {
343 gss_release_buffer(&minor_status, &input_token);
344 gss_release_buffer(&minor_status, &output_token);
345 gss_release_buffer(&minor_status, &service);
346 gss_release_cred(&minor_status, &server_creds);
347 if (server_name)
348 gss_release_name(&minor_status, &server_name);
349 if (client_name)
350 gss_release_name(&minor_status, &client_name);
351 if (gss_context != GSS_C_NO_CONTEXT)
352 gss_delete_sec_context(&minor_status, &gss_context, NULL);
353 if (kerberosToken) {
354 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
355 if (!spnego_flag)
356 xfree((char *) kerberosToken);
357 kerberosToken = NULL;
358 }
359 if (spnego_flag) {
360 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
361 if (spnegoToken)
362 xfree((char *) spnegoToken);
363 spnegoToken = NULL;
364 }
365 if (token) {
366 xfree(token);
367 token = NULL;
368 }
369 if (host_name) {
370 xfree(host_name);
371 host_name = NULL;
372 }
373 fprintf(stdout, "BH quit command\n");
374 exit(0);
375 }
376
377 if (strncmp(buf, "YR", 2) && strncmp(buf, "KK", 2)) {
378 if (debug)
379 fprintf(stderr, "%s| %s: ERROR: Invalid request [%s]\n", LogTime(),
380 PROGRAM, buf);
381 fprintf(stdout, "BH Invalid request\n");
382 continue;
383 }
384 if (!strncmp(buf, "YR", 2)) {
385 if (gss_context != GSS_C_NO_CONTEXT)
386 gss_delete_sec_context(&minor_status, &gss_context, NULL);
387 gss_context = GSS_C_NO_CONTEXT;
388 }
389
390 if (strlen(buf) <= 3) {
391 if (debug)
392 fprintf(stderr, "%s| %s: ERROR: Invalid negotiate request [%s]\n",
393 LogTime(), PROGRAM, buf);
394 fprintf(stdout, "BH Invalid negotiate request\n");
395 continue;
396 }
397
398 input_token.length = ska_base64_decode_len(buf + 3);
399 if (debug)
400 fprintf(stderr, "%s| %s: DEBUG: Decode '%s' (decoded length: %d).\n",
401 LogTime(), PROGRAM, buf + 3, (int) input_token.length);
402 input_token.value = xmalloc(input_token.length);
403
404 ska_base64_decode((char*)input_token.value, buf + 3, input_token.length);
405
406
407 if ((input_token.length >= sizeof ntlmProtocol + 1) &&
408 (!memcmp(input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
409 if (debug)
410 fprintf(stderr, "%s| %s: WARNING: received type %d NTLM token\n",
411 LogTime(), PROGRAM,
412 (int) *((unsigned char *) input_token.value +
413 sizeof ntlmProtocol));
414 fprintf(stdout, "BH received type %d NTLM token\n",
415 (int) *((unsigned char *) input_token.value +
416 sizeof ntlmProtocol));
417 goto cleanup;
418 }
419
420 if (service_principal) {
421 if (strcasecmp(service_principal, "GSS_C_NO_NAME")) {
422 major_status = gss_import_name(&minor_status, &service,
423 (gss_OID) GSS_C_NULL_OID, &server_name);
424
425 } else {
426 server_name = GSS_C_NO_NAME;
427 major_status = GSS_S_COMPLETE;
428 }
429 } else {
430 major_status = gss_import_name(&minor_status, &service,
431 gss_nt_service_name, &server_name);
432 }
433
434 if (check_gss_err(major_status, minor_status, "gss_import_name()",
435 debug, log))
436 goto cleanup;
437
438 major_status =
439 gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
440 GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_creds, NULL, NULL);
441 if (check_gss_err(major_status, minor_status, "gss_acquire_cred()",
442 debug, log))
443 goto cleanup;
444
445 major_status = gss_accept_sec_context(&minor_status,
446 &gss_context,
447 server_creds,
448 &input_token,
449 GSS_C_NO_CHANNEL_BINDINGS,
450 &client_name, NULL, &output_token, &ret_flags, NULL, NULL);
451
452
453 if (output_token.length) {
454 spnegoToken = (const unsigned char*)output_token.value;
455 spnegoTokenLength = output_token.length;
456 token = (char*)xmalloc(ska_base64_encode_len(spnegoTokenLength));
457 if (token == NULL) {
458 if (debug)
459 fprintf(stderr, "%s| %s: ERROR: Not enough memory\n", LogTime(),
460 PROGRAM);
461 fprintf(stdout, "BH Not enough memory\n");
462 goto cleanup;
463 }
464
465 ska_base64_encode(token, (const char *) spnegoToken,
466 ska_base64_encode_len(spnegoTokenLength), spnegoTokenLength);
467
468 if (check_gss_err(major_status, minor_status,
469 "gss_accept_sec_context()", debug, log))
470 goto cleanup;
471 if (major_status & GSS_S_CONTINUE_NEEDED) {
472 if (debug)
473 fprintf(stderr, "%s| %s: INFO: continuation needed\n", LogTime(),
474 PROGRAM);
475 fprintf(stdout, "TT %s\n", token);
476 goto cleanup;
477 }
478 gss_release_buffer(&minor_status, &output_token);
479 major_status =
480 gss_display_name(&minor_status, client_name, &output_token,
481 NULL);
482
483 if (check_gss_err(major_status, minor_status, "gss_display_name()",
484 debug, log))
485 goto cleanup;
486 user = (char*)xmalloc(output_token.length + 1);
487 if (user == NULL) {
488 if (debug)
489 fprintf(stderr, "%s| %s: ERROR: Not enough memory\n", LogTime(),
490 PROGRAM);
491 fprintf(stdout, "BH Not enough memory\n");
492 goto cleanup;
493 }
494 memcpy(user, output_token.value, output_token.length);
495 user[output_token.length] = '\0';
496 if (norealm && (p = strchr(user, '@')) != NULL) {
497 *p = '\0';
498 }
499 fprintf(stdout, "AF %s %s\n", token, user);
500 if (debug)
501 fprintf(stderr, "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM, token,
502 user);
503 if (log)
504 fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
505 PROGRAM, user);
506 goto cleanup;
507 } else {
508 if (check_gss_err(major_status, minor_status,
509 "gss_accept_sec_context()", debug, log))
510 goto cleanup;
511 if (major_status & GSS_S_CONTINUE_NEEDED) {
512 if (debug)
513 fprintf(stderr, "%s| %s: INFO: continuation needed\n", LogTime(),
514 PROGRAM);
515 fprintf(stdout, "NA %s\n", token);
516 goto cleanup;
517 }
518 gss_release_buffer(&minor_status, &output_token);
519 major_status =
520 gss_display_name(&minor_status, client_name, &output_token,
521 NULL);
522
523 if (check_gss_err(major_status, minor_status, "gss_display_name()",
524 debug, log))
525 goto cleanup;
526 /*
527 * Return dummy token AA. May need an extra return tag then AF
528 */
529 user = (char*)xmalloc(output_token.length + 1);
530 if (user == NULL) {
531 if (debug)
532 fprintf(stderr, "%s| %s: ERROR: Not enough memory\n", LogTime(),
533 PROGRAM);
534 fprintf(stdout, "BH Not enough memory\n");
535 goto cleanup;
536 }
537 memcpy(user, output_token.value, output_token.length);
538 user[output_token.length] = '\0';
539 if (norealm && (p = strchr(user, '@')) != NULL) {
540 *p = '\0';
541 }
542 fprintf(stdout, "AF %s %s\n", "AA==", user);
543 if (debug)
544 fprintf(stderr, "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM,
545 "AA==", user);
546 if (log)
547 fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
548 PROGRAM, user);
549
550 }
551 cleanup:
552 gss_release_buffer(&minor_status, &input_token);
553 gss_release_buffer(&minor_status, &output_token);
554 gss_release_cred(&minor_status, &server_creds);
555 if (server_name)
556 gss_release_name(&minor_status, &server_name);
557 if (client_name)
558 gss_release_name(&minor_status, &client_name);
559 if (kerberosToken) {
560 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
561 if (!spnego_flag)
562 xfree((char *) kerberosToken);
563 kerberosToken = NULL;
564 }
565 if (spnego_flag) {
566 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
567 if (spnegoToken)
568 xfree((char *) spnegoToken);
569 spnegoToken = NULL;
570 }
571 if (token) {
572 xfree(token);
573 token = NULL;
574 }
575 if (user) {
576 xfree(user);
577 user = NULL;
578 }
579 continue;
580 }
581 }
582 #else
583 #include <stdio.h>
584 #include <stdlib.h>
585 #ifndef MAX_AUTHTOKEN_LEN
586 #define MAX_AUTHTOKEN_LEN 65535
587 #endif
588 int
589 main(int argc, char *const argv[])
590 {
591 setbuf(stdout, NULL);
592 setbuf(stdin, NULL);
593 char buf[MAX_AUTHTOKEN_LEN];
594 while (1) {
595 if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
596 fprintf(stdout, "BH input error\n");
597 exit(0);
598 }
599 fprintf(stdout, "BH Kerberos authentication not supported\n");
600 }
601 }
602 #endif /* HAVE_GSSAPI */