2 * -----------------------------------------------------------------------------
4 * Author: Markus Moeller (markus_moeller at compuserve.com)
6 * Copyright (C) 2007 Markus Moeller. All rights reserved.
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.
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.
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.
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.
27 * -----------------------------------------------------------------------------
30 * Hosted at http://sourceforge.net/projects/squidkerbauth
60 #include "getaddrinfo.h"
61 #include "getnameinfo.h"
63 #include "spnegohelp.h"
66 #if HAVE_HEIMDAL_KERBEROS
67 #if HAVE_GSSAPI_GSSAPI_H
68 #include <gssapi/gssapi.h>
71 #endif /* HAVE_GSSAPI_GSSAPI_H */
72 #else /* HAVE_HEIMDAL_KERBEROS */
73 #if HAVE_GSSAPI_GSSAPI_H
74 #include <gssapi/gssapi.h>
77 #endif /* HAVE_GSSAPI_GSSAPI_H */
78 #if HAVE_GSSAPI_GSSAPI_KRB5_H
79 #include <gssapi/gssapi_krb5.h>
80 #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
81 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
82 #include <gssapi/gssapi_generic.h>
83 #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
84 #endif /* HAVE_HEIMDAL_KERBEROS */
85 #ifndef gss_nt_service_name
86 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
89 #define PROGRAM "negotiate_kerberos_auth"
91 #ifndef MAX_AUTHTOKEN_LEN
92 #define MAX_AUTHTOKEN_LEN 65535
94 #ifndef SQUID_KERB_AUTH_VERSION
95 #define SQUID_KERB_AUTH_VERSION "3.0.1sq"
98 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
99 const char *function
, int debug
, int log
);
100 char *gethost_name(void);
101 static const char *LogTime(void);
103 static const unsigned char ntlmProtocol
[] =
104 { 'N', 'T', 'L', 'M', 'S', 'S', 'P', 0 };
111 static time_t last_t
= 0;
112 static char buf
[128];
114 gettimeofday(&now
, NULL
);
115 if (now
.tv_sec
!= last_t
) {
116 tm
= localtime((time_t *) & now
.tv_sec
);
117 strftime(buf
, 127, "%Y/%m/%d %H:%M:%S", tm
);
126 char hostname
[sysconf(_SC_HOST_NAME_MAX
)];
127 struct addrinfo
*hres
= NULL
, *hres_list
;
130 rc
= gethostname(hostname
, sysconf(_SC_HOST_NAME_MAX
));
132 fprintf(stderr
, "%s| %s: error while resolving hostname '%s'\n",
133 LogTime(), PROGRAM
, hostname
);
136 rc
= xgetaddrinfo(hostname
, NULL
, NULL
, &hres
);
139 "%s| %s: error while resolving hostname with getaddrinfo: %s\n",
140 LogTime(), PROGRAM
, xgai_strerror(rc
));
147 hres_list
= hres_list
->ai_next
;
149 rc
= xgetnameinfo(hres
->ai_addr
, hres
->ai_addrlen
, hostname
,
150 sizeof(hostname
), NULL
, 0, 0);
153 "%s| %s: error while resolving ip address with getnameinfo: %s\n",
154 LogTime(), PROGRAM
, xgai_strerror(rc
));
160 hostname
[sysconf(_SC_HOST_NAME_MAX
) - 1] = '\0';
161 return (xstrdup(hostname
));
165 check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
166 const char *function
, int debug
, int log
)
168 if (GSS_ERROR(major_status
)) {
169 OM_uint32 maj_stat
, min_stat
;
170 OM_uint32 msg_ctx
= 0;
171 gss_buffer_desc status_string
;
178 /* convert major status code (GSS-API error) to text */
179 maj_stat
= gss_display_status(&min_stat
, major_status
,
180 GSS_C_GSS_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
181 if (maj_stat
== GSS_S_COMPLETE
) {
182 if (sizeof(buf
) > len
+ status_string
.length
+ 1) {
183 sprintf(buf
+ len
, "%s", (char *) status_string
.value
);
184 len
+= status_string
.length
;
186 gss_release_buffer(&min_stat
, &status_string
);
189 gss_release_buffer(&min_stat
, &status_string
);
191 if (sizeof(buf
) > len
+ 2) {
192 sprintf(buf
+ len
, "%s", ". ");
197 /* convert minor status code (underlying routine error) to text */
198 maj_stat
= gss_display_status(&min_stat
, minor_status
,
199 GSS_C_MECH_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
200 if (maj_stat
== GSS_S_COMPLETE
) {
201 if (sizeof(buf
) > len
+ status_string
.length
) {
202 sprintf(buf
+ len
, "%s", (char *) status_string
.value
);
203 len
+= status_string
.length
;
205 gss_release_buffer(&min_stat
, &status_string
);
208 gss_release_buffer(&min_stat
, &status_string
);
211 fprintf(stderr
, "%s| %s: %s failed: %s\n", LogTime(), PROGRAM
,
213 fprintf(stdout
, "BH %s failed: %s\n", function
, buf
);
215 fprintf(stderr
, "%s| %s: User not authenticated\n", LogTime(),
225 main(int argc
, char *const argv
[])
227 char buf
[MAX_AUTHTOKEN_LEN
];
232 int opt
, debug
= 0, log
= 0, norealm
= 0;
236 OM_uint32 ret_flags
= 0, spnego_flag
= 0;
237 char *service_name
= (char *) "HTTP", *host_name
= NULL
;
239 char *service_principal
= NULL
;
240 OM_uint32 major_status
, minor_status
;
241 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
242 gss_name_t client_name
= GSS_C_NO_NAME
;
243 gss_name_t server_name
= GSS_C_NO_NAME
;
244 gss_cred_id_t server_creds
= GSS_C_NO_CREDENTIAL
;
245 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
246 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
247 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
248 const unsigned char *kerberosToken
= NULL
;
250 size_t kerberosTokenLength
= 0;
252 const unsigned char *spnegoToken
= NULL
;
253 size_t spnegoTokenLength
= 0;
255 setbuf(stdout
, NULL
);
258 while (-1 != (opt
= getopt(argc
, argv
, "dirs:h"))) {
270 service_principal
= xstrdup(optarg
);
273 fprintf(stderr
, "Usage: \n");
274 fprintf(stderr
, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
275 fprintf(stderr
, "-d full debug\n");
276 fprintf(stderr
, "-i informational messages\n");
277 fprintf(stderr
, "-r remove realm from username\n");
278 fprintf(stderr
, "-s service principal name\n");
279 fprintf(stderr
, "-h help\n");
281 "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
282 fprintf(stderr
, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
285 fprintf(stderr
, "%s| %s: unknown option: -%c.\n", LogTime(),
291 fprintf(stderr
, "%s| %s: Starting version %s\n", LogTime(), PROGRAM
,
292 SQUID_KERB_AUTH_VERSION
);
293 if (service_principal
&& strcasecmp(service_principal
, "GSS_C_NO_NAME")) {
294 service
.value
= service_principal
;
295 service
.length
= strlen((char *) service
.value
);
297 host_name
= gethost_name();
300 "%s| %s: Local hostname could not be determined. Please specify the service principal\n",
302 fprintf(stdout
, "BH hostname error\n");
305 service
.value
= xmalloc(strlen(service_name
) + strlen(host_name
) + 2);
306 snprintf((char*)service
.value
, strlen(service_name
) + strlen(host_name
) + 2,
307 "%s@%s", service_name
, host_name
);
308 service
.length
= strlen((char *) service
.value
);
312 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
316 "%s| %s: fgets() failed! dying..... errno=%d (%s)\n",
317 LogTime(), PROGRAM
, ferror(stdin
),
318 strerror(ferror(stdin
)));
320 fprintf(stdout
, "BH input error\n");
321 exit(1); /* BIIG buffer */
323 fprintf(stdout
, "BH input error\n");
327 c
= (char*)memchr(buf
, '\n', sizeof(buf
) - 1);
336 fprintf(stderr
, "%s| %s: Oversized message\n", LogTime(),
338 fprintf(stdout
, "BH Oversized message\n");
344 fprintf(stderr
, "%s| %s: Got '%s' from squid (length: %d).\n",
345 LogTime(), PROGRAM
, buf
, length
);
347 if (buf
[0] == '\0') {
349 fprintf(stderr
, "%s| %s: Invalid request\n", LogTime(),
351 fprintf(stdout
, "BH Invalid request\n");
355 if (strlen(buf
) < 2) {
357 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(),
359 fprintf(stdout
, "BH Invalid request\n");
363 if (!strncmp(buf
, "QQ", 2)) {
364 gss_release_buffer(&minor_status
, &input_token
);
365 gss_release_buffer(&minor_status
, &output_token
);
366 gss_release_buffer(&minor_status
, &service
);
367 gss_release_cred(&minor_status
, &server_creds
);
369 gss_release_name(&minor_status
, &server_name
);
371 gss_release_name(&minor_status
, &client_name
);
372 if (gss_context
!= GSS_C_NO_CONTEXT
)
373 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
375 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
377 xfree((char *) kerberosToken
);
378 kerberosToken
= NULL
;
381 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
383 xfree((char *) spnegoToken
);
394 fprintf(stdout
, "BH quit command\n");
398 if (strncmp(buf
, "YR", 2) && strncmp(buf
, "KK", 2)) {
400 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(),
402 fprintf(stdout
, "BH Invalid request\n");
405 if (!strncmp(buf
, "YR", 2)) {
406 if (gss_context
!= GSS_C_NO_CONTEXT
)
407 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
408 gss_context
= GSS_C_NO_CONTEXT
;
411 if (strlen(buf
) <= 3) {
413 fprintf(stderr
, "%s| %s: Invalid negotiate request [%s]\n",
414 LogTime(), PROGRAM
, buf
);
415 fprintf(stdout
, "BH Invalid negotiate request\n");
419 input_token
.length
= ska_base64_decode_len(buf
+ 3);
421 fprintf(stderr
, "%s| %s: Decode '%s' (decoded length: %d).\n",
422 LogTime(), PROGRAM
, buf
+ 3, (int) input_token
.length
);
423 input_token
.value
= xmalloc(input_token
.length
);
425 ska_base64_decode((char*)input_token
.value
, buf
+ 3, input_token
.length
);
429 if ((rc
= parseNegTokenInit(input_token
.value
,
431 &kerberosToken
, &kerberosTokenLength
)) != 0) {
433 fprintf(stderr
, "%s| %s: parseNegTokenInit failed with rc=%d\n",
434 LogTime(), PROGRAM
, rc
);
436 /* if between 100 and 200 it might be a GSSAPI token and not a SPNEGO token */
437 if (rc
< 100 || rc
> 199) {
439 fprintf(stderr
, "%s| %s: Invalid GSS-SPNEGO query [%s]\n",
440 LogTime(), PROGRAM
, buf
);
441 fprintf(stdout
, "BH Invalid GSS-SPNEGO query\n");
444 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
445 (!memcmp(input_token
.value
, ntlmProtocol
,
446 sizeof ntlmProtocol
))) {
448 fprintf(stderr
, "%s| %s: received type %d NTLM token\n",
450 (int) *((unsigned char *) input_token
.value
+
451 sizeof ntlmProtocol
));
452 fprintf(stdout
, "BH received type %d NTLM token\n",
453 (int) *((unsigned char *) input_token
.value
+
454 sizeof ntlmProtocol
));
458 fprintf(stderr
, "%s| %s: Token is possibly a GSSAPI token\n",
462 gss_release_buffer(&minor_status
, &input_token
);
463 input_token
.length
= kerberosTokenLength
;
464 input_token
.value
= (void *) kerberosToken
;
468 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
469 (!memcmp(input_token
.value
, ntlmProtocol
, sizeof ntlmProtocol
))) {
471 fprintf(stderr
, "%s| %s: received type %d NTLM token\n",
473 (int) *((unsigned char *) input_token
.value
+
474 sizeof ntlmProtocol
));
475 fprintf(stdout
, "BH received type %d NTLM token\n",
476 (int) *((unsigned char *) input_token
.value
+
477 sizeof ntlmProtocol
));
482 if (service_principal
) {
483 if (strcasecmp(service_principal
, "GSS_C_NO_NAME")) {
484 major_status
= gss_import_name(&minor_status
, &service
,
485 (gss_OID
) GSS_C_NULL_OID
, &server_name
);
488 server_name
= GSS_C_NO_NAME
;
489 major_status
= GSS_S_COMPLETE
;
492 major_status
= gss_import_name(&minor_status
, &service
,
493 gss_nt_service_name
, &server_name
);
496 if (check_gss_err(major_status
, minor_status
, "gss_import_name()",
501 gss_acquire_cred(&minor_status
, server_name
, GSS_C_INDEFINITE
,
502 GSS_C_NO_OID_SET
, GSS_C_ACCEPT
, &server_creds
, NULL
, NULL
);
503 if (check_gss_err(major_status
, minor_status
, "gss_acquire_cred()",
507 major_status
= gss_accept_sec_context(&minor_status
,
511 GSS_C_NO_CHANNEL_BINDINGS
,
512 &client_name
, NULL
, &output_token
, &ret_flags
, NULL
, NULL
);
515 if (output_token
.length
) {
518 if ((rc
= makeNegTokenTarg(output_token
.value
,
520 &spnegoToken
, &spnegoTokenLength
)) != 0) {
523 "%s| %s: makeNegTokenTarg failed with rc=%d\n",
524 LogTime(), PROGRAM
, rc
);
525 fprintf(stdout
, "BH makeNegTokenTarg failed with rc=%d\n",
530 spnegoToken
= output_token
.value
;
531 spnegoTokenLength
= output_token
.length
;
534 spnegoToken
= (unsigned char *)output_token
.value
;
535 spnegoTokenLength
= output_token
.length
;
537 token
= (char*)xmalloc(ska_base64_encode_len(spnegoTokenLength
));
540 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
542 fprintf(stdout
, "BH Not enough memory\n");
546 ska_base64_encode(token
, (const char *) spnegoToken
,
547 ska_base64_encode_len(spnegoTokenLength
), spnegoTokenLength
);
549 if (check_gss_err(major_status
, minor_status
,
550 "gss_accept_sec_context()", debug
, log
))
552 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
554 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(),
556 fprintf(stdout
, "TT %s\n", token
);
559 gss_release_buffer(&minor_status
, &output_token
);
561 gss_display_name(&minor_status
, client_name
, &output_token
,
564 if (check_gss_err(major_status
, minor_status
, "gss_display_name()",
567 user
= (char*)xmalloc(output_token
.length
+ 1);
570 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
572 fprintf(stdout
, "BH Not enough memory\n");
575 memcpy(user
, output_token
.value
, output_token
.length
);
576 user
[output_token
.length
] = '\0';
577 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
580 fprintf(stdout
, "AF %s %s\n", token
, user
);
582 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
, token
,
585 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(),
589 if (check_gss_err(major_status
, minor_status
,
590 "gss_accept_sec_context()", debug
, log
))
592 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
594 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(),
596 fprintf(stdout
, "NA %s\n", token
);
599 gss_release_buffer(&minor_status
, &output_token
);
601 gss_display_name(&minor_status
, client_name
, &output_token
,
604 if (check_gss_err(major_status
, minor_status
, "gss_display_name()",
608 * Return dummy token AA. May need an extra return tag then AF
610 user
= (char*)xmalloc(output_token
.length
+ 1);
613 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
615 fprintf(stdout
, "BH Not enough memory\n");
618 memcpy(user
, output_token
.value
, output_token
.length
);
619 user
[output_token
.length
] = '\0';
620 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
623 fprintf(stdout
, "AF %s %s\n", "AA==", user
);
625 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
,
628 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(),
633 gss_release_buffer(&minor_status
, &input_token
);
634 gss_release_buffer(&minor_status
, &output_token
);
635 gss_release_cred(&minor_status
, &server_creds
);
637 gss_release_name(&minor_status
, &server_name
);
639 gss_release_name(&minor_status
, &client_name
);
641 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
643 xfree((char *) kerberosToken
);
644 kerberosToken
= NULL
;
647 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
649 xfree((char *) spnegoToken
);
666 #ifndef MAX_AUTHTOKEN_LEN
667 #define MAX_AUTHTOKEN_LEN 65535
670 main(int argc
, char *const argv
[])
672 setbuf(stdout
, NULL
);
674 char buf
[MAX_AUTHTOKEN_LEN
];
676 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
677 fprintf(stdout
, "BH input error\n");
680 fprintf(stdout
, "BH Kerberos authentication not supported\n");
683 #endif /* HAVE_GSSAPI */