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
46 #ifdef PACKAGE_BUGREPORT
47 #undef PACKAGE_BUGREPORT
55 #ifdef PACKAGE_TARNAME
56 #undef PACKAGE_TARNAME
58 #ifdef PACKAGE_VERSION
59 #undef PACKAGE_VERSION
64 #ifdef HAVE_GETADDRINFO_H
65 #include "getaddrinfo.h"
67 #ifdef HAVE_GETNAMEINFO_H
68 #include "getnameinfo.h"
76 #ifdef PACKAGE_BUGREPORT
77 #undef PACKAGE_BUGREPORT
85 #ifdef PACKAGE_TARNAME
86 #undef PACKAGE_TARNAME
88 #ifdef PACKAGE_VERSION
89 #undef PACKAGE_VERSION
99 #if !defined(HAVE_DECL_XGETADDRINFO) || !HAVE_DECL_XGETADDRINFO
100 #define xgetaddrinfo getaddrinfo
102 #if !defined(HAVE_DECL_XFREEADDRINFO) || !HAVE_DECL_XFREEADDRINFO
103 #define xfreeaddrinfo freeaddrinfo
105 #if !defined(HAVE_DECL_XGAI_STRERROR) || !HAVE_DECL_XGAI_STRERROR
106 #define xgai_strerror gai_strerror
108 #if !defined(HAVE_DECL_XGETNAMEINFO) || !HAVE_DECL_XGETNAMEINFO
109 #define xgetnameinfo getnameinfo
111 #if !defined(HAVE_DECL_XMALLOC) || !HAVE_DECL_XMALLOC
112 #define xmalloc malloc
114 #if !defined(HAVE_DECL_XSTRDUP) || !HAVE_DECL_XSTRDUP
115 #define xstrdup strdup
117 #if !defined(HAVE_DECL_XFREE) || !HAVE_DECL_XFREE
123 #include "spnegohelp.h"
126 #define PROGRAM "squid_kerb_auth"
129 #ifndef MAX_AUTHTOKEN_LEN
130 #define MAX_AUTHTOKEN_LEN 65535
133 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
, const char* function
, int debug
, int log
);
134 char *gethost_name(void);
135 static const char *LogTime(void);
137 static const unsigned char ntlmProtocol
[] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0};
139 static const char *LogTime()
143 static time_t last_t
= 0;
144 static char buf
[128];
146 gettimeofday(&now
, NULL
);
147 if (now
.tv_sec
!= last_t
) {
148 tm
= localtime((time_t *)&now
.tv_sec
);
149 strftime(buf
, 127, "%Y/%m/%d %H:%M:%S", tm
);
155 char *gethost_name(void) {
156 char hostname
[sysconf(_SC_HOST_NAME_MAX
)];
157 struct addrinfo
*hres
=NULL
, *hres_list
;
160 rc
= gethostname(hostname
,sysconf(_SC_HOST_NAME_MAX
));
163 fprintf(stderr
, "%s| %s: error while resolving hostname '%s'\n", LogTime(), PROGRAM
, hostname
);
166 rc
= xgetaddrinfo(hostname
,NULL
,NULL
,&hres
);
168 fprintf(stderr
, "%s| %s: error while resolving hostname with getaddrinfo: %s\n", LogTime(), PROGRAM
, xgai_strerror(rc
));
175 hres_list
=hres_list
->ai_next
;
177 rc
= xgetnameinfo (hres
->ai_addr
, hres
->ai_addrlen
,hostname
, sizeof (hostname
), NULL
, 0, 0);
179 fprintf(stderr
, "%s| %s: error while resolving ip address with getnameinfo: %s\n", LogTime(), PROGRAM
, xgai_strerror(rc
));
185 hostname
[sysconf(_SC_HOST_NAME_MAX
)-1]='\0';
186 return(xstrdup(hostname
));
189 int check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
, const char* function
, int debug
, int log
) {
190 if (GSS_ERROR(major_status
)) {
191 OM_uint32 maj_stat
,min_stat
;
192 OM_uint32 msg_ctx
= 0;
193 gss_buffer_desc status_string
;
200 /* convert major status code (GSS-API error) to text */
201 maj_stat
= gss_display_status(&min_stat
, major_status
,
204 &msg_ctx
, &status_string
);
205 if (maj_stat
== GSS_S_COMPLETE
) {
206 if (sizeof(buf
) > len
+ status_string
.length
+ 1) {
207 sprintf(buf
+len
, "%s", (char*) status_string
.value
);
208 len
+= status_string
.length
;
210 gss_release_buffer(&min_stat
, &status_string
);
213 gss_release_buffer(&min_stat
, &status_string
);
215 if (sizeof(buf
) > len
+ 2) {
216 sprintf(buf
+len
, "%s", ". ");
221 /* convert minor status code (underlying routine error) to text */
222 maj_stat
= gss_display_status(&min_stat
, minor_status
,
225 &msg_ctx
, &status_string
);
226 if (maj_stat
== GSS_S_COMPLETE
) {
227 if (sizeof(buf
) > len
+ status_string
.length
) {
228 sprintf(buf
+len
, "%s", (char*) status_string
.value
);
229 len
+= status_string
.length
;
231 gss_release_buffer(&min_stat
, &status_string
);
234 gss_release_buffer(&min_stat
, &status_string
);
237 fprintf(stderr
, "%s| %s: %s failed: %s\n", LogTime(), PROGRAM
, function
, buf
);
238 fprintf(stdout
, "BH %s failed: %s\n",function
, buf
);
240 fprintf(stderr
, "%s| %s: User not authenticated\n", LogTime(), PROGRAM
);
248 int main(int argc
, char * const argv
[])
250 char buf
[MAX_AUTHTOKEN_LEN
];
254 int opt
, debug
=0, log
=0;
258 OM_uint32 ret_flags
=0, spnego_flag
=0;
259 char *service_name
=(char *)"HTTP",*host_name
=NULL
;
261 char *service_principal
= NULL
;
262 OM_uint32 major_status
, minor_status
;
263 gss_ctx_id_t gss_context
= GSS_C_NO_CONTEXT
;
264 gss_name_t client_name
= GSS_C_NO_NAME
;
265 gss_name_t server_name
= GSS_C_NO_NAME
;
266 gss_cred_id_t server_creds
= GSS_C_NO_CREDENTIAL
;
267 gss_buffer_desc service
= GSS_C_EMPTY_BUFFER
;
268 gss_buffer_desc input_token
= GSS_C_EMPTY_BUFFER
;
269 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
270 const unsigned char *kerberosToken
= NULL
;
272 size_t kerberosTokenLength
= 0;
274 const unsigned char *spnegoToken
= NULL
;
275 size_t spnegoTokenLength
= 0;
280 while (-1 != (opt
= getopt(argc
, argv
, "dis:h"))) {
289 service_principal
= xstrdup(optarg
);
292 fprintf(stderr
, "Usage: \n");
293 fprintf(stderr
, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
294 fprintf(stderr
, "-d full debug\n");
295 fprintf(stderr
, "-i informational messages\n");
296 fprintf(stderr
, "-s service principal name\n");
297 fprintf(stderr
, "-h help\n");
298 fprintf(stderr
, "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
299 fprintf(stderr
, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
302 fprintf(stderr
, "%s| %s: unknown option: -%c.\n", LogTime(), PROGRAM
, opt
);
307 fprintf(stderr
, "%s| %s: Starting version %s\n", LogTime(), PROGRAM
, VERSION
);
308 if (service_principal
&& strcasecmp(service_principal
,"GSS_C_NO_NAME") ) {
309 service
.value
= service_principal
;
310 service
.length
= strlen((char *)service
.value
);
312 host_name
=gethost_name();
314 fprintf(stderr
, "%s| %s: Local hostname could not be determined. Please specify the service principal\n", LogTime(), PROGRAM
);
315 fprintf(stdout
, "BH hostname error\n");
318 service
.value
= xmalloc(strlen(service_name
)+strlen(host_name
)+2);
319 snprintf(service
.value
,strlen(service_name
)+strlen(host_name
)+2,"%s@%s",service_name
,host_name
);
320 service
.length
= strlen((char *)service
.value
);
324 if (fgets(buf
, sizeof(buf
)-1, stdin
) == NULL
) {
327 fprintf(stderr
, "%s| %s: fgets() failed! dying..... errno=%d (%s)\n", LogTime(), PROGRAM
, ferror(stdin
),
328 strerror(ferror(stdin
)));
330 fprintf(stdout
, "BH input error\n");
331 exit(1); /* BIIG buffer */
333 fprintf(stdout
, "BH input error\n");
337 c
=memchr(buf
,'\n',sizeof(buf
)-1);
346 fprintf(stderr
, "%s| %s: Oversized message\n", LogTime(), PROGRAM
);
347 fprintf(stdout
, "BH Oversized message\n");
353 fprintf(stderr
, "%s| %s: Got '%s' from squid (length: %d).\n", LogTime(), PROGRAM
, buf
,length
);
355 if (buf
[0] == '\0') {
357 fprintf(stderr
, "%s| %s: Invalid request\n", LogTime(), PROGRAM
);
358 fprintf(stdout
, "BH Invalid request\n");
362 if (strlen(buf
) < 2) {
364 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(), PROGRAM
, buf
);
365 fprintf(stdout
, "BH Invalid request\n");
369 if ( !strncmp(buf
, "QQ", 2) ) {
370 gss_release_buffer(&minor_status
, &input_token
);
371 gss_release_buffer(&minor_status
, &output_token
);
372 gss_release_buffer(&minor_status
, &service
);
373 gss_release_cred(&minor_status
, &server_creds
);
375 gss_release_name(&minor_status
, &server_name
);
377 gss_release_name(&minor_status
, &client_name
);
378 if (gss_context
!= GSS_C_NO_CONTEXT
)
379 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
381 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
383 xfree((char *)kerberosToken
);
387 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
389 xfree((char *)spnegoToken
);
400 fprintf(stdout
, "BH quit command\n");
404 if ( strncmp(buf
, "YR", 2) && strncmp(buf
, "KK", 2) ) {
406 fprintf(stderr
, "%s| %s: Invalid request [%s]\n", LogTime(), PROGRAM
, buf
);
407 fprintf(stdout
, "BH Invalid request\n");
410 if ( !strncmp(buf
, "YR", 2) ){
411 if (gss_context
!= GSS_C_NO_CONTEXT
)
412 gss_delete_sec_context(&minor_status
, &gss_context
, NULL
);
413 gss_context
= GSS_C_NO_CONTEXT
;
416 if (strlen(buf
) <= 3) {
418 fprintf(stderr
, "%s| %s: Invalid negotiate request [%s]\n", LogTime(), PROGRAM
, buf
);
419 fprintf(stdout
, "BH Invalid negotiate request\n");
423 input_token
.length
= ska_base64_decode_len(buf
+3);
425 fprintf(stderr
, "%s| %s: Decode '%s' (decoded length: %d).\n", LogTime(), PROGRAM
, buf
+3,(int)input_token
.length
);
426 input_token
.value
= xmalloc(input_token
.length
);
428 ska_base64_decode(input_token
.value
,buf
+3,input_token
.length
);
432 if (( rc
=parseNegTokenInit (input_token
.value
,
435 &kerberosTokenLength
))!=0 ){
437 fprintf(stderr
, "%s| %s: parseNegTokenInit failed with rc=%d\n", LogTime(), PROGRAM
, rc
);
439 /* if between 100 and 200 it might be a GSSAPI token and not a SPNEGO token */
440 if ( rc
< 100 || rc
> 199 ) {
442 fprintf(stderr
, "%s| %s: Invalid GSS-SPNEGO query [%s]\n", LogTime(), PROGRAM
, buf
);
443 fprintf(stdout
, "BH Invalid GSS-SPNEGO query\n");
446 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
447 (!memcmp (input_token
.value
, ntlmProtocol
, sizeof ntlmProtocol
))) {
449 fprintf(stderr
, "%s| %s: received type %d NTLM token\n", LogTime(), PROGRAM
, (int) *((unsigned char *)input_token
.value
+ sizeof ntlmProtocol
));
450 fprintf(stdout
, "BH received type %d NTLM token\n",(int) *((unsigned char *)input_token
.value
+ sizeof ntlmProtocol
));
454 fprintf(stderr
, "%s| %s: Token is possibly a GSSAPI token\n", LogTime(), PROGRAM
);
457 gss_release_buffer(&minor_status
, &input_token
);
458 input_token
.length
=kerberosTokenLength
;
459 input_token
.value
=(void *)kerberosToken
;
463 if ((input_token
.length
>= sizeof ntlmProtocol
+ 1) &&
464 (!memcmp (input_token
.value
, ntlmProtocol
, sizeof ntlmProtocol
))) {
466 fprintf(stderr
, "%s| %s: received type %d NTLM token\n", LogTime(), PROGRAM
, (int) *((unsigned char *)input_token
.value
+ sizeof ntlmProtocol
));
467 fprintf(stdout
, "BH received type %d NTLM token\n",(int) *((unsigned char *)input_token
.value
+ sizeof ntlmProtocol
));
472 if ( service_principal
) {
473 if ( strcasecmp(service_principal
,"GSS_C_NO_NAME") ){
474 major_status
= gss_import_name(&minor_status
, &service
,
475 (gss_OID
) GSS_C_NULL_OID
, &server_name
);
478 server_name
= GSS_C_NO_NAME
;
479 major_status
= GSS_S_COMPLETE
;
482 major_status
= gss_import_name(&minor_status
, &service
,
483 gss_nt_service_name
, &server_name
);
486 if ( check_gss_err(major_status
,minor_status
,"gss_import_name()",debug
,log
) )
489 major_status
= gss_acquire_cred(&minor_status
, server_name
, GSS_C_INDEFINITE
,
490 GSS_C_NO_OID_SET
, GSS_C_ACCEPT
, &server_creds
,
492 if (check_gss_err(major_status
,minor_status
,"gss_acquire_cred()",debug
,log
) )
495 major_status
= gss_accept_sec_context(&minor_status
,
499 GSS_C_NO_CHANNEL_BINDINGS
,
508 if (output_token
.length
) {
511 if ((rc
=makeNegTokenTarg (output_token
.value
,
514 &spnegoTokenLength
))!=0 ) {
516 fprintf(stderr
, "%s| %s: makeNegTokenTarg failed with rc=%d\n", LogTime(), PROGRAM
, rc
);
517 fprintf(stdout
, "BH makeNegTokenTarg failed with rc=%d\n",rc
);
521 spnegoToken
= output_token
.value
;
522 spnegoTokenLength
= output_token
.length
;
525 spnegoToken
= output_token
.value
;
526 spnegoTokenLength
= output_token
.length
;
528 token
= xmalloc(ska_base64_encode_len(spnegoTokenLength
));
531 fprintf(stderr
, "%s| %s: Not enough memory\n", LogTime(), PROGRAM
);
532 fprintf(stdout
, "BH Not enough memory\n");
536 ska_base64_encode(token
,(const char *)spnegoToken
,ska_base64_encode_len(spnegoTokenLength
),spnegoTokenLength
);
538 if (check_gss_err(major_status
,minor_status
,"gss_accept_sec_context()",debug
,log
) )
540 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
542 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(), PROGRAM
);
543 fprintf(stdout
, "TT %s\n",token
);
546 gss_release_buffer(&minor_status
, &output_token
);
547 major_status
= gss_display_name(&minor_status
, client_name
, &output_token
,
550 if (check_gss_err(major_status
,minor_status
,"gss_display_name()",debug
,log
) )
552 fprintf(stdout
, "AF %s %s\n",token
,(char *)output_token
.value
);
554 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
, token
,(char *)output_token
.value
);
556 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(), PROGRAM
, (char *)output_token
.value
);
559 if (check_gss_err(major_status
,minor_status
,"gss_accept_sec_context()",debug
,log
) )
561 if (major_status
& GSS_S_CONTINUE_NEEDED
) {
563 fprintf(stderr
, "%s| %s: continuation needed\n", LogTime(), PROGRAM
);
564 fprintf(stdout
, "NA %s\n",token
);
567 gss_release_buffer(&minor_status
, &output_token
);
568 major_status
= gss_display_name(&minor_status
, client_name
, &output_token
,
571 if (check_gss_err(major_status
,minor_status
,"gss_display_name()",debug
,log
) )
574 * Return dummy token AA. May need an extra return tag then AF
576 fprintf(stdout
, "AF %s %s\n","AA==",(char *)output_token
.value
);
578 fprintf(stderr
, "%s| %s: AF %s %s\n", LogTime(), PROGRAM
, "AA==", (char *)output_token
.value
);
580 fprintf(stderr
, "%s| %s: User %s authenticated\n", LogTime(), PROGRAM
, (char *)output_token
.value
);
583 gss_release_buffer(&minor_status
, &input_token
);
584 gss_release_buffer(&minor_status
, &output_token
);
585 gss_release_cred(&minor_status
, &server_creds
);
587 gss_release_name(&minor_status
, &server_name
);
589 gss_release_name(&minor_status
, &client_name
);
591 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
593 xfree((char *)kerberosToken
);
597 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
599 xfree((char *)spnegoToken
);