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