2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
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.
10 * -----------------------------------------------------------------------------
12 * Author: Markus Moeller (markus_moeller at compuserve.com)
14 * Copyright (C) 2007 Markus Moeller. All rights reserved.
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.
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.
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.
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.
35 * -----------------------------------------------------------------------------
39 #include "compat/getaddrinfo.h"
40 #include "compat/getnameinfo.h"
45 #include "negotiate_kerberos.h"
51 * char hostname[sysconf(_SC_HOST_NAME_MAX)];
54 struct addrinfo
*hres
= NULL
, *hres_list
;
57 rc
= gethostname(hostname
, sizeof(hostname
)-1);
59 fprintf(stderr
, "%s| %s: ERROR: resolving hostname '%s' failed\n",
60 LogTime(), PROGRAM
, hostname
);
63 rc
= getaddrinfo(hostname
, NULL
, NULL
, &hres
);
66 "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
67 LogTime(), PROGRAM
, gai_strerror(rc
));
74 hres_list
= hres_list
->ai_next
;
76 rc
= getnameinfo(hres
->ai_addr
, hres
->ai_addrlen
, hostname
,
77 sizeof(hostname
), NULL
, 0, 0);
80 "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
81 LogTime(), PROGRAM
, gai_strerror(rc
));
86 hostname
[sizeof(hostname
)-1] = '\0';
87 return (xstrdup(hostname
));
91 check_gss_err(OM_uint32 major_status
, OM_uint32 minor_status
,
92 const char *function
, int log
, int sout
)
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
;
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
;
114 gss_release_buffer(&min_stat
, &status_string
);
116 if (sizeof(buf
) > len
+ 2) {
117 snprintf(buf
+ len
, (sizeof(buf
) - len
), "%s", ". ");
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
;
132 gss_release_buffer(&min_stat
, &status_string
);
134 debug((char *) "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM
, function
, buf
);
136 fprintf(stdout
, "BH %s failed: %s\n", function
, buf
);
138 fprintf(stderr
, "%s| %s: INFO: User not authenticated\n", LogTime(),
146 main(int argc
, char *const argv
[])
148 char buf
[MAX_AUTHTOKEN_LEN
];
151 char *rfc_user
= NULL
;
153 char ad_groups
[MAX_PAC_GROUP_SIZE
];
155 krb5_context context
= NULL
;
159 gss_buffer_desc data_set
= GSS_C_EMPTY_BUFFER
;
161 gss_buffer_desc type_id
= GSS_C_EMPTY_BUFFER
;
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
;
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;
183 setbuf(stdout
, NULL
);
186 while (-1 != (opt
= getopt(argc
, argv
, "dirs:h"))) {
198 service_principal
= xstrdup(optarg
);
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");
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");
213 fprintf(stderr
, "%s| %s: WARNING: unknown option: -%c.\n", LogTime(),
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
);
223 host_name
= gethost_name();
226 "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
228 fprintf(stdout
, "BH hostname error\n");
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
);
239 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
241 debug((char *) "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
242 LogTime(), PROGRAM
, ferror(stdin
),
243 strerror(ferror(stdin
)));
245 fprintf(stdout
, "BH input error\n");
246 exit(1); /* BIIG buffer */
248 fprintf(stdout
, "BH input error\n");
251 c
= (char *) memchr(buf
, '\n', sizeof(buf
) - 1);
259 debug((char *) "%s| %s: ERROR: Oversized message\n", LogTime(), PROGRAM
);
260 fprintf(stdout
, "BH Oversized message\n");
264 debug((char *) "%s| %s: DEBUG: Got '%s' from squid (length: %ld).\n", LogTime(), PROGRAM
, buf
, length
);
266 if (buf
[0] == '\0') {
267 debug((char *) "%s| %s: ERROR: Invalid request\n", LogTime(), PROGRAM
);
268 fprintf(stdout
, "BH Invalid request\n");
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");
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
);
282 gss_release_name(&minor_status
, &server_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
);
288 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
290 xfree(kerberosToken
);
293 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
297 fprintf(stdout
, "BH quit command\n");
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");
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
;
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");
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
);
320 input_token
.length
= (size_t)base64_decode((char *) input_token
.value
, (unsigned int)input_token
.length
, buf
+3);
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",
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
));
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
);
339 server_name
= GSS_C_NO_NAME
;
340 major_status
= GSS_S_COMPLETE
;
344 major_status
= gss_import_name(&minor_status
, &service
,
345 gss_nt_service_name
, &server_name
);
348 if (check_gss_err(major_status
, minor_status
, "gss_import_name()", log
, 1))
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))
357 major_status
= gss_accept_sec_context(&minor_status
,
361 GSS_C_NO_CHANNEL_BINDINGS
,
362 &client_name
, NULL
, &output_token
, &ret_flags
, NULL
, NULL
);
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
));
369 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
370 fprintf(stdout
, "BH Not enough memory\n");
373 base64_encode_str(token
, base64_encode_len((int)spnegoTokenLength
),
374 (const char *) spnegoToken
, (int)spnegoTokenLength
);
376 if (check_gss_err(major_status
, minor_status
, "gss_accept_sec_context()", log
, 1))
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
);
383 gss_release_buffer(&minor_status
, &output_token
);
385 gss_display_name(&minor_status
, client_name
, &output_token
,
388 if (check_gss_err(major_status
, minor_status
, "gss_display_name()", log
, 1))
390 user
= (char *) xmalloc(output_token
.length
+ 1);
392 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
393 fprintf(stdout
, "BH Not enough memory\n");
396 memcpy(user
, output_token
.value
, output_token
.length
);
397 user
[output_token
.length
] = '\0';
398 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
403 ret
= krb5_init_context(&context
);
404 if (!check_k5_err(context
, "krb5_init_context", ret
)) {
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
);
417 krb5_free_context(context
);
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
);
427 (void)gss_release_any_name_mapping(&minor_status
, client_name
, &type_id
, (gss_any_t
*)&pac
);
428 krb5_free_context(context
);
432 debug((char *) "%s| %s: DEBUG: Groups %s\n", LogTime(), PROGRAM
, ag
);
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
);
439 fprintf(stderr
, "%s| %s: INFO: User %s authenticated\n", LogTime(),
440 PROGRAM
, rfc1738_escape(user
));
443 if (check_gss_err(major_status
, minor_status
, "gss_accept_sec_context()", log
, 1))
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
);
450 gss_release_buffer(&minor_status
, &output_token
);
452 gss_display_name(&minor_status
, client_name
, &output_token
,
455 if (check_gss_err(major_status
, minor_status
, "gss_display_name()", log
, 1))
458 * Return dummy token AA. May need an extra return tag then AF
460 user
= (char *) xmalloc(output_token
.length
+ 1);
462 debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM
);
463 fprintf(stdout
, "BH Not enough memory\n");
466 memcpy(user
, output_token
.value
, output_token
.length
);
467 user
[output_token
.length
] = '\0';
468 if (norealm
&& (p
= strchr(user
, '@')) != NULL
) {
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
));
474 fprintf(stderr
, "%s| %s: INFO: User %s authenticated\n", LogTime(),
475 PROGRAM
, rfc1738_escape(user
));
479 gss_release_buffer(&minor_status
, &input_token
);
480 gss_release_buffer(&minor_status
, &output_token
);
481 gss_release_cred(&minor_status
, &server_creds
);
483 gss_release_name(&minor_status
, &server_name
);
485 gss_release_name(&minor_status
, &client_name
);
487 /* Allocated by parseNegTokenInit, but no matching free function exists.. */
489 safe_free(kerberosToken
);
492 /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
493 safe_free(spnegoToken
);
502 #ifndef MAX_AUTHTOKEN_LEN
503 #define MAX_AUTHTOKEN_LEN 65535
506 main(int argc
, char *const argv
[])
508 setbuf(stdout
, NULL
);
510 char buf
[MAX_AUTHTOKEN_LEN
];
512 if (fgets(buf
, sizeof(buf
) - 1, stdin
) == NULL
) {
513 fprintf(stdout
, "BH input error\n");
516 fprintf(stdout
, "BH Kerberos authentication not supported\n");
519 #endif /* HAVE_GSSAPI */