negotiate_kerberos_auth.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 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 "rfc1738.h"
40 
41 #if HAVE_GSSAPI
42 
43 #include "compat/unistd.h"
44 #include "negotiate_kerberos.h"
45 
46 #if HAVE_SYS_STAT_H
47 #include "sys/stat.h"
48 #endif
49 
50 #if HAVE_KRB5_MEMORY_KEYTAB
51 typedef struct _krb5_kt_list {
52  struct _krb5_kt_list *next;
53  krb5_keytab_entry *entry;
54 } *krb5_kt_list;
55 krb5_kt_list ktlist = nullptr;
56 
57 krb5_keytab memory_keytab;
58 
59 krb5_error_code krb5_free_kt_list(krb5_context context, krb5_kt_list kt_list);
60 krb5_error_code krb5_write_keytab(krb5_context context,
61  krb5_kt_list kt_list,
62  char *name);
63 krb5_error_code krb5_read_keytab(krb5_context context,
64  char *name,
65  krb5_kt_list *kt_list);
66 #endif /* HAVE_KRB5_MEMORY_KEYTAB */
67 
68 int
69 check_k5_err(krb5_context context, const char *function, krb5_error_code code)
70 {
71 
72  if (code && code != KRB5_KT_END) {
73  const char *errmsg;
74  errmsg = krb5_get_error_message(context, code);
75  debug((char *) "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM, function, errmsg);
76  fprintf(stderr, "%s| %s: ERROR: %s: %s\n", LogTime(), PROGRAM, function, errmsg);
77 #if HAVE_KRB5_FREE_ERROR_MESSAGE
78  krb5_free_error_message(context, errmsg);
79 #elif HAVE_KRB5_FREE_ERROR_STRING
80  krb5_free_error_string(context, (char *)errmsg);
81 #else
82  xfree(errmsg);
83 #endif
84  }
85  return code;
86 }
87 
88 char *
89 gethost_name(void)
90 {
91  /*
92  * char hostname[sysconf(_SC_HOST_NAME_MAX)];
93  */
94  char hostname[1024];
95  struct addrinfo *hres = nullptr, *hres_list;
96  int rc;
97 
98  rc = xgethostname(hostname, sizeof(hostname)-1);
99  if (rc) {
100  debug((char *) "%s| %s: ERROR: resolving hostname '%s' failed\n", LogTime(), PROGRAM, hostname);
101  fprintf(stderr, "%s| %s: ERROR: resolving hostname '%s' failed\n",
102  LogTime(), PROGRAM, hostname);
103  return nullptr;
104  }
105  rc = getaddrinfo(hostname, nullptr, nullptr, &hres);
106  if (rc != 0 || hres == nullptr ) {
107  debug((char *) "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
108  LogTime(), PROGRAM, gai_strerror(rc));
109  fprintf(stderr,
110  "%s| %s: ERROR: resolving hostname with getaddrinfo: %s failed\n",
111  LogTime(), PROGRAM, gai_strerror(rc));
112  return nullptr;
113  }
114  hres_list = hres;
115  while (hres_list) {
116  hres_list = hres_list->ai_next;
117  }
118  rc = getnameinfo(hres->ai_addr, hres->ai_addrlen, hostname,
119  sizeof(hostname), nullptr, 0, 0);
120  if (rc != 0) {
121  debug((char *) "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
122  LogTime(), PROGRAM, gai_strerror(rc));
123  fprintf(stderr,
124  "%s| %s: ERROR: resolving ip address with getnameinfo: %s failed\n",
125  LogTime(), PROGRAM, gai_strerror(rc));
126  freeaddrinfo(hres);
127  return nullptr;
128  }
129  freeaddrinfo(hres);
130  hostname[sizeof(hostname)-1] = '\0';
131  return (xstrdup(hostname));
132 }
133 
134 int
135 check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
136  const char *function, int log, int sout)
137 {
138  if (GSS_ERROR(major_status)) {
139  OM_uint32 maj_stat, min_stat;
140  OM_uint32 msg_ctx = 0;
141  gss_buffer_desc status_string;
142  char buf[1024];
143  size_t len;
144 
145  len = 0;
146  msg_ctx = 0;
147  do {
148  /* convert major status code (GSS-API error) to text */
149  maj_stat = gss_display_status(&min_stat, major_status,
150  GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
151  if (maj_stat == GSS_S_COMPLETE && status_string.length > 0) {
152  if (sizeof(buf) > len + status_string.length + 1) {
153  snprintf(buf + len, (sizeof(buf) - len), "%s", (char *) status_string.value);
154  len += status_string.length;
155  }
156  } else
157  msg_ctx = 0;
158  gss_release_buffer(&min_stat, &status_string);
159  } while (msg_ctx);
160  if (sizeof(buf) > len + 2) {
161  snprintf(buf + len, (sizeof(buf) - len), "%s", ". ");
162  len += 2;
163  }
164  msg_ctx = 0;
165  do {
166  /* convert minor status code (underlying routine error) to text */
167  maj_stat = gss_display_status(&min_stat, minor_status,
168  GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
169  if (maj_stat == GSS_S_COMPLETE && status_string.length > 0) {
170  if (sizeof(buf) > len + status_string.length) {
171  snprintf(buf + len, (sizeof(buf) - len), "%s", (char *) status_string.value);
172  len += status_string.length;
173  }
174  } else
175  msg_ctx = 0;
176  gss_release_buffer(&min_stat, &status_string);
177  } while (msg_ctx);
178  debug((char *) "%s| %s: ERROR: %s failed: %s\n", LogTime(), PROGRAM, function, buf);
179  if (sout)
180  fprintf(stdout, "BH %s failed: %s\n", function, buf);
181  if (log)
182  fprintf(stderr, "%s| %s: INFO: User not authenticated\n", LogTime(),
183  PROGRAM);
184  return (1);
185  }
186  return (0);
187 }
188 
189 #if HAVE_KRB5_MEMORY_KEYTAB
190 /*
191  * Free a kt_list
192  */
193 krb5_error_code krb5_free_kt_list(krb5_context context, krb5_kt_list list)
194 {
195  krb5_kt_list lp = list;
196 
197  while (lp) {
198 #if HAVE_LIBHEIMDAL_KRB5 || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY )
199  krb5_error_code retval = krb5_kt_free_entry(context, lp->entry);
200 #else
201  krb5_error_code retval = krb5_free_keytab_entry_contents(context, lp->entry);
202 #endif
203  safe_free(lp->entry);
204  if (check_k5_err(context, "krb5_kt_free_entry", retval))
205  return retval;
206  krb5_kt_list prev = lp;
207  lp = lp->next;
208  xfree(prev);
209  }
210  return 0;
211 }
212 /*
213  * Read in a keytab and append it to list. If list starts as NULL,
214  * allocate a new one if necessary.
215  */
216 krb5_error_code krb5_read_keytab(krb5_context context, char *name, krb5_kt_list *list)
217 {
218  krb5_kt_list lp = nullptr, tail = nullptr, back = nullptr;
219  krb5_keytab kt;
220  krb5_keytab_entry *entry;
221  krb5_kt_cursor cursor;
222  krb5_error_code retval = 0;
223 
224  if (*list) {
225  /* point lp at the tail of the list */
226  for (lp = *list; lp->next; lp = lp->next);
227  back = lp;
228  }
229  retval = krb5_kt_resolve(context, name, &kt);
230  if (check_k5_err(context, "krb5_kt_resolve", retval))
231  return retval;
232  retval = krb5_kt_start_seq_get(context, kt, &cursor);
233  if (check_k5_err(context, "krb5_kt_start_seq_get", retval))
234  goto close_kt;
235  for (;;) {
236  entry = (krb5_keytab_entry *)xcalloc(1, sizeof (krb5_keytab_entry));
237  if (!entry) {
238  retval = ENOMEM;
239  debug((char *) "%s| %s: ERROR: krb5_read_keytab failed: %s\n",
240  LogTime(), PROGRAM, strerror(retval));
241  fprintf(stderr, "%s| %s: ERROR: krb5_read_keytab: %s\n",
242  LogTime(), PROGRAM, strerror(retval));
243  break;
244  }
245  memset(entry, 0, sizeof (*entry));
246  retval = krb5_kt_next_entry(context, kt, entry, &cursor);
247  if (check_k5_err(context, "krb5_kt_next_entry", retval))
248  break;
249 
250  if (!lp) { /* if list is empty, start one */
251  lp = (krb5_kt_list)xmalloc(sizeof (*lp));
252  if (!lp) {
253  retval = ENOMEM;
254  debug((char *) "%s| %s: ERROR: krb5_read_keytab failed: %s\n",
255  LogTime(), PROGRAM, strerror(retval));
256  fprintf(stderr, "%s| %s: ERROR: krb5_read_keytab: %s\n",
257  LogTime(), PROGRAM, strerror(retval));
258  break;
259  }
260  } else {
261  lp->next = (krb5_kt_list)xmalloc(sizeof (*lp));
262  if (!lp->next) {
263  retval = ENOMEM;
264  debug((char *) "%s| %s: ERROR: krb5_read_keytab failed: %s\n",
265  LogTime(), PROGRAM, strerror(retval));
266  fprintf(stderr, "%s| %s: ERROR: krb5_read_keytab: %s\n",
267  LogTime(), PROGRAM, strerror(retval));
268  break;
269  }
270  lp = lp->next;
271  }
272  if (!tail)
273  tail = lp;
274  lp->next = nullptr;
275  lp->entry = entry;
276  }
277  xfree(entry);
278  if (retval) {
279  if (retval == KRB5_KT_END)
280  retval = 0;
281  else {
282  krb5_free_kt_list(context, tail);
283  tail = nullptr;
284  if (back)
285  back->next = nullptr;
286  }
287  }
288  if (!*list)
289  *list = tail;
290  krb5_kt_end_seq_get(context, kt, &cursor);
291 close_kt:
292  krb5_kt_close(context, kt);
293  return retval;
294 }
295 
296 /*
297  * Takes a kt_list and writes it to the named keytab.
298  */
299 krb5_error_code krb5_write_keytab(krb5_context context, krb5_kt_list list, char *name)
300 {
301  char ktname[MAXPATHLEN+sizeof("MEMORY:")+1];
302  krb5_error_code retval = 0;
303 
304  snprintf(ktname, sizeof(ktname), "%s", name);
305  retval = krb5_kt_resolve(context, ktname, &memory_keytab);
306  if (retval)
307  return retval;
308  for (krb5_kt_list lp = list; lp; lp = lp->next) {
309  retval = krb5_kt_add_entry(context, memory_keytab, lp->entry);
310  if (retval)
311  break;
312  }
313  /*
314  * krb5_kt_close(context, kt);
315  */
316  return retval;
317 }
318 #endif /* HAVE_KRB5_MEMORY_KEYTAB */
319 
320 int
321 main(int argc, char *const argv[])
322 {
323  char buf[MAX_AUTHTOKEN_LEN];
324  char *c, *p;
325  char *user = nullptr;
326  char *rfc_user = nullptr;
327 #if HAVE_PAC_SUPPORT
328  char ad_groups[MAX_PAC_GROUP_SIZE];
329  char *ag=nullptr;
330  krb5_pac pac;
331 #if HAVE_LIBHEIMDAL_KRB5
332  gss_buffer_desc data_set = GSS_C_EMPTY_BUFFER;
333 #else
334  gss_buffer_desc type_id = GSS_C_EMPTY_BUFFER;
335 #endif
336 #endif
337  krb5_context context = nullptr;
338  krb5_error_code ret;
339  long length = 0;
340  static int err = 0;
341  int opt, log = 0, norealm = 0;
342  OM_uint32 ret_flags = 0, spnego_flag = 0;
343  char *service_name = (char *) "HTTP", *host_name = nullptr;
344  char *token = nullptr;
345  char *service_principal = nullptr;
346  char *keytab_name = nullptr;
347  char *keytab_name_env = nullptr;
348  char default_keytab[MAXPATHLEN] = {};
349 #if HAVE_KRB5_MEMORY_KEYTAB
350  char *memory_keytab_name = nullptr;
351 #endif
352  char *rcache_type = nullptr;
353  char *rcache_dir = nullptr;
354  OM_uint32 major_status, minor_status;
355  gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
356  gss_name_t client_name = GSS_C_NO_NAME;
357  gss_name_t server_name = GSS_C_NO_NAME;
358  gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
359  gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
360  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
361  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
362  const unsigned char *kerberosToken = nullptr;
363  const unsigned char *spnegoToken = nullptr;
364  size_t spnegoTokenLength = 0;
365 
366  setbuf(stdout, nullptr);
367  setbuf(stdin, nullptr);
368 
369  while (-1 != (opt = getopt(argc, argv, "dirs:k:c:t:"))) {
370  switch (opt) {
371  case 'd':
372  debug_enabled = 1;
373  break;
374  case 'i':
375  log = 1;
376  break;
377  case 'r':
378  norealm = 1;
379  break;
380  case 'k':
381 #if HAVE_SYS_STAT_H
382  struct stat fstat;
383  char *ktp;
384 #endif
385  if (optarg)
386  keytab_name = xstrdup(optarg);
387  else {
388  fprintf(stderr, "ERROR: keytab file not given\n");
389  exit(EXIT_FAILURE);
390  }
391  /*
392  * Some sanity checks
393  */
394 #if HAVE_SYS_STAT_H
395  if ((ktp=strchr(keytab_name,':')))
396  ktp++;
397  else
398  ktp=keytab_name;
399  if (stat((const char*)ktp, &fstat)) {
400  if (ENOENT == errno)
401  fprintf(stderr, "ERROR: keytab file %s does not exist\n",keytab_name);
402  else
403  fprintf(stderr, "ERROR: Error %s during stat of keytab file %s\n",strerror(errno),keytab_name);
404  exit(EXIT_FAILURE);
405  } else if (!S_ISREG(fstat.st_mode)) {
406  fprintf(stderr, "ERROR: keytab file %s is not a file\n",keytab_name);
407  exit(EXIT_FAILURE);
408  }
409 #endif
410 #if HAVE_UNISTD_H
411  if (access(ktp, R_OK)) {
412  fprintf(stderr, "ERROR: keytab file %s is not accessible\n",keytab_name);
413  exit(EXIT_FAILURE);
414  }
415 #endif
416  break;
417  case 'c':
418 #if HAVE_SYS_STAT_H
419  struct stat dstat;
420 #endif
421  if (optarg)
422  rcache_dir = xstrdup(optarg);
423  else {
424  fprintf(stderr, "ERROR: replay cache directory not given\n");
425  exit(EXIT_FAILURE);
426  }
427  /*
428  * Some sanity checks
429  */
430 #if HAVE_SYS_STAT_H
431  if (stat((const char*)rcache_dir, &dstat)) {
432  if (ENOENT == errno)
433  fprintf(stderr, "ERROR: replay cache directory %s does not exist\n",rcache_dir);
434  else
435  fprintf(stderr, "ERROR: Error %s during stat of replay cache directory %s\n",strerror(errno),rcache_dir);
436  exit(EXIT_FAILURE);
437  } else if (!S_ISDIR(dstat.st_mode)) {
438  fprintf(stderr, "ERROR: replay cache directory %s is not a directory\n",rcache_dir);
439  exit(EXIT_FAILURE);
440  }
441 #endif
442 #if HAVE_UNISTD_H
443  if (access(rcache_dir, W_OK)) {
444  fprintf(stderr, "ERROR: replay cache directory %s is not accessible\n",rcache_dir);
445  exit(EXIT_FAILURE);
446  }
447 #endif
448  break;
449  case 't':
450  if (optarg)
451  rcache_type = xstrdup(optarg);
452  else {
453  fprintf(stderr, "ERROR: replay cache type not given\n");
454  exit(EXIT_FAILURE);
455  }
456  break;
457  case 's':
458  if (optarg)
459  service_principal = xstrdup(optarg);
460  else {
461  fprintf(stderr, "ERROR: service principal not given\n");
462  exit(EXIT_FAILURE);
463  }
464  break;
465  default:
466  fprintf(stderr, "Usage: \n");
467  fprintf(stderr, "squid_kerb_auth [-d] [-i] [-s SPN] [-k keytab] [-c rcdir] [-t rctype]\n");
468  fprintf(stderr, "-d full debug\n");
469  fprintf(stderr, "-i informational messages\n");
470  fprintf(stderr, "-r remove realm from username\n");
471  fprintf(stderr, "-s service principal name\n");
472  fprintf(stderr, "-k keytab name\n");
473  fprintf(stderr, "-c replay cache directory\n");
474  fprintf(stderr, "-t replay cache type\n");
475  fprintf(stderr,
476  "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
477  fprintf(stderr, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
478  exit(EXIT_SUCCESS);
479  }
480  }
481 
482  debug((char *) "%s| %s: INFO: Starting version %s\n", LogTime(), PROGRAM, SQUID_KERB_AUTH_VERSION);
483  if (service_principal && strcasecmp(service_principal, "GSS_C_NO_NAME")) {
484  if (!strstr(service_principal,"HTTP/")) {
485  debug((char *) "%s| %s: WARN: service_principal %s does not start with HTTP/\n",
486  LogTime(), PROGRAM, service_principal);
487  }
488  service.value = service_principal;
489  service.length = strlen((char *) service.value);
490  } else {
491  host_name = gethost_name();
492  if (!host_name) {
493  fprintf(stderr,
494  "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
495  LogTime(), PROGRAM);
496  fprintf(stdout, "BH hostname error\n");
497  exit(EXIT_FAILURE);
498  }
499  service.value = xmalloc(strlen(service_name) + strlen(host_name) + 2);
500  snprintf((char *) service.value, strlen(service_name) + strlen(host_name) + 2,
501  "%s@%s", service_name, host_name);
502  service.length = strlen((char *) service.value);
503  xfree(host_name);
504  }
505 
506  if (rcache_type) {
507  (void)setenv("KRB5RCACHETYPE", rcache_type, 1);
508  debug((char *) "%s| %s: INFO: Setting replay cache type to %s\n",
509  LogTime(), PROGRAM, rcache_type);
510  }
511 
512  if (rcache_dir) {
513  (void)setenv("KRB5RCACHEDIR", rcache_dir, 1);
514  debug((char *) "%s| %s: INFO: Setting replay cache directory to %s\n",
515  LogTime(), PROGRAM, rcache_dir);
516  }
517 
518  if (keytab_name) {
519  (void)setenv("KRB5_KTNAME", keytab_name, 1);
520  } else {
521  keytab_name_env = getenv("KRB5_KTNAME");
522  if (!keytab_name_env) {
523  ret = krb5_init_context(&context);
524  if (!check_k5_err(context, "krb5_init_context", ret)) {
525  krb5_kt_default_name(context, default_keytab, MAXPATHLEN);
526  }
527  keytab_name = xstrdup(default_keytab);
528  krb5_free_context(context);
529  } else
530  keytab_name = xstrdup(keytab_name_env);
531  }
532  debug((char *) "%s| %s: INFO: Setting keytab to %s\n", LogTime(), PROGRAM, keytab_name);
533 #if HAVE_KRB5_MEMORY_KEYTAB
534  ret = krb5_init_context(&context);
535  if (!check_k5_err(context, "krb5_init_context", ret)) {
536  memory_keytab_name = (char *)xmalloc(strlen("MEMORY:negotiate_kerberos_auth_")+16);
537  snprintf(memory_keytab_name, strlen("MEMORY:negotiate_kerberos_auth_")+16,
538  "MEMORY:negotiate_kerberos_auth_%d", (unsigned int) getpid());
539  ret = krb5_read_keytab(context, keytab_name, &ktlist);
540  if (check_k5_err(context, "krb5_read_keytab", ret)) {
541  debug((char *) "%s| %s: ERROR: Reading keytab %s into list failed\n",
542  LogTime(), PROGRAM, keytab_name);
543  } else {
544  ret = krb5_write_keytab(context, ktlist, memory_keytab_name);
545  if (check_k5_err(context, "krb5_write_keytab", ret)) {
546  debug((char *) "%s| %s: ERROR: Writing list into keytab %s\n",
547  LogTime(), PROGRAM, memory_keytab_name);
548  } else {
549  (void)setenv("KRB5_KTNAME", memory_keytab_name, 1);
550  xfree(keytab_name);
551  keytab_name = xstrdup(memory_keytab_name);
552  debug((char *) "%s| %s: INFO: Changed keytab to %s\n",
553  LogTime(), PROGRAM, memory_keytab_name);
554  }
555  }
556  ret = krb5_free_kt_list(context,ktlist);
557  if (check_k5_err(context, "krb5_free_kt_list", ret)) {
558  debug((char *) "%s| %s: ERROR: Freeing list failed\n",
559  LogTime(), PROGRAM);
560  }
561  }
562  krb5_free_context(context);
563 #endif
564 #ifdef HAVE_HEIMDAL_KERBEROS
565  gsskrb5_register_acceptor_identity(keytab_name);
566 #endif
567  while (1) {
568  if (fgets(buf, sizeof(buf) - 1, stdin) == nullptr) {
569  if (ferror(stdin)) {
570  debug((char *) "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
571  LogTime(), PROGRAM, ferror(stdin),
572  strerror(ferror(stdin)));
573 
574  fprintf(stdout, "BH input error\n");
575  exit(EXIT_FAILURE); /* BIIG buffer */
576  }
577  fprintf(stdout, "BH input error\n");
578  exit(EXIT_SUCCESS);
579  }
580  c = (char *) memchr(buf, '\n', sizeof(buf) - 1);
581  if (c) {
582  *c = '\0';
583  length = c - buf;
584  } else {
585  err = 1;
586  }
587  if (err) {
588  debug((char *) "%s| %s: ERROR: Oversized message\n", LogTime(), PROGRAM);
589  fprintf(stdout, "BH Oversized message\n");
590  err = 0;
591  continue;
592  }
593  debug((char *) "%s| %s: DEBUG: Got '%s' from squid (length: %ld).\n", LogTime(), PROGRAM, buf, length);
594 
595  if (buf[0] == '\0') {
596  debug((char *) "%s| %s: ERROR: Invalid request\n", LogTime(), PROGRAM);
597  fprintf(stdout, "BH Invalid request\n");
598  continue;
599  }
600  if (strlen(buf) < 2) {
601  debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
602  fprintf(stdout, "BH Invalid request\n");
603  continue;
604  }
605  if (!strncmp(buf, "QQ", 2)) {
606  gss_release_buffer(&minor_status, &input_token);
607  gss_release_buffer(&minor_status, &output_token);
608  gss_release_buffer(&minor_status, &service);
609  gss_release_cred(&minor_status, &server_creds);
610  if (server_name)
611  gss_release_name(&minor_status, &server_name);
612  if (client_name)
613  gss_release_name(&minor_status, &client_name);
614  if (gss_context != GSS_C_NO_CONTEXT)
615  gss_delete_sec_context(&minor_status, &gss_context, nullptr);
616  if (kerberosToken) {
617  /* Allocated by parseNegTokenInit, but no matching free function exists.. */
618  if (!spnego_flag)
619  xfree(kerberosToken);
620  }
621  if (spnego_flag) {
622  /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
623  xfree(spnegoToken);
624  }
625  xfree(token);
626  xfree(rcache_type);
627  xfree(rcache_dir);
628  xfree(keytab_name);
629 #if HAVE_KRB5_MEMORY_KEYTAB
630  krb5_kt_close(context, memory_keytab);
631  xfree(memory_keytab_name);
632 #endif
633  xfree(rfc_user);
634  fprintf(stdout, "BH quit command\n");
635  exit(EXIT_SUCCESS);
636  }
637  if (strncmp(buf, "YR", 2) && strncmp(buf, "KK", 2)) {
638  debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
639  fprintf(stdout, "BH Invalid request\n");
640  continue;
641  }
642  if (!strncmp(buf, "YR", 2)) {
643  if (gss_context != GSS_C_NO_CONTEXT)
644  gss_delete_sec_context(&minor_status, &gss_context, nullptr);
645  gss_context = GSS_C_NO_CONTEXT;
646  }
647  if (strlen(buf) <= 3) {
648  debug((char *) "%s| %s: ERROR: Invalid negotiate request [%s]\n", LogTime(), PROGRAM, buf);
649  fprintf(stdout, "BH Invalid negotiate request\n");
650  continue;
651  }
652  const char *b64Token = buf+3;
653  const size_t srcLen = strlen(buf+3);
654  input_token.length = BASE64_DECODE_LENGTH(srcLen);
655  debug((char *) "%s| %s: DEBUG: Decode '%s' (decoded length estimate: %d).\n",
656  LogTime(), PROGRAM, b64Token, (int) input_token.length);
657  input_token.value = xmalloc(input_token.length);
658 
659  struct base64_decode_ctx ctx;
660  base64_decode_init(&ctx);
661  size_t dstLen = 0;
662  if (!base64_decode_update(&ctx, &dstLen, static_cast<uint8_t*>(input_token.value), srcLen, b64Token) ||
663  !base64_decode_final(&ctx)) {
664  debug((char *) "%s| %s: ERROR: Invalid base64 token [%s]\n", LogTime(), PROGRAM, b64Token);
665  fprintf(stdout, "BH Invalid negotiate request token\n");
666  continue;
667  }
668  input_token.length = dstLen;
669 
670  if ((input_token.length >= sizeof ntlmProtocol + 1) &&
671  (!memcmp(input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
672  debug((char *) "%s| %s: WARNING: received type %d NTLM token\n",
673  LogTime(), PROGRAM,
674  (int) *((unsigned char *) input_token.value +
675  sizeof ntlmProtocol));
676  fprintf(stdout, "BH received type %d NTLM token\n",
677  (int) *((unsigned char *) input_token.value +
678  sizeof ntlmProtocol));
679  goto cleanup;
680  }
681  if (service_principal) {
682  if (strcasecmp(service_principal, "GSS_C_NO_NAME")) {
683  major_status = gss_import_name(&minor_status, &service,
684  (gss_OID) GSS_C_NULL_OID, &server_name);
685 
686  } else {
687  server_name = GSS_C_NO_NAME;
688  major_status = GSS_S_COMPLETE;
689  minor_status = 0;
690  }
691  } else {
692  major_status = gss_import_name(&minor_status, &service,
693  gss_nt_service_name, &server_name);
694  }
695 
696  if (check_gss_err(major_status, minor_status, "gss_import_name()", log, 1))
697  goto cleanup;
698 
699  major_status =
700  gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
701  GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_creds, nullptr, nullptr);
702  if (check_gss_err(major_status, minor_status, "gss_acquire_cred()", log, 1))
703  goto cleanup;
704 
705  major_status = gss_accept_sec_context(&minor_status,
706  &gss_context,
707  server_creds,
708  &input_token,
709  GSS_C_NO_CHANNEL_BINDINGS,
710  &client_name, nullptr, &output_token, &ret_flags, nullptr, nullptr);
711 
712  if (output_token.length) {
713  spnegoToken = (const unsigned char *) output_token.value;
714  spnegoTokenLength = output_token.length;
715  token = (char *) xmalloc((size_t)base64_encode_len(spnegoTokenLength));
716  if (token == nullptr) {
717  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
718  fprintf(stdout, "BH Not enough memory\n");
719  goto cleanup;
720  }
721  struct base64_encode_ctx tokCtx;
722  base64_encode_init(&tokCtx);
723  size_t blen = base64_encode_update(&tokCtx, token, spnegoTokenLength, reinterpret_cast<const uint8_t*>(spnegoToken));
724  blen += base64_encode_final(&tokCtx, token+blen);
725  token[blen] = '\0';
726 
727  if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
728  goto cleanup;
729  if (major_status & GSS_S_CONTINUE_NEEDED) {
730  debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
731  fprintf(stdout, "TT token=%s\n", token);
732  goto cleanup;
733  }
734  gss_release_buffer(&minor_status, &output_token);
735  major_status =
736  gss_display_name(&minor_status, client_name, &output_token,
737  nullptr);
738 
739  if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
740  goto cleanup;
741  user = (char *) xmalloc(output_token.length + 1);
742  if (user == nullptr) {
743  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
744  fprintf(stdout, "BH Not enough memory\n");
745  goto cleanup;
746  }
747  memcpy(user, output_token.value, output_token.length);
748  user[output_token.length] = '\0';
749  if (norealm && (p = strchr(user, '@')) != nullptr) {
750  *p = '\0';
751  }
752 
753 #if HAVE_PAC_SUPPORT
754  ret = krb5_init_context(&context);
755  if (!check_k5_err(context, "krb5_init_context", ret)) {
756 #if HAVE_LIBHEIMDAL_KRB5
757 #define ADWIN2KPAC 128
758  major_status = gsskrb5_extract_authz_data_from_sec_context(&minor_status,
759  gss_context, ADWIN2KPAC, &data_set);
760  if (!check_gss_err(major_status, minor_status,
761  "gsskrb5_extract_authz_data_from_sec_context()", log, 0)) {
762  ret = krb5_pac_parse(context, data_set.value, data_set.length, &pac);
763  gss_release_buffer(&minor_status, &data_set);
764  if (!check_k5_err(context, "krb5_pac_parse", ret)) {
765  ag = get_ad_groups((char *)&ad_groups, context, pac);
766  krb5_pac_free(context, pac);
767  }
768  krb5_free_context(context);
769  }
770 #else
771  type_id.value = (void *)"mspac";
772  type_id.length = strlen((char *)type_id.value);
773 #define KRB5PACLOGONINFO 1
774  major_status = gss_map_name_to_any(&minor_status, client_name, KRB5PACLOGONINFO, &type_id, (gss_any_t *)&pac);
775  if (!check_gss_err(major_status, minor_status, "gss_map_name_to_any()", log, 0)) {
776  ag = get_ad_groups((char *)&ad_groups,context, pac);
777  }
778  (void)gss_release_any_name_mapping(&minor_status, client_name, &type_id, (gss_any_t *)&pac);
779  krb5_free_context(context);
780 #endif
781  }
782  if (ag) {
783  debug((char *) "%s| %s: DEBUG: Groups %s\n", LogTime(), PROGRAM, ag);
784  }
785 #endif
786  rfc_user = rfc1738_escape(user);
787 #if HAVE_PAC_SUPPORT
788  fprintf(stdout, "OK token=%s user=%s %s\n", token, rfc_user, ag?ag:"group=");
789 #else
790  fprintf(stdout, "OK token=%s user=%s\n", token, rfc_user);
791 #endif
792  debug((char *) "%s| %s: DEBUG: OK token=%s user=%s\n", LogTime(), PROGRAM, token, rfc_user);
793  if (log)
794  fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
795  PROGRAM, rfc_user);
796  goto cleanup;
797  } else {
798  if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
799  goto cleanup;
800  if (major_status & GSS_S_CONTINUE_NEEDED) {
801  debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
802  // XXX: where to get the server token for delivery to client? token is nullptr here.
803  fprintf(stdout, "ERR\n");
804  goto cleanup;
805  }
806  gss_release_buffer(&minor_status, &output_token);
807  major_status =
808  gss_display_name(&minor_status, client_name, &output_token,
809  nullptr);
810 
811  if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
812  goto cleanup;
813  /*
814  * Return dummy token AA. May need an extra return tag then AF
815  */
816  user = (char *) xmalloc(output_token.length + 1);
817  if (user == nullptr) {
818  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
819  fprintf(stdout, "BH Not enough memory\n");
820  goto cleanup;
821  }
822  memcpy(user, output_token.value, output_token.length);
823  user[output_token.length] = '\0';
824  if (norealm && (p = strchr(user, '@')) != nullptr) {
825  *p = '\0';
826  }
827  rfc_user = rfc1738_escape(user);
828 #if HAVE_PAC_SUPPORT
829  fprintf(stdout, "OK token=%s user=%s %s\n", "AA==", rfc_user, ag?ag:"group=");
830 #else
831  fprintf(stdout, "OK token=%s user=%s\n", "AA==", rfc_user);
832 #endif
833  debug((char *) "%s| %s: DEBUG: OK token=%s user=%s\n", LogTime(), PROGRAM, "AA==", rfc_user);
834  if (log)
835  fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
836  PROGRAM, rfc_user);
837  }
838 cleanup:
839  gss_release_buffer(&minor_status, &input_token);
840  gss_release_buffer(&minor_status, &output_token);
841  gss_release_cred(&minor_status, &server_creds);
842  if (server_name)
843  gss_release_name(&minor_status, &server_name);
844  if (client_name)
845  gss_release_name(&minor_status, &client_name);
846  if (kerberosToken) {
847  /* Allocated by parseNegTokenInit, but no matching free function exists.. */
848  if (!spnego_flag)
849  safe_free(kerberosToken);
850  }
851  if (spnego_flag) {
852  /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
853  safe_free(spnegoToken);
854  }
855  safe_free(token);
856  safe_free(user);
857  continue;
858  }
859  return EXIT_SUCCESS;
860 }
861 #else
862 #include <cstdlib>
863 #ifndef MAX_AUTHTOKEN_LEN
864 #define MAX_AUTHTOKEN_LEN 65535
865 #endif
866 int
867 main(int argc, char *const argv[])
868 {
869  setbuf(stdout, nullptr);
870  setbuf(stdin, nullptr);
871  char buf[MAX_AUTHTOKEN_LEN];
872  while (1) {
873  if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
874  fprintf(stdout, "BH input error\n");
875  exit(EXIT_SUCCESS);
876  }
877  fprintf(stdout, "BH Kerberos authentication not supported\n");
878  }
879  return EXIT_SUCCESS;
880 }
881 #endif /* HAVE_GSSAPI */
882 
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
#define base64_encode_len(length)
Definition: base64.h:169
#define xmalloc
#define gss_nt_service_name
#define PROGRAM
Definition: support.h:169
int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, const char *function, int log, int sout)
void debug(const char *format,...)
Definition: debug.cc:19
void log(char *format,...)
void base64_decode_init(struct base64_decode_ctx *ctx)
Definition: base64.c:54
size_t base64_encode_final(struct base64_encode_ctx *ctx, char *dst)
Definition: base64.c:308
#define xstrdup
char * optarg
Definition: getopt.c:51
SBuf service_name(APP_SHORTNAME)
int check_k5_err(krb5_context context, const char *msg, krb5_error_code code)
#define rfc1738_escape(x)
Definition: rfc1738.h:52
int getopt(int nargc, char *const *nargv, const char *ostr)
Definition: getopt.c:62
char * strerror(int ern)
Definition: strerror.c:22
int base64_decode_final(struct base64_decode_ctx *ctx)
Definition: base64.c:159
#define NULL
Definition: types.h:145
#define MAX_AUTHTOKEN_LEN
#define SQUID_KERB_AUTH_VERSION
int debug_enabled
Definition: debug.cc:13
#define safe_free(x)
Definition: xalloc.h:73
int base64_decode_update(struct base64_decode_ctx *ctx, size_t *dst_length, uint8_t *dst, size_t src_length, const char *src)
Definition: base64.c:129
static const unsigned char ntlmProtocol[]
void base64_encode_init(struct base64_encode_ctx *ctx)
Definition: base64.c:232
const char * LogTime(void)
#define xfree
char * gethost_name(void)
int main(int argc, char *const argv[])
size_t base64_encode_update(struct base64_encode_ctx *ctx, char *dst, size_t length, const uint8_t *src)
Definition: base64.c:265
#define MAXPATHLEN
Definition: stdio.h:62
int xgethostname(char *name, size_t nameLength)
POSIX gethostname(2) equivalent.
Definition: unistd.h:49
#define BASE64_DECODE_LENGTH(length)
Definition: base64.h:120

 

Introduction

Documentation

Support

Miscellaneous