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
[] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', 0 };
110 static time_t last_t
= 0;
111 static char buf
[128];
113 gettimeofday(&now
, NULL
);
114 if (now
.tv_sec
!= last_t
) {
115 tm
= localtime((time_t *) & now
.tv_sec
);
116 strftime(buf
, 127, "%Y/%m/%d %H:%M:%S", tm
);
125 char hostname
[sysconf(_SC_HOST_NAME_MAX
)];
126 struct addrinfo
*hres
= NULL
, *hres_list
;
129 rc
= gethostname(hostname
, sysconf(_SC_HOST_NAME_MAX
));
131 fprintf(stderr
, "%s| %s: error while resolving hostname '%s'\n",
132 LogTime(), PROGRAM
, hostname
);
135 rc
= xgetaddrinfo(hostname
, NULL
, NULL
, &hres
);
138 "%s| %s: error while resolving hostname with getaddrinfo: %s\n",
139 LogTime(), PROGRAM
, xgai_strerror(rc
));
146 hres_list
= hres_list
->ai_next
;
148 rc
= xgetnameinfo(hres
->ai_addr
, hres
->ai_addrlen
, hostname
,
149 sizeof(hostname
), NULL
, 0, 0);
152 "%s| %s: error while resolving ip address with getnameinfo: %s\n",
153 LogTime(), PROGRAM
, xgai_strerror(rc
));
159 hostname
[sysconf(_SC_HOST_NAME_MAX
) - 1] = '\0';
160 return (xstrdup(hostname
));
164 check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
165 const char *function
, int debug
, int log
)
167 if (GSS_ERROR(major_status
)) {
168 OM_uint32 maj_stat
, min_stat
;
169 OM_uint32 msg_ctx
= 0;
170 gss_buffer_desc status_string
;
177 /* convert major status code (GSS-API error) to text */
178 maj_stat
= gss_display_status(&min_stat
, major_status
,
179 GSS_C_GSS_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
180 if (maj_stat
== GSS_S_COMPLETE
) {
181 if (sizeof(buf
) > len
+ status_string
.length
+ 1) {
182 sprintf(buf
+ len
, "%s", (char *) status_string
.value
);
183 len
+= status_string
.length
;
185 gss_release_buffer(&min_stat
, &status_string
);
188 gss_release_buffer(&min_stat
, &status_string
);
190 if (sizeof(buf
) > len
+ 2) {
191 sprintf(buf
+ len
, "%s", ". ");
196 /* convert minor status code (underlying routine error) to text */
197 maj_stat
= gss_display_status(&min_stat
, minor_status
,
198 GSS_C_MECH_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
199 if (maj_stat
== GSS_S_COMPLETE
) {
200 if (sizeof(buf
) > len
+ status_string
.length
) {
201 sprintf(buf
+ len
, "%s", (char *) status_string
.value
);
202 len
+= status_string
.length
;
204 gss_release_buffer(&min_stat
, &status_string
);
207 gss_release_buffer(&min_stat
, &status_string
);
210 fprintf(stderr
, "%s| %s: %s failed: %s\n", LogTime(), PROGRAM
,
212 fprintf(stdout
, "BH %s failed: %s\n", function
, buf
);
214 fprintf(stderr
, "%s| %s: User not authenticated\n", LogTime(),
224 main(int argc
, char *const argv
[])
226 char buf
[MAX_AUTHTOKEN_LEN
];
231 int opt
, debug
= 0, log
= 0, norealm
= 0;
235 OM_uint32 ret_flags
= 0, spnego_flag
= 0;
236 char *service_name
= (char *) "HTTP", *host_name
= NULL
;
238 char *service_principal
= NULL
;
239 OM_uint32 major_status
, minor_status
;
240 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
241 gss_name_t client_name
= GSS_C_NO_NAME
;
242 gss_name_t server_name
= GSS_C_NO_NAME
;
243 gss_cred_id_t server_creds
= GSS_C_NO_CREDENTIAL
;
244 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
245 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
246 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
247 const unsigned char *kerberosToken
= NULL
;
249 size_t kerberosTokenLength
= 0;
251 const unsigned char *spnegoToken
= NULL
;
252 size_t spnegoTokenLength
= 0;
254 setbuf(stdout
, NULL
);
257 while (-1 != (opt
= getopt(argc
, argv
, "dirs:h"))) {
269 service_principal
= xstrdup(optarg
);
272 fprintf(stderr
, "Usage: \n");
273 fprintf(stderr
, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
274 fprintf(stderr
, "-d full debug\n");
275 fprintf(stderr
, "-i informational messages\n");
276 fprintf(stderr
, "-r remove realm from username\n");
277 fprintf(stderr
, "-s service principal name\n");
278 fprintf(stderr
, "-h help\n");
280 "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
281 fprintf(stderr
, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
284 fprintf(stderr
, "%s| %s: unknown option: -%c.\n", LogTime(),
290 fprintf(stderr
, "%s| %s: Starting version %s\n", LogTime(), PROGRAM
,
291 SQUID_KERB_AUTH_VERSION
);
292 if (service_principal
&& strcasecmp(service_principal
, "GSS_C_NO_NAME")) {
293 service
.value
= service_principal
;
294 service
.length
= strlen((char *) service
.value
);
296 host_name
= gethost_name();
299 "%s| %s: Local hostname could not be determined. Please specify the service principal\n",
301 fprintf(stdout
, "BH hostname error\n");
304 service
.value
= xmalloc(strlen(service_name
) + strlen(host_name
) + 2);
305 snprintf((char*)service
.value
, strlen(service_name
) + strlen(host_name
) + 2,
306 "%s@%s", service_name
, host_name
);
307 service
.length
= strlen((char *) service
.value
);
311 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
315 "%s| %s: fgets() failed! dying..... errno=%d (%s)\n",
316 LogTime(), PROGRAM
, ferror(stdin
),
317 strerror(ferror(stdin
)));
319 fprintf(stdout
, "BH input error\n");
320 exit(1); /* BIIG buffer */
322 fprintf(stdout
, "BH input error\n");
326 c
= (char*)memchr(buf
, '\n', sizeof(buf
) - 1);
335 fprintf(stderr
, "%s| %s: Oversized message\n", LogTime(),
337 fprintf(stdout
, "BH Oversized message\n");
343 fprintf(stderr
, "%s| %s: Got '%s' from squid (length: %d).\n",
344 LogTime(), PROGRAM
, buf
, length
);
346 if (buf
[0] == '\0') {
348 fprintf(stderr
, "%s| %s: Invalid request\n", LogTime(),
350 fprintf(stdout
, "BH Invalid request\n");
354 if (strlen(buf
) < 2) {
356 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(),
358 fprintf(stdout
, "BH Invalid request\n");
362 if (!strncmp(buf
, "QQ", 2)) {
363 gss_release_buffer(&minor_status
, &input_token
);
364 gss_release_buffer(&minor_status
, &output_token
);
365 gss_release_buffer(&minor_status
, &service
);
366 gss_release_cred(&minor_status
, &server_creds
);
368 gss_release_name(&minor_status
, &server_name
);
370 gss_release_name(&minor_status
, &client_name
);
371 if (gss_context
!= GSS_C_NO_CONTEXT
)
372 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
374 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
376 xfree((char *) kerberosToken
);
377 kerberosToken
= NULL
;
380 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
382 xfree((char *) spnegoToken
);
393 fprintf(stdout
, "BH quit command\n");
397 if (strncmp(buf
, "YR", 2) && strncmp(buf
, "KK", 2)) {
399 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(),
401 fprintf(stdout
, "BH Invalid request\n");
404 if (!strncmp(buf
, "YR", 2)) {
405 if (gss_context
!= GSS_C_NO_CONTEXT
)
406 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
407 gss_context
= GSS_C_NO_CONTEXT
;
410 if (strlen(buf
) <= 3) {
412 fprintf(stderr
, "%s| %s: Invalid negotiate request [%s]\n",
413 LogTime(), PROGRAM
, buf
);
414 fprintf(stdout
, "BH Invalid negotiate request\n");
418 input_token
.length
= ska_base64_decode_len(buf
+ 3);
420 fprintf(stderr
, "%s| %s: Decode '%s' (decoded length: %d).\n",
421 LogTime(), PROGRAM
, buf
+ 3, (int) input_token
.length
);
422 input_token
.value
= xmalloc(input_token
.length
);
424 ska_base64_decode((char*)input_token
.value
, buf
+ 3, input_token
.length
);
428 if ((rc
= parseNegTokenInit(input_token
.value
,
430 &kerberosToken
, &kerberosTokenLength
)) != 0) {
432 fprintf(stderr
, "%s| %s: parseNegTokenInit failed with rc=%d\n",
433 LogTime(), PROGRAM
, rc
);
435 /* if between 100 and 200 it might be a GSSAPI token and not a SPNEGO token */
436 if (rc
< 100 || rc
> 199) {
438 fprintf(stderr
, "%s| %s: Invalid GSS-SPNEGO query [%s]\n",
439 LogTime(), PROGRAM
, buf
);
440 fprintf(stdout
, "BH Invalid GSS-SPNEGO query\n");
443 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
444 (!memcmp(input_token
.value
, ntlmProtocol
,
445 sizeof ntlmProtocol
))) {
447 fprintf(stderr
, "%s| %s: received type %d NTLM token\n",
449 (int) *((unsigned char *) input_token
.value
+
450 sizeof ntlmProtocol
));
451 fprintf(stdout
, "BH received type %d NTLM token\n",
452 (int) *((unsigned char *) input_token
.value
+
453 sizeof ntlmProtocol
));
457 fprintf(stderr
, "%s| %s: Token is possibly a GSSAPI token\n",
461 gss_release_buffer(&minor_status
, &input_token
);
462 input_token
.length
= kerberosTokenLength
;
463 input_token
.value
= (void *) kerberosToken
;
467 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
468 (!memcmp(input_token
.value
, ntlmProtocol
, sizeof ntlmProtocol
))) {
470 fprintf(stderr
, "%s| %s: received type %d NTLM token\n",
472 (int) *((unsigned char *) input_token
.value
+
473 sizeof ntlmProtocol
));
474 fprintf(stdout
, "BH received type %d NTLM token\n",
475 (int) *((unsigned char *) input_token
.value
+
476 sizeof ntlmProtocol
));
481 if (service_principal
) {
482 if (strcasecmp(service_principal
, "GSS_C_NO_NAME")) {
483 major_status
= gss_import_name(&minor_status
, &service
,
484 (gss_OID
) GSS_C_NULL_OID
, &server_name
);
487 server_name
= GSS_C_NO_NAME
;
488 major_status
= GSS_S_COMPLETE
;
491 major_status
= gss_import_name(&minor_status
, &service
,
492 gss_nt_service_name
, &server_name
);
495 if (check_gss_err(major_status
, minor_status
, "gss_import_name()",
500 gss_acquire_cred(&minor_status
, server_name
, GSS_C_INDEFINITE
,
501 GSS_C_NO_OID_SET
, GSS_C_ACCEPT
, &server_creds
, NULL
, NULL
);
502 if (check_gss_err(major_status
, minor_status
, "gss_acquire_cred()",
506 major_status
= gss_accept_sec_context(&minor_status
,
510 GSS_C_NO_CHANNEL_BINDINGS
,
511 &client_name
, NULL
, &output_token
, &ret_flags
, NULL
, NULL
);
514 if (output_token
.length
) {
517 if ((rc
= makeNegTokenTarg(output_token
.value
,
519 &spnegoToken
, &spnegoTokenLength
)) != 0) {
522 "%s| %s: makeNegTokenTarg failed with rc=%d\n",
523 LogTime(), PROGRAM
, rc
);
524 fprintf(stdout
, "BH makeNegTokenTarg failed with rc=%d\n",
529 spnegoToken
= output_token
.value
;
530 spnegoTokenLength
= output_token
.length
;
533 spnegoToken
= (unsigned char *)output_token
.value
;
534 spnegoTokenLength
= output_token
.length
;
536 token
= (char*)xmalloc(ska_base64_encode_len(spnegoTokenLength
));
539 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
541 fprintf(stdout
, "BH Not enough memory\n");
545 ska_base64_encode(token
, (const char *) spnegoToken
,
546 ska_base64_encode_len(spnegoTokenLength
), spnegoTokenLength
);
548 if (check_gss_err(major_status
, minor_status
,
549 "gss_accept_sec_context()", debug
, log
))
551 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
553 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(),
555 fprintf(stdout
, "TT %s\n", token
);
558 gss_release_buffer(&minor_status
, &output_token
);
560 gss_display_name(&minor_status
, client_name
, &output_token
,
563 if (check_gss_err(major_status
, minor_status
, "gss_display_name()",
566 user
= (char*)xmalloc(output_token
.length
+ 1);
569 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
571 fprintf(stdout
, "BH Not enough memory\n");
574 memcpy(user
, output_token
.value
, output_token
.length
);
575 user
[output_token
.length
] = '\0';
576 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
579 fprintf(stdout
, "AF %s %s\n", token
, user
);
581 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
, token
,
584 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(),
588 if (check_gss_err(major_status
, minor_status
,
589 "gss_accept_sec_context()", debug
, log
))
591 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
593 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(),
595 fprintf(stdout
, "NA %s\n", token
);
598 gss_release_buffer(&minor_status
, &output_token
);
600 gss_display_name(&minor_status
, client_name
, &output_token
,
603 if (check_gss_err(major_status
, minor_status
, "gss_display_name()",
607 * Return dummy token AA. May need an extra return tag then AF
609 user
= (char*)xmalloc(output_token
.length
+ 1);
612 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(),
614 fprintf(stdout
, "BH Not enough memory\n");
617 memcpy(user
, output_token
.value
, output_token
.length
);
618 user
[output_token
.length
] = '\0';
619 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
622 fprintf(stdout
, "AF %s %s\n", "AA==", user
);
624 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
,
627 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(),
632 gss_release_buffer(&minor_status
, &input_token
);
633 gss_release_buffer(&minor_status
, &output_token
);
634 gss_release_cred(&minor_status
, &server_creds
);
636 gss_release_name(&minor_status
, &server_name
);
638 gss_release_name(&minor_status
, &client_name
);
640 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
642 xfree((char *) kerberosToken
);
643 kerberosToken
= NULL
;
646 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
648 xfree((char *) spnegoToken
);
665 #ifndef MAX_AUTHTOKEN_LEN
666 #define MAX_AUTHTOKEN_LEN 65535
669 main(int argc
, char *const argv
[])
671 setbuf(stdout
, NULL
);
673 char buf
[MAX_AUTHTOKEN_LEN
];
675 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
676 fprintf(stdout
, "BH input error\n");
679 fprintf(stdout
, "BH Kerberos authentication not supported\n");
682 #endif /* HAVE_GSSAPI */