]> git.ipfire.org Git - thirdparty/squid.git/blob - helpers/negotiate_auth/kerberos/negotiate_kerberos_auth.cc
Boilerplate: update copyright blurbs on Squid helpers
[thirdparty/squid.git] / helpers / negotiate_auth / kerberos / negotiate_kerberos_auth.cc
1 /*
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 /*
10 * -----------------------------------------------------------------------------
11 *
12 * Author: Markus Moeller (markus_moeller at compuserve.com)
13 *
14 * Copyright (C) 2007 Markus Moeller. All rights reserved.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
29 *
30 * As a special exemption, M Moeller gives permission to link this program
31 * with MIT, Heimdal or other GSS/Kerberos libraries, and distribute
32 * the resulting executable, without including the source code for
33 * the Libraries in the source distribution.
34 *
35 * -----------------------------------------------------------------------------
36 */
37
38 #include "squid.h"
39 #include "compat/getaddrinfo.h"
40 #include "compat/getnameinfo.h"
41 #include "rfc1738.h"
42
43 #if HAVE_GSSAPI
44
45 #include "negotiate_kerberos.h"
46
47 char *
48 gethost_name(void)
49 {
50 /*
51 * char hostname[sysconf(_SC_HOST_NAME_MAX)];
52 */
53 char hostname[1024];
54 struct addrinfo *hres = NULL, *hres_list;
55 int rc, count;
56
57 rc = gethostname(hostname, sizeof(hostname)-1);
58 if (rc) {
59 fprintf(stderr, "%s| %s: ERROR: resolving hostname '%s' failed\n",
60 LogTime(), PROGRAM, hostname);
61 return NULL;
62 }
63 rc = getaddrinfo(hostname, NULL, NULL, &hres);
64 if (rc != 0) {
65 fprintf(stderr,
66 "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
67 LogTime(), PROGRAM, gai_strerror(rc));
68 return NULL;
69 }
70 hres_list = hres;
71 count = 0;
72 while (hres_list) {
73 ++count;
74 hres_list = hres_list->ai_next;
75 }
76 rc = getnameinfo(hres->ai_addr, hres->ai_addrlen, hostname,
77 sizeof(hostname), NULL, 0, 0);
78 if (rc != 0) {
79 fprintf(stderr,
80 "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
81 LogTime(), PROGRAM, gai_strerror(rc));
82 freeaddrinfo(hres);
83 return NULL;
84 }
85 freeaddrinfo(hres);
86 hostname[sizeof(hostname)-1] = '\0';
87 return (xstrdup(hostname));
88 }
89
90 int
91 check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
92 const char *function, int log, int sout)
93 {
94 if (GSS_ERROR(major_status)) {
95 OM_uint32 maj_stat, min_stat;
96 OM_uint32 msg_ctx = 0;
97 gss_buffer_desc status_string;
98 char buf[1024];
99 size_t len;
100
101 len = 0;
102 msg_ctx = 0;
103 do {
104 /* convert major status code (GSS-API error) to text */
105 maj_stat = gss_display_status(&min_stat, major_status,
106 GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
107 if (maj_stat == GSS_S_COMPLETE && status_string.length > 0) {
108 if (sizeof(buf) > len + status_string.length + 1) {
109 snprintf(buf + len, (sizeof(buf) - len), "%s", (char *) status_string.value);
110 len += status_string.length;
111 }
112 } else
113 msg_ctx = 0;
114 gss_release_buffer(&min_stat, &status_string);
115 } while (msg_ctx);
116 if (sizeof(buf) > len + 2) {
117 snprintf(buf + len, (sizeof(buf) - len), "%s", ". ");
118 len += 2;
119 }
120 msg_ctx = 0;
121 do {
122 /* convert minor status code (underlying routine error) to text */
123 maj_stat = gss_display_status(&min_stat, minor_status,
124 GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
125 if (maj_stat == GSS_S_COMPLETE && status_string.length > 0) {
126 if (sizeof(buf) > len + status_string.length) {
127 snprintf(buf + len, (sizeof(buf) - len), "%s", (char *) status_string.value);
128 len += status_string.length;
129 }
130 } else
131 msg_ctx = 0;
132 gss_release_buffer(&min_stat, &status_string);
133 } while (msg_ctx);
134 debug((char *) "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM, function, buf);
135 if (sout)
136 fprintf(stdout, "BH %s failed: %s\n", function, buf);
137 if (log)
138 fprintf(stderr, "%s| %s: INFO: User not authenticated\n", LogTime(),
139 PROGRAM);
140 return (1);
141 }
142 return (0);
143 }
144
145 int
146 main(int argc, char *const argv[])
147 {
148 char buf[MAX_AUTHTOKEN_LEN];
149 char *c, *p;
150 char *user = NULL;
151 char *rfc_user = NULL;
152 #if HAVE_PAC_SUPPORT
153 char ad_groups[MAX_PAC_GROUP_SIZE];
154 char *ag=NULL;
155 krb5_context context = NULL;
156 krb5_error_code ret;
157 krb5_pac pac;
158 #if USE_HEIMDAL_KRB5
159 gss_buffer_desc data_set = GSS_C_EMPTY_BUFFER;
160 #else
161 gss_buffer_desc type_id = GSS_C_EMPTY_BUFFER;
162 #endif
163 #endif
164 long length = 0;
165 static int err = 0;
166 int opt, log = 0, norealm = 0;
167 OM_uint32 ret_flags = 0, spnego_flag = 0;
168 char *service_name = (char *) "HTTP", *host_name = NULL;
169 char *token = NULL;
170 char *service_principal = NULL;
171 OM_uint32 major_status, minor_status;
172 gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
173 gss_name_t client_name = GSS_C_NO_NAME;
174 gss_name_t server_name = GSS_C_NO_NAME;
175 gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
176 gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
177 gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
178 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
179 const unsigned char *kerberosToken = NULL;
180 const unsigned char *spnegoToken = NULL;
181 size_t spnegoTokenLength = 0;
182
183 setbuf(stdout, NULL);
184 setbuf(stdin, NULL);
185
186 while (-1 != (opt = getopt(argc, argv, "dirs:h"))) {
187 switch (opt) {
188 case 'd':
189 debug_enabled = 1;
190 break;
191 case 'i':
192 log = 1;
193 break;
194 case 'r':
195 norealm = 1;
196 break;
197 case 's':
198 service_principal = xstrdup(optarg);
199 break;
200 case 'h':
201 fprintf(stderr, "Usage: \n");
202 fprintf(stderr, "squid_kerb_auth [-d] [-i] [-s SPN] [-h]\n");
203 fprintf(stderr, "-d full debug\n");
204 fprintf(stderr, "-i informational messages\n");
205 fprintf(stderr, "-r remove realm from username\n");
206 fprintf(stderr, "-s service principal name\n");
207 fprintf(stderr, "-h help\n");
208 fprintf(stderr,
209 "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
210 fprintf(stderr, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
211 exit(0);
212 default:
213 fprintf(stderr, "%s| %s: WARNING: unknown option: -%c.\n", LogTime(),
214 PROGRAM, opt);
215 }
216 }
217
218 debug((char *) "%s| %s: INFO: Starting version %s\n", LogTime(), PROGRAM, SQUID_KERB_AUTH_VERSION);
219 if (service_principal && strcasecmp(service_principal, "GSS_C_NO_NAME")) {
220 service.value = service_principal;
221 service.length = strlen((char *) service.value);
222 } else {
223 host_name = gethost_name();
224 if (!host_name) {
225 fprintf(stderr,
226 "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
227 LogTime(), PROGRAM);
228 fprintf(stdout, "BH hostname error\n");
229 exit(-1);
230 }
231 service.value = xmalloc(strlen(service_name) + strlen(host_name) + 2);
232 snprintf((char *) service.value, strlen(service_name) + strlen(host_name) + 2,
233 "%s@%s", service_name, host_name);
234 service.length = strlen((char *) service.value);
235 xfree(host_name);
236 }
237
238 while (1) {
239 if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
240 if (ferror(stdin)) {
241 debug((char *) "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
242 LogTime(), PROGRAM, ferror(stdin),
243 strerror(ferror(stdin)));
244
245 fprintf(stdout, "BH input error\n");
246 exit(1); /* BIIG buffer */
247 }
248 fprintf(stdout, "BH input error\n");
249 exit(0);
250 }
251 c = (char *) 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 debug((char *) "%s| %s: ERROR: Oversized message\n", LogTime(), PROGRAM);
260 fprintf(stdout, "BH Oversized message\n");
261 err = 0;
262 continue;
263 }
264 debug((char *) "%s| %s: DEBUG: Got '%s' from squid (length: %ld).\n", LogTime(), PROGRAM, buf, length);
265
266 if (buf[0] == '\0') {
267 debug((char *) "%s| %s: ERROR: Invalid request\n", LogTime(), PROGRAM);
268 fprintf(stdout, "BH Invalid request\n");
269 continue;
270 }
271 if (strlen(buf) < 2) {
272 debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
273 fprintf(stdout, "BH Invalid request\n");
274 continue;
275 }
276 if (!strncmp(buf, "QQ", 2)) {
277 gss_release_buffer(&minor_status, &input_token);
278 gss_release_buffer(&minor_status, &output_token);
279 gss_release_buffer(&minor_status, &service);
280 gss_release_cred(&minor_status, &server_creds);
281 if (server_name)
282 gss_release_name(&minor_status, &server_name);
283 if (client_name)
284 gss_release_name(&minor_status, &client_name);
285 if (gss_context != GSS_C_NO_CONTEXT)
286 gss_delete_sec_context(&minor_status, &gss_context, NULL);
287 if (kerberosToken) {
288 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
289 if (!spnego_flag)
290 xfree(kerberosToken);
291 }
292 if (spnego_flag) {
293 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
294 xfree(spnegoToken);
295 }
296 xfree(token);
297 fprintf(stdout, "BH quit command\n");
298 exit(0);
299 }
300 if (strncmp(buf, "YR", 2) && strncmp(buf, "KK", 2)) {
301 debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
302 fprintf(stdout, "BH Invalid request\n");
303 continue;
304 }
305 if (!strncmp(buf, "YR", 2)) {
306 if (gss_context != GSS_C_NO_CONTEXT)
307 gss_delete_sec_context(&minor_status, &gss_context, NULL);
308 gss_context = GSS_C_NO_CONTEXT;
309 }
310 if (strlen(buf) <= 3) {
311 debug((char *) "%s| %s: ERROR: Invalid negotiate request [%s]\n", LogTime(), PROGRAM, buf);
312 fprintf(stdout, "BH Invalid negotiate request\n");
313 continue;
314 }
315 input_token.length = (size_t)base64_decode_len(buf+3);
316 debug((char *) "%s| %s: DEBUG: Decode '%s' (decoded length: %d).\n",
317 LogTime(), PROGRAM, buf + 3, (int) input_token.length);
318 input_token.value = xmalloc(input_token.length);
319
320 input_token.length = (size_t)base64_decode((char *) input_token.value, (unsigned int)input_token.length, buf+3);
321
322 if ((input_token.length >= sizeof ntlmProtocol + 1) &&
323 (!memcmp(input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
324 debug((char *) "%s| %s: WARNING: received type %d NTLM token\n",
325 LogTime(), PROGRAM,
326 (int) *((unsigned char *) input_token.value +
327 sizeof ntlmProtocol));
328 fprintf(stdout, "BH received type %d NTLM token\n",
329 (int) *((unsigned char *) input_token.value +
330 sizeof ntlmProtocol));
331 goto cleanup;
332 }
333 if (service_principal) {
334 if (strcasecmp(service_principal, "GSS_C_NO_NAME")) {
335 major_status = gss_import_name(&minor_status, &service,
336 (gss_OID) GSS_C_NULL_OID, &server_name);
337
338 } else {
339 server_name = GSS_C_NO_NAME;
340 major_status = GSS_S_COMPLETE;
341 minor_status = 0;
342 }
343 } else {
344 major_status = gss_import_name(&minor_status, &service,
345 gss_nt_service_name, &server_name);
346 }
347
348 if (check_gss_err(major_status, minor_status, "gss_import_name()", log, 1))
349 goto cleanup;
350
351 major_status =
352 gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
353 GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_creds, NULL, NULL);
354 if (check_gss_err(major_status, minor_status, "gss_acquire_cred()", log, 1))
355 goto cleanup;
356
357 major_status = gss_accept_sec_context(&minor_status,
358 &gss_context,
359 server_creds,
360 &input_token,
361 GSS_C_NO_CHANNEL_BINDINGS,
362 &client_name, NULL, &output_token, &ret_flags, NULL, NULL);
363
364 if (output_token.length) {
365 spnegoToken = (const unsigned char *) output_token.value;
366 spnegoTokenLength = output_token.length;
367 token = (char *) xmalloc((size_t)base64_encode_len((int)spnegoTokenLength));
368 if (token == NULL) {
369 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
370 fprintf(stdout, "BH Not enough memory\n");
371 goto cleanup;
372 }
373 base64_encode_str(token, base64_encode_len((int)spnegoTokenLength),
374 (const char *) spnegoToken, (int)spnegoTokenLength);
375
376 if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
377 goto cleanup;
378 if (major_status & GSS_S_CONTINUE_NEEDED) {
379 debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
380 fprintf(stdout, "TT %s\n", token);
381 goto cleanup;
382 }
383 gss_release_buffer(&minor_status, &output_token);
384 major_status =
385 gss_display_name(&minor_status, client_name, &output_token,
386 NULL);
387
388 if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
389 goto cleanup;
390 user = (char *) xmalloc(output_token.length + 1);
391 if (user == NULL) {
392 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
393 fprintf(stdout, "BH Not enough memory\n");
394 goto cleanup;
395 }
396 memcpy(user, output_token.value, output_token.length);
397 user[output_token.length] = '\0';
398 if (norealm && (p = strchr(user, '@')) != NULL) {
399 *p = '\0';
400 }
401
402 #if HAVE_PAC_SUPPORT
403 ret = krb5_init_context(&context);
404 if (!check_k5_err(context, "krb5_init_context", ret)) {
405 #if USE_HEIMDAL_KRB5
406 #define ADWIN2KPAC 128
407 major_status = gsskrb5_extract_authz_data_from_sec_context(&minor_status,
408 gss_context, ADWIN2KPAC, &data_set);
409 if (!check_gss_err(major_status, minor_status,
410 "gsskrb5_extract_authz_data_from_sec_context()", log, 0)) {
411 ret = krb5_pac_parse(context, data_set.value, data_set.length, &pac);
412 gss_release_buffer(&minor_status, &data_set);
413 if (!check_k5_err(context, "krb5_pac_parse", ret)) {
414 ag = get_ad_groups((char *)&ad_groups, context, pac);
415 krb5_pac_free(context, pac);
416 }
417 krb5_free_context(context);
418 }
419 #else
420 type_id.value = (void *)"mspac";
421 type_id.length = strlen((char *)type_id.value);
422 #define KRB5PACLOGONINFO 1
423 major_status = gss_map_name_to_any(&minor_status, client_name, KRB5PACLOGONINFO, &type_id, (gss_any_t *)&pac);
424 if (!check_gss_err(major_status, minor_status, "gss_map_name_to_any()", log, 0)) {
425 ag = get_ad_groups((char *)&ad_groups,context, pac);
426 }
427 (void)gss_release_any_name_mapping(&minor_status, client_name, &type_id, (gss_any_t *)&pac);
428 krb5_free_context(context);
429 #endif
430 }
431 if (ag) {
432 debug((char *) "%s| %s: DEBUG: Groups %s\n", LogTime(), PROGRAM, ag);
433 }
434 #endif
435 fprintf(stdout, "AF %s %s\n", token, user);
436 rfc_user = rfc1738_escape(user);
437 debug((char *) "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM, token, rfc_user);
438 if (log)
439 fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
440 PROGRAM, rfc1738_escape(user));
441 goto cleanup;
442 } else {
443 if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
444 goto cleanup;
445 if (major_status & GSS_S_CONTINUE_NEEDED) {
446 debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
447 fprintf(stdout, "NA %s\n", token);
448 goto cleanup;
449 }
450 gss_release_buffer(&minor_status, &output_token);
451 major_status =
452 gss_display_name(&minor_status, client_name, &output_token,
453 NULL);
454
455 if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
456 goto cleanup;
457 /*
458 * Return dummy token AA. May need an extra return tag then AF
459 */
460 user = (char *) xmalloc(output_token.length + 1);
461 if (user == NULL) {
462 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
463 fprintf(stdout, "BH Not enough memory\n");
464 goto cleanup;
465 }
466 memcpy(user, output_token.value, output_token.length);
467 user[output_token.length] = '\0';
468 if (norealm && (p = strchr(user, '@')) != NULL) {
469 *p = '\0';
470 }
471 fprintf(stdout, "AF %s %s\n", "AA==", user);
472 debug((char *) "%s| %s: DEBUG: AF %s %s\n", LogTime(), PROGRAM, "AA==", rfc1738_escape(user));
473 if (log)
474 fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
475 PROGRAM, rfc1738_escape(user));
476
477 }
478 cleanup:
479 gss_release_buffer(&minor_status, &input_token);
480 gss_release_buffer(&minor_status, &output_token);
481 gss_release_cred(&minor_status, &server_creds);
482 if (server_name)
483 gss_release_name(&minor_status, &server_name);
484 if (client_name)
485 gss_release_name(&minor_status, &client_name);
486 if (kerberosToken) {
487 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
488 if (!spnego_flag)
489 safe_free(kerberosToken);
490 }
491 if (spnego_flag) {
492 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
493 safe_free(spnegoToken);
494 }
495 safe_free(token);
496 safe_free(user);
497 continue;
498 }
499 }
500 #else
501 #include <cstdlib>
502 #ifndef MAX_AUTHTOKEN_LEN
503 #define MAX_AUTHTOKEN_LEN 65535
504 #endif
505 int
506 main(int argc, char *const argv[])
507 {
508 setbuf(stdout, NULL);
509 setbuf(stdin, NULL);
510 char buf[MAX_AUTHTOKEN_LEN];
511 while (1) {
512 if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
513 fprintf(stdout, "BH input error\n");
514 exit(0);
515 }
516 fprintf(stdout, "BH Kerberos authentication not supported\n");
517 }
518 }
519 #endif /* HAVE_GSSAPI */