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
33 #include "compat/getaddrinfo.h"
34 #include "compat/getnameinfo.h"
57 #if HAVE_GSSAPI_GSSAPI_H
58 #include <gssapi/gssapi.h>
63 #if !HAVE_HEIMDAL_KERBEROS
64 #if HAVE_GSSAPI_GSSAPI_KRB5_H
65 #include <gssapi/gssapi_krb5.h>
67 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
68 #include <gssapi/gssapi_generic.h>
70 #if HAVE_GSSAPI_GSSAPI_EXT_H
71 #include <gssapi/gssapi_ext.h>
75 #ifndef gss_nt_service_name
76 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
79 #define PROGRAM "negotiate_kerberos_auth"
81 #ifndef MAX_AUTHTOKEN_LEN
82 #define MAX_AUTHTOKEN_LEN 65535
84 #ifndef SQUID_KERB_AUTH_VERSION
85 #define SQUID_KERB_AUTH_VERSION "3.0.4sq"
88 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
89 const char *function
, int log
);
90 char *gethost_name(void);
91 static const char *LogTime(void);
93 static const unsigned char ntlmProtocol
[] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0};
100 static time_t last_t
= 0;
101 static char buf
[128];
103 gettimeofday(&now
, NULL
);
104 if (now
.tv_sec
!= last_t
) {
105 tm
= localtime((time_t *) & now
.tv_sec
);
106 strftime(buf
, 127, "%Y/%m/%d %H:%M:%S", tm
);
116 * char hostname[sysconf(_SC_HOST_NAME_MAX)];
119 struct addrinfo
*hres
= NULL
, *hres_list
;
122 rc
= gethostname(hostname
, sysconf(_SC_HOST_NAME_MAX
));
124 fprintf(stderr
, "%s| %s: ERROR: resolving hostname '%s' failed\n",
125 LogTime(), PROGRAM
, hostname
);
128 rc
= getaddrinfo(hostname
, NULL
, NULL
, &hres
);
131 "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
132 LogTime(), PROGRAM
, gai_strerror(rc
));
139 hres_list
= hres_list
->ai_next
;
141 rc
= getnameinfo(hres
->ai_addr
, hres
->ai_addrlen
, hostname
,
142 sizeof(hostname
), NULL
, 0, 0);
145 "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
146 LogTime(), PROGRAM
, gai_strerror(rc
));
151 hostname
[sysconf(_SC_HOST_NAME_MAX
) - 1] = '\0';
152 return (xstrdup(hostname
));
156 check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
157 const char *function
, int log
)
159 if (GSS_ERROR(major_status
)) {
160 OM_uint32 maj_stat
, min_stat
;
161 OM_uint32 msg_ctx
= 0;
162 gss_buffer_desc status_string
;
169 /* convert major status code (GSS-API error) to text */
170 maj_stat
= gss_display_status(&min_stat
, major_status
,
171 GSS_C_GSS_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
172 if (maj_stat
== GSS_S_COMPLETE
&& status_string
.length
> 0) {
173 if (sizeof(buf
) > len
+ status_string
.length
+ 1) {
174 snprintf(buf
+ len
, (sizeof(buf
) - len
), "%s", (char *) status_string
.value
);
175 len
+= status_string
.length
;
179 gss_release_buffer(&min_stat
, &status_string
);
181 if (sizeof(buf
) > len
+ 2) {
182 snprintf(buf
+ len
, (sizeof(buf
) - len
), "%s", ". ");
187 /* convert minor status code (underlying routine error) to text */
188 maj_stat
= gss_display_status(&min_stat
, minor_status
,
189 GSS_C_MECH_CODE
, GSS_C_NULL_OID
, &msg_ctx
, &status_string
);
190 if (maj_stat
== GSS_S_COMPLETE
&& status_string
.length
> 0) {
191 if (sizeof(buf
) > len
+ status_string
.length
) {
192 snprintf(buf
+ len
, (sizeof(buf
) - len
), "%s", (char *) status_string
.value
);
193 len
+= status_string
.length
;
197 gss_release_buffer(&min_stat
, &status_string
);
199 debug((char *) "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM
, function
, buf
);
200 fprintf(stdout
, "BH %s failed: %s\n", function
, buf
);
202 fprintf(stderr
, "%s| %s: INFO: User not authenticated\n", LogTime(),
212 main(int argc
, char *const argv
[])
214 char buf
[MAX_AUTHTOKEN_LEN
];
219 int opt
, log
= 0, norealm
= 0;
220 OM_uint32 ret_flags
= 0, spnego_flag
= 0;
221 char *service_name
= (char *) "HTTP", *host_name
= NULL
;
223 char *service_principal
= NULL
;
224 OM_uint32 major_status
, minor_status
;
225 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
226 gss_name_t client_name
= GSS_C_NO_NAME
;
227 gss_name_t server_name
= GSS_C_NO_NAME
;
228 gss_cred_id_t server_creds
= GSS_C_NO_CREDENTIAL
;
229 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
230 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
231 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
232 const unsigned char *kerberosToken
= NULL
;
233 const unsigned char *spnegoToken
= NULL
;
234 size_t spnegoTokenLength
= 0;
236 setbuf(stdout
, NULL
);
239 while (-1 != (opt
= getopt(argc
, argv
, "dirs:h"))) {
251 service_principal
= xstrdup(optarg
);
254 fprintf(stderr
, "Usage: \n");
255 fprintf(stderr
, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
256 fprintf(stderr
, "-d full debug\n");
257 fprintf(stderr
, "-i informational messages\n");
258 fprintf(stderr
, "-r remove realm from username\n");
259 fprintf(stderr
, "-s service principal name\n");
260 fprintf(stderr
, "-h help\n");
262 "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
263 fprintf(stderr
, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
266 fprintf(stderr
, "%s| %s: WARNING: unknown option: -%c.\n", LogTime(),
271 debug((char *) "%s| %s: INFO: Starting version %s\n", LogTime(), PROGRAM
, 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
);
276 host_name
= gethost_name();
279 "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
281 fprintf(stdout
, "BH hostname error\n");
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
);
291 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
293 debug((char *) "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
294 LogTime(), PROGRAM
, ferror(stdin
),
295 strerror(ferror(stdin
)));
297 fprintf(stdout
, "BH input error\n");
298 exit(1); /* BIIG buffer */
300 fprintf(stdout
, "BH input error\n");
303 c
= (char *) memchr(buf
, '\n', sizeof(buf
) - 1);
311 debug((char *) "%s| %s: ERROR: Oversized message\n", LogTime(), PROGRAM
);
312 fprintf(stdout
, "BH Oversized message\n");
316 debug((char *) "%s| %s: DEBUG: Got '%s' from squid (length: %d).\n", LogTime(), PROGRAM
, buf
, length
);
318 if (buf
[0] == '\0') {
319 debug((char *) "%s| %s: ERROR: Invalid request\n", LogTime(), PROGRAM
);
320 fprintf(stdout
, "BH Invalid request\n");
323 if (strlen(buf
) < 2) {
324 debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM
, buf
);
325 fprintf(stdout
, "BH Invalid request\n");
328 if (!strncmp(buf
, "QQ", 2)) {
329 gss_release_buffer(&minor_status
, &input_token
);
330 gss_release_buffer(&minor_status
, &output_token
);
331 gss_release_buffer(&minor_status
, &service
);
332 gss_release_cred(&minor_status
, &server_creds
);
334 gss_release_name(&minor_status
, &server_name
);
336 gss_release_name(&minor_status
, &client_name
);
337 if (gss_context
!= GSS_C_NO_CONTEXT
)
338 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
340 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
342 xfree((char *) kerberosToken
);
343 kerberosToken
= NULL
;
346 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
348 xfree((char *) spnegoToken
);
359 fprintf(stdout
, "BH quit command\n");
362 if (strncmp(buf
, "YR", 2) && strncmp(buf
, "KK", 2)) {
363 debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM
, buf
);
364 fprintf(stdout
, "BH Invalid request\n");
367 if (!strncmp(buf
, "YR", 2)) {
368 if (gss_context
!= GSS_C_NO_CONTEXT
)
369 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
370 gss_context
= GSS_C_NO_CONTEXT
;
372 if (strlen(buf
) <= 3) {
373 debug((char *) "%s| %s: ERROR: Invalid negotiate request [%s]\n", LogTime(), PROGRAM
, buf
);
374 fprintf(stdout
, "BH Invalid negotiate request\n");
377 input_token
.length
= ska_base64_decode_len(buf
+ 3);
378 debug((char *) "%s| %s: DEBUG: Decode '%s' (decoded length: %d).\n",
379 LogTime(), PROGRAM
, buf
+ 3, (int) input_token
.length
);
380 input_token
.value
= xmalloc(input_token
.length
);
382 ska_base64_decode((char *) input_token
.value
, buf
+ 3, input_token
.length
);
385 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
386 (!memcmp(input_token
.value
, ntlmProtocol
, sizeof ntlmProtocol
))) {
387 debug((char *) "%s| %s: WARNING: received type %d NTLM token\n",
389 (int) *((unsigned char *) input_token
.value
+
390 sizeof ntlmProtocol
));
391 fprintf(stdout
, "BH received type %d NTLM token\n",
392 (int) *((unsigned char *) input_token
.value
+
393 sizeof ntlmProtocol
));
396 if (service_principal
) {
397 if (strcasecmp(service_principal
, "GSS_C_NO_NAME")) {
398 major_status
= gss_import_name(&minor_status
, &service
,
399 (gss_OID
) GSS_C_NULL_OID
, &server_name
);
402 server_name
= GSS_C_NO_NAME
;
403 major_status
= GSS_S_COMPLETE
;
406 major_status
= gss_import_name(&minor_status
, &service
,
407 gss_nt_service_name
, &server_name
);
410 if (check_gss_err(major_status
, minor_status
, "gss_import_name()", log
))
414 gss_acquire_cred(&minor_status
, server_name
, GSS_C_INDEFINITE
,
415 GSS_C_NO_OID_SET
, GSS_C_ACCEPT
, &server_creds
, NULL
, NULL
);
416 if (check_gss_err(major_status
, minor_status
, "gss_acquire_cred()", log
))
419 major_status
= gss_accept_sec_context(&minor_status
,
423 GSS_C_NO_CHANNEL_BINDINGS
,
424 &client_name
, NULL
, &output_token
, &ret_flags
, NULL
, NULL
);
427 if (output_token
.length
) {
428 spnegoToken
= (const unsigned char *) output_token
.value
;
429 spnegoTokenLength
= output_token
.length
;
430 token
= (char *) xmalloc(ska_base64_encode_len(spnegoTokenLength
));
432 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
433 fprintf(stdout
, "BH Not enough memory\n");
436 ska_base64_encode(token
, (const char *) spnegoToken
,
437 ska_base64_encode_len(spnegoTokenLength
), spnegoTokenLength
);
439 if (check_gss_err(major_status
, minor_status
, "gss_accept_sec_context()", log
))
441 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
442 debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM
);
443 fprintf(stdout
, "TT %s\n", token
);
446 gss_release_buffer(&minor_status
, &output_token
);
448 gss_display_name(&minor_status
, client_name
, &output_token
,
451 if (check_gss_err(major_status
, minor_status
, "gss_display_name()", log
))
453 user
= (char *) xmalloc(output_token
.length
+ 1);
455 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
456 fprintf(stdout
, "BH Not enough memory\n");
459 memcpy(user
, output_token
.value
, output_token
.length
);
460 user
[output_token
.length
] = '\0';
461 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
464 fprintf(stdout
, "AF %s %s\n", token
, user
);
465 debug((char *) "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM
, token
, user
);
467 fprintf(stderr
, "%s| %s: INFO: User %s authenticated\n", LogTime(),
471 if (check_gss_err(major_status
, minor_status
, "gss_accept_sec_context()", log
))
473 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
474 debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM
);
475 fprintf(stdout
, "NA %s\n", token
);
478 gss_release_buffer(&minor_status
, &output_token
);
480 gss_display_name(&minor_status
, client_name
, &output_token
,
483 if (check_gss_err(major_status
, minor_status
, "gss_display_name()", log
))
486 * Return dummy token AA. May need an extra return tag then AF
488 user
= (char *) xmalloc(output_token
.length
+ 1);
490 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
491 fprintf(stdout
, "BH Not enough memory\n");
494 memcpy(user
, output_token
.value
, output_token
.length
);
495 user
[output_token
.length
] = '\0';
496 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
499 fprintf(stdout
, "AF %s %s\n", "AA==", user
);
500 debug((char *) "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM
, "AA==", user
);
502 fprintf(stderr
, "%s| %s: INFO: User %s authenticated\n", LogTime(),
507 gss_release_buffer(&minor_status
, &input_token
);
508 gss_release_buffer(&minor_status
, &output_token
);
509 gss_release_cred(&minor_status
, &server_creds
);
511 gss_release_name(&minor_status
, &server_name
);
513 gss_release_name(&minor_status
, &client_name
);
515 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
517 xfree((char *) kerberosToken
);
518 kerberosToken
= NULL
;
521 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
523 xfree((char *) spnegoToken
);
540 #ifndef MAX_AUTHTOKEN_LEN
541 #define MAX_AUTHTOKEN_LEN 65535
544 main(int argc
, char *const argv
[])
546 setbuf(stdout
, NULL
);
548 char buf
[MAX_AUTHTOKEN_LEN
];
550 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
551 fprintf(stdout
, "BH input error\n");
554 fprintf(stdout
, "BH Kerberos authentication not supported\n");
557 #endif /* HAVE_GSSAPI */