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