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