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