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