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