]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/negotiate_auth/squid_kerb_auth/squid_kerb_auth.c
Import new helper code.
[thirdparty/squid.git] / helpers / negotiate_auth / squid_kerb_auth / squid_kerb_auth.c
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 <string.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <netdb.h>
36 #include <unistd.h>
37 #include <time.h>
38 #include <sys/time.h>
39
40 #include "config.h"
41
42 #ifdef HAVE_SQUID
43 #ifdef PACKAGE
44 #undef PACKAGE
45 #endif
46 #ifdef PACKAGE_BUGREPORT
47 #undef PACKAGE_BUGREPORT
48 #endif
49 #ifdef PACKAGE_NAME
50 #undef PACKAGE_NAME
51 #endif
52 #ifdef PACKAGE_STRING
53 #undef PACKAGE_STRING
54 #endif
55 #ifdef PACKAGE_TARNAME
56 #undef PACKAGE_TARNAME
57 #endif
58 #ifdef PACKAGE_VERSION
59 #undef PACKAGE_VERSION
60 #endif
61 #ifdef VERSION
62 #undef VERSION
63 #endif
64 #ifdef HAVE_GETADDRINFO_H
65 #include "getaddrinfo.h"
66 #endif
67 #ifdef HAVE_GETNAMEINFO_H
68 #include "getnameinfo.h"
69 #endif
70 #ifdef HAVE_UTIL_H
71 #include "util.h"
72 #endif
73 #ifdef PACKAGE
74 #undef PACKAGE
75 #endif
76 #ifdef PACKAGE_BUGREPORT
77 #undef PACKAGE_BUGREPORT
78 #endif
79 #ifdef PACKAGE_NAME
80 #undef PACKAGE_NAME
81 #endif
82 #ifdef PACKAGE_STRING
83 #undef PACKAGE_STRING
84 #endif
85 #ifdef PACKAGE_TARNAME
86 #undef PACKAGE_TARNAME
87 #endif
88 #ifdef PACKAGE_VERSION
89 #undef PACKAGE_VERSION
90 #endif
91 #ifdef VERSION
92 #undef VERSION
93 #endif
94 /*
95 * Reset varibles
96 */
97 #include "config.h"
98 #endif
99 #if !defined(HAVE_DECL_XGETADDRINFO) || !HAVE_DECL_XGETADDRINFO
100 #define xgetaddrinfo getaddrinfo
101 #endif
102 #if !defined(HAVE_DECL_XFREEADDRINFO) || !HAVE_DECL_XFREEADDRINFO
103 #define xfreeaddrinfo freeaddrinfo
104 #endif
105 #if !defined(HAVE_DECL_XGAI_STRERROR) || !HAVE_DECL_XGAI_STRERROR
106 #define xgai_strerror gai_strerror
107 #endif
108 #if !defined(HAVE_DECL_XGETNAMEINFO) || !HAVE_DECL_XGETNAMEINFO
109 #define xgetnameinfo getnameinfo
110 #endif
111 #if !defined(HAVE_DECL_XMALLOC) || !HAVE_DECL_XMALLOC
112 #define xmalloc malloc
113 #endif
114 #if !defined(HAVE_DECL_XSTRDUP) || !HAVE_DECL_XSTRDUP
115 #define xstrdup strdup
116 #endif
117 #if !defined(HAVE_DECL_XFREE) || !HAVE_DECL_XFREE
118 #define xfree free
119 #endif
120
121 #include "base64.h"
122 #ifndef HAVE_SPNEGO
123 #include "spnegohelp.h"
124 #endif
125
126 #define PROGRAM "squid_kerb_auth"
127
128
129 #ifndef MAX_AUTHTOKEN_LEN
130 #define MAX_AUTHTOKEN_LEN 65535
131 #endif
132
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);
136
137 static const unsigned char ntlmProtocol [] = {'N', 'T', 'L', 'M', 'S', 'S', 'P', 0};
138
139 static const char *LogTime()
140 {
141 struct tm *tm;
142 struct timeval now;
143 static time_t last_t = 0;
144 static char buf[128];
145
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);
150 last_t = now.tv_sec;
151 }
152 return buf;
153 }
154
155 char *gethost_name(void) {
156 char hostname[sysconf(_SC_HOST_NAME_MAX)];
157 struct addrinfo *hres=NULL, *hres_list;
158 int rc,count;
159
160 rc = gethostname(hostname,sysconf(_SC_HOST_NAME_MAX));
161 if (rc)
162 {
163 fprintf(stderr, "%s| %s: error while resolving hostname '%s'\n", LogTime(), PROGRAM, hostname);
164 return NULL;
165 }
166 rc = xgetaddrinfo(hostname,NULL,NULL,&hres);
167 if (rc != 0) {
168 fprintf(stderr, "%s| %s: error while resolving hostname with getaddrinfo: %s\n", LogTime(), PROGRAM, xgai_strerror(rc));
169 return NULL;
170 }
171 hres_list=hres;
172 count=0;
173 while (hres_list) {
174 count++;
175 hres_list=hres_list->ai_next;
176 }
177 rc = xgetnameinfo (hres->ai_addr, hres->ai_addrlen,hostname, sizeof (hostname), NULL, 0, 0);
178 if (rc != 0) {
179 fprintf(stderr, "%s| %s: error while resolving ip address with getnameinfo: %s\n", LogTime(), PROGRAM, xgai_strerror(rc));
180 xfreeaddrinfo(hres);
181 return NULL ;
182 }
183
184 xfreeaddrinfo(hres);
185 hostname[sysconf(_SC_HOST_NAME_MAX)-1]='\0';
186 return(xstrdup(hostname));
187 }
188
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;
194 char buf[1024];
195 size_t len;
196
197 len = 0;
198 msg_ctx = 0;
199 while (!msg_ctx) {
200 /* convert major status code (GSS-API error) to text */
201 maj_stat = gss_display_status(&min_stat, major_status,
202 GSS_C_GSS_CODE,
203 GSS_C_NULL_OID,
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;
209 }
210 gss_release_buffer(&min_stat, &status_string);
211 break;
212 }
213 gss_release_buffer(&min_stat, &status_string);
214 }
215 if (sizeof(buf) > len + 2) {
216 sprintf(buf+len, "%s", ". ");
217 len += 2;
218 }
219 msg_ctx = 0;
220 while (!msg_ctx) {
221 /* convert minor status code (underlying routine error) to text */
222 maj_stat = gss_display_status(&min_stat, minor_status,
223 GSS_C_MECH_CODE,
224 GSS_C_NULL_OID,
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;
230 }
231 gss_release_buffer(&min_stat, &status_string);
232 break;
233 }
234 gss_release_buffer(&min_stat, &status_string);
235 }
236 if (debug)
237 fprintf(stderr, "%s| %s: %s failed: %s\n", LogTime(), PROGRAM, function, buf);
238 fprintf(stdout, "BH %s failed: %s\n",function, buf);
239 if (log)
240 fprintf(stderr, "%s| %s: User not authenticated\n", LogTime(), PROGRAM);
241 return(1);
242 }
243 return(0);
244 }
245
246
247
248 int main(int argc, char * const argv[])
249 {
250 char buf[MAX_AUTHTOKEN_LEN];
251 char *c;
252 int length=0;
253 static int err=0;
254 int opt, debug=0, log=0;
255 #ifndef HAVE_SPNEGO
256 int rc;
257 #endif
258 OM_uint32 ret_flags=0, spnego_flag=0;
259 char *service_name=(char *)"HTTP",*host_name=NULL;
260 char *token = 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;
271 #ifndef HAVE_SPNEGO
272 size_t kerberosTokenLength = 0;
273 #endif
274 const unsigned char *spnegoToken = NULL ;
275 size_t spnegoTokenLength = 0;
276
277 setbuf(stdout,NULL);
278 setbuf(stdin,NULL);
279
280 while (-1 != (opt = getopt(argc, argv, "dis:h"))) {
281 switch (opt) {
282 case 'd':
283 debug = 1;
284 break;
285 case 'i':
286 log = 1;
287 break;
288 case 's':
289 service_principal = xstrdup(optarg);
290 break;
291 case 'h':
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");
300 exit(0);
301 default:
302 fprintf(stderr, "%s| %s: unknown option: -%c.\n", LogTime(), PROGRAM, opt);
303 }
304 }
305
306 if (debug)
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);
311 } else {
312 host_name=gethost_name();
313 if ( !host_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");
316 exit(-1);
317 }
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);
321 }
322
323 while (1) {
324 if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
325 if (ferror(stdin)) {
326 if (debug)
327 fprintf(stderr, "%s| %s: fgets() failed! dying..... errno=%d (%s)\n", LogTime(), PROGRAM, ferror(stdin),
328 strerror(ferror(stdin)));
329
330 fprintf(stdout, "BH input error\n");
331 exit(1); /* BIIG buffer */
332 }
333 fprintf(stdout, "BH input error\n");
334 exit(0);
335 }
336
337 c=memchr(buf,'\n',sizeof(buf)-1);
338 if (c) {
339 *c = '\0';
340 length = c-buf;
341 } else {
342 err = 1;
343 }
344 if (err) {
345 if (debug)
346 fprintf(stderr, "%s| %s: Oversized message\n", LogTime(), PROGRAM);
347 fprintf(stdout, "BH Oversized message\n");
348 err = 0;
349 continue;
350 }
351
352 if (debug)
353 fprintf(stderr, "%s| %s: Got '%s' from squid (length: %d).\n", LogTime(), PROGRAM, buf,length);
354
355 if (buf[0] == '\0') {
356 if (debug)
357 fprintf(stderr, "%s| %s: Invalid request\n", LogTime(), PROGRAM);
358 fprintf(stdout, "BH Invalid request\n");
359 continue;
360 }
361
362 if (strlen(buf) < 2) {
363 if (debug)
364 fprintf(stderr, "%s| %s: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
365 fprintf(stdout, "BH Invalid request\n");
366 continue;
367 }
368
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);
374 if (server_name)
375 gss_release_name(&minor_status, &server_name);
376 if (client_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);
380 if (kerberosToken) {
381 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
382 if (!spnego_flag)
383 xfree((char *)kerberosToken);
384 kerberosToken=NULL;
385 }
386 if (spnego_flag) {
387 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
388 if (spnegoToken)
389 xfree((char *)spnegoToken);
390 spnegoToken=NULL;
391 }
392 if (token) {
393 xfree(token);
394 token=NULL;
395 }
396 if (host_name) {
397 xfree(host_name);
398 host_name=NULL;
399 }
400 fprintf(stdout, "BH quit command\n");
401 exit(0);
402 }
403
404 if ( strncmp(buf, "YR", 2) && strncmp(buf, "KK", 2) ) {
405 if (debug)
406 fprintf(stderr, "%s| %s: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
407 fprintf(stdout, "BH Invalid request\n");
408 continue;
409 }
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;
414 }
415
416 if (strlen(buf) <= 3) {
417 if (debug)
418 fprintf(stderr, "%s| %s: Invalid negotiate request [%s]\n", LogTime(), PROGRAM, buf);
419 fprintf(stdout, "BH Invalid negotiate request\n");
420 continue;
421 }
422
423 input_token.length = ska_base64_decode_len(buf+3);
424 if (debug)
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);
427
428 ska_base64_decode(input_token.value,buf+3,input_token.length);
429
430
431 #ifndef HAVE_SPNEGO
432 if (( rc=parseNegTokenInit (input_token.value,
433 input_token.length,
434 &kerberosToken,
435 &kerberosTokenLength))!=0 ){
436 if (debug)
437 fprintf(stderr, "%s| %s: parseNegTokenInit failed with rc=%d\n", LogTime(), PROGRAM, rc);
438
439 /* if between 100 and 200 it might be a GSSAPI token and not a SPNEGO token */
440 if ( rc < 100 || rc > 199 ) {
441 if (debug)
442 fprintf(stderr, "%s| %s: Invalid GSS-SPNEGO query [%s]\n", LogTime(), PROGRAM, buf);
443 fprintf(stdout, "BH Invalid GSS-SPNEGO query\n");
444 goto cleanup;
445 }
446 if ((input_token.length >= sizeof ntlmProtocol + 1) &&
447 (!memcmp (input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
448 if (debug)
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));
451 goto cleanup;
452 }
453 if (debug)
454 fprintf(stderr, "%s| %s: Token is possibly a GSSAPI token\n", LogTime(), PROGRAM);
455 spnego_flag=0;
456 } else {
457 gss_release_buffer(&minor_status, &input_token);
458 input_token.length=kerberosTokenLength;
459 input_token.value=(void *)kerberosToken;
460 spnego_flag=1;
461 }
462 #else
463 if ((input_token.length >= sizeof ntlmProtocol + 1) &&
464 (!memcmp (input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
465 if (debug)
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));
468 goto cleanup;
469 }
470 #endif
471
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);
476
477 } else {
478 server_name = GSS_C_NO_NAME;
479 major_status = GSS_S_COMPLETE;
480 }
481 } else {
482 major_status = gss_import_name(&minor_status, &service,
483 gss_nt_service_name, &server_name);
484 }
485
486 if ( check_gss_err(major_status,minor_status,"gss_import_name()",debug,log) )
487 goto cleanup;
488
489 major_status = gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
490 GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_creds,
491 NULL, NULL);
492 if (check_gss_err(major_status,minor_status,"gss_acquire_cred()",debug,log) )
493 goto cleanup;
494
495 major_status = gss_accept_sec_context(&minor_status,
496 &gss_context,
497 server_creds,
498 &input_token,
499 GSS_C_NO_CHANNEL_BINDINGS,
500 &client_name,
501 NULL,
502 &output_token,
503 &ret_flags,
504 NULL,
505 NULL);
506
507
508 if (output_token.length) {
509 #ifndef HAVE_SPNEGO
510 if (spnego_flag) {
511 if ((rc=makeNegTokenTarg (output_token.value,
512 output_token.length,
513 &spnegoToken,
514 &spnegoTokenLength))!=0 ) {
515 if (debug)
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);
518 goto cleanup;
519 }
520 } else {
521 spnegoToken = output_token.value;
522 spnegoTokenLength = output_token.length;
523 }
524 #else
525 spnegoToken = output_token.value;
526 spnegoTokenLength = output_token.length;
527 #endif
528 token = xmalloc(ska_base64_encode_len(spnegoTokenLength));
529 if (token == NULL) {
530 if (debug)
531 fprintf(stderr, "%s| %s: Not enough memory\n", LogTime(), PROGRAM);
532 fprintf(stdout, "BH Not enough memory\n");
533 goto cleanup;
534 }
535
536 ska_base64_encode(token,(const char *)spnegoToken,ska_base64_encode_len(spnegoTokenLength),spnegoTokenLength);
537
538 if (check_gss_err(major_status,minor_status,"gss_accept_sec_context()",debug,log) )
539 goto cleanup;
540 if (major_status & GSS_S_CONTINUE_NEEDED) {
541 if (debug)
542 fprintf(stderr, "%s| %s: continuation needed\n", LogTime(), PROGRAM);
543 fprintf(stdout, "TT %s\n",token);
544 goto cleanup;
545 }
546 gss_release_buffer(&minor_status, &output_token);
547 major_status = gss_display_name(&minor_status, client_name, &output_token,
548 NULL);
549
550 if (check_gss_err(major_status,minor_status,"gss_display_name()",debug,log) )
551 goto cleanup;
552 fprintf(stdout, "AF %s %s\n",token,(char *)output_token.value);
553 if (debug)
554 fprintf(stderr, "%s| %s: AF %s %s\n", LogTime(), PROGRAM, token,(char *)output_token.value);
555 if (log)
556 fprintf(stderr, "%s| %s: User %s authenticated\n", LogTime(), PROGRAM, (char *)output_token.value);
557 goto cleanup;
558 } else {
559 if (check_gss_err(major_status,minor_status,"gss_accept_sec_context()",debug,log) )
560 goto cleanup;
561 if (major_status & GSS_S_CONTINUE_NEEDED) {
562 if (debug)
563 fprintf(stderr, "%s| %s: continuation needed\n", LogTime(), PROGRAM);
564 fprintf(stdout, "NA %s\n",token);
565 goto cleanup;
566 }
567 gss_release_buffer(&minor_status, &output_token);
568 major_status = gss_display_name(&minor_status, client_name, &output_token,
569 NULL);
570
571 if (check_gss_err(major_status,minor_status,"gss_display_name()",debug,log) )
572 goto cleanup;
573 /*
574 * Return dummy token AA. May need an extra return tag then AF
575 */
576 fprintf(stdout, "AF %s %s\n","AA==",(char *)output_token.value);
577 if (debug)
578 fprintf(stderr, "%s| %s: AF %s %s\n", LogTime(), PROGRAM, "AA==", (char *)output_token.value);
579 if (log)
580 fprintf(stderr, "%s| %s: User %s authenticated\n", LogTime(), PROGRAM, (char *)output_token.value);
581
582 cleanup:
583 gss_release_buffer(&minor_status, &input_token);
584 gss_release_buffer(&minor_status, &output_token);
585 gss_release_cred(&minor_status, &server_creds);
586 if (server_name)
587 gss_release_name(&minor_status, &server_name);
588 if (client_name)
589 gss_release_name(&minor_status, &client_name);
590 if (kerberosToken) {
591 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
592 if (!spnego_flag)
593 xfree((char *)kerberosToken);
594 kerberosToken=NULL;
595 }
596 if (spnego_flag) {
597 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
598 if (spnegoToken)
599 xfree((char *)spnegoToken);
600 spnegoToken=NULL;
601 }
602 if (token) {
603 xfree(token);
604 token=NULL;
605 }
606 continue;
607 }
608 }
609 }