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  char *memory_keytab_name_env = nullptr;
352 #endif
353  char *rcache_type = nullptr;
354  char *rcache_type_env = nullptr;
355  char *rcache_dir = nullptr;
356  char *rcache_dir_env = nullptr;
357  OM_uint32 major_status, minor_status;
358  gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
359  gss_name_t client_name = GSS_C_NO_NAME;
360  gss_name_t server_name = GSS_C_NO_NAME;
361  gss_cred_id_t server_creds = GSS_C_NO_CREDENTIAL;
362  gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
363  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
364  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
365  const unsigned char *kerberosToken = nullptr;
366  const unsigned char *spnegoToken = nullptr;
367  size_t spnegoTokenLength = 0;
368 
369  setbuf(stdout, nullptr);
370  setbuf(stdin, nullptr);
371 
372  while (-1 != (opt = getopt(argc, argv, "dirs:k:c:t:"))) {
373  switch (opt) {
374  case 'd':
375  debug_enabled = 1;
376  break;
377  case 'i':
378  log = 1;
379  break;
380  case 'r':
381  norealm = 1;
382  break;
383  case 'k':
384 #if HAVE_SYS_STAT_H
385  struct stat fstat;
386  char *ktp;
387 #endif
388  if (optarg)
389  keytab_name = xstrdup(optarg);
390  else {
391  fprintf(stderr, "ERROR: keytab file not given\n");
392  exit(EXIT_FAILURE);
393  }
394  /*
395  * Some sanity checks
396  */
397 #if HAVE_SYS_STAT_H
398  if ((ktp=strchr(keytab_name,':')))
399  ktp++;
400  else
401  ktp=keytab_name;
402  if (stat((const char*)ktp, &fstat)) {
403  if (ENOENT == errno)
404  fprintf(stderr, "ERROR: keytab file %s does not exist\n",keytab_name);
405  else
406  fprintf(stderr, "ERROR: Error %s during stat of keytab file %s\n",strerror(errno),keytab_name);
407  exit(EXIT_FAILURE);
408  } else if (!S_ISREG(fstat.st_mode)) {
409  fprintf(stderr, "ERROR: keytab file %s is not a file\n",keytab_name);
410  exit(EXIT_FAILURE);
411  }
412 #endif
413 #if HAVE_UNISTD_H
414  if (access(ktp, R_OK)) {
415  fprintf(stderr, "ERROR: keytab file %s is not accessible\n",keytab_name);
416  exit(EXIT_FAILURE);
417  }
418 #endif
419  break;
420  case 'c':
421 #if HAVE_SYS_STAT_H
422  struct stat dstat;
423 #endif
424  if (optarg)
425  rcache_dir = xstrdup(optarg);
426  else {
427  fprintf(stderr, "ERROR: replay cache directory not given\n");
428  exit(EXIT_FAILURE);
429  }
430  /*
431  * Some sanity checks
432  */
433 #if HAVE_SYS_STAT_H
434  if (stat((const char*)rcache_dir, &dstat)) {
435  if (ENOENT == errno)
436  fprintf(stderr, "ERROR: replay cache directory %s does not exist\n",rcache_dir);
437  else
438  fprintf(stderr, "ERROR: Error %s during stat of replay cache directory %s\n",strerror(errno),rcache_dir);
439  exit(EXIT_FAILURE);
440  } else if (!S_ISDIR(dstat.st_mode)) {
441  fprintf(stderr, "ERROR: replay cache directory %s is not a directory\n",rcache_dir);
442  exit(EXIT_FAILURE);
443  }
444 #endif
445 #if HAVE_UNISTD_H
446  if (access(rcache_dir, W_OK)) {
447  fprintf(stderr, "ERROR: replay cache directory %s is not accessible\n",rcache_dir);
448  exit(EXIT_FAILURE);
449  }
450 #endif
451  break;
452  case 't':
453  if (optarg)
454  rcache_type = xstrdup(optarg);
455  else {
456  fprintf(stderr, "ERROR: replay cache type not given\n");
457  exit(EXIT_FAILURE);
458  }
459  break;
460  case 's':
461  if (optarg)
462  service_principal = xstrdup(optarg);
463  else {
464  fprintf(stderr, "ERROR: service principal not given\n");
465  exit(EXIT_FAILURE);
466  }
467  break;
468  default:
469  fprintf(stderr, "Usage: \n");
470  fprintf(stderr, "squid_kerb_auth [-d] [-i] [-s SPN] [-k keytab] [-c rcdir] [-t rctype]\n");
471  fprintf(stderr, "-d full debug\n");
472  fprintf(stderr, "-i informational messages\n");
473  fprintf(stderr, "-r remove realm from username\n");
474  fprintf(stderr, "-s service principal name\n");
475  fprintf(stderr, "-k keytab name\n");
476  fprintf(stderr, "-c replay cache directory\n");
477  fprintf(stderr, "-t replay cache type\n");
478  fprintf(stderr,
479  "The SPN can be set to GSS_C_NO_NAME to allow any entry from keytab\n");
480  fprintf(stderr, "default SPN is HTTP/fqdn@DEFAULT_REALM\n");
481  exit(EXIT_SUCCESS);
482  }
483  }
484 
485  debug((char *) "%s| %s: INFO: Starting version %s\n", LogTime(), PROGRAM, SQUID_KERB_AUTH_VERSION);
486  if (service_principal && strcasecmp(service_principal, "GSS_C_NO_NAME")) {
487  if (!strstr(service_principal,"HTTP/")) {
488  debug((char *) "%s| %s: WARN: service_principal %s does not start with HTTP/\n",
489  LogTime(), PROGRAM, service_principal);
490  }
491  service.value = service_principal;
492  service.length = strlen((char *) service.value);
493  } else {
494  host_name = gethost_name();
495  if (!host_name) {
496  fprintf(stderr,
497  "%s| %s: FATAL: Local hostname could not be determined. Please specify the service principal\n",
498  LogTime(), PROGRAM);
499  fprintf(stdout, "BH hostname error\n");
500  exit(EXIT_FAILURE);
501  }
502  service.value = xmalloc(strlen(service_name) + strlen(host_name) + 2);
503  snprintf((char *) service.value, strlen(service_name) + strlen(host_name) + 2,
504  "%s@%s", service_name, host_name);
505  service.length = strlen((char *) service.value);
506  xfree(host_name);
507  }
508 
509  if (rcache_type) {
510  rcache_type_env = (char *) xmalloc(strlen("KRB5RCACHETYPE=")+strlen(rcache_type)+1);
511  strcpy(rcache_type_env, "KRB5RCACHETYPE=");
512  strcat(rcache_type_env, rcache_type);
513  putenv(rcache_type_env);
514  debug((char *) "%s| %s: INFO: Setting replay cache type to %s\n",
515  LogTime(), PROGRAM, rcache_type);
516  }
517 
518  if (rcache_dir) {
519  rcache_dir_env = (char *) xmalloc(strlen("KRB5RCACHEDIR=")+strlen(rcache_dir)+1);
520  strcpy(rcache_dir_env, "KRB5RCACHEDIR=");
521  strcat(rcache_dir_env, rcache_dir);
522  putenv(rcache_dir_env);
523  debug((char *) "%s| %s: INFO: Setting replay cache directory to %s\n",
524  LogTime(), PROGRAM, rcache_dir);
525  }
526 
527  if (keytab_name) {
528  keytab_name_env = (char *) xmalloc(strlen("KRB5_KTNAME=")+strlen(keytab_name)+1);
529  strcpy(keytab_name_env, "KRB5_KTNAME=");
530  strcat(keytab_name_env, keytab_name);
531  putenv(keytab_name_env);
532  } else {
533  keytab_name_env = getenv("KRB5_KTNAME");
534  if (!keytab_name_env) {
535  ret = krb5_init_context(&context);
536  if (!check_k5_err(context, "krb5_init_context", ret)) {
537  krb5_kt_default_name(context, default_keytab, MAXPATHLEN);
538  }
539  keytab_name = xstrdup(default_keytab);
540  krb5_free_context(context);
541  } else
542  keytab_name = xstrdup(keytab_name_env);
543  }
544  debug((char *) "%s| %s: INFO: Setting keytab to %s\n", LogTime(), PROGRAM, keytab_name);
545 #if HAVE_KRB5_MEMORY_KEYTAB
546  ret = krb5_init_context(&context);
547  if (!check_k5_err(context, "krb5_init_context", ret)) {
548  memory_keytab_name = (char *)xmalloc(strlen("MEMORY:negotiate_kerberos_auth_")+16);
549  snprintf(memory_keytab_name, strlen("MEMORY:negotiate_kerberos_auth_")+16,
550  "MEMORY:negotiate_kerberos_auth_%d", (unsigned int) getpid());
551  ret = krb5_read_keytab(context, keytab_name, &ktlist);
552  if (check_k5_err(context, "krb5_read_keytab", ret)) {
553  debug((char *) "%s| %s: ERROR: Reading keytab %s into list failed\n",
554  LogTime(), PROGRAM, keytab_name);
555  } else {
556  ret = krb5_write_keytab(context, ktlist, memory_keytab_name);
557  if (check_k5_err(context, "krb5_write_keytab", ret)) {
558  debug((char *) "%s| %s: ERROR: Writing list into keytab %s\n",
559  LogTime(), PROGRAM, memory_keytab_name);
560  } else {
561  memory_keytab_name_env = (char *) xmalloc(strlen("KRB5_KTNAME=")+strlen(memory_keytab_name)+1);
562  strcpy(memory_keytab_name_env, "KRB5_KTNAME=");
563  strcat(memory_keytab_name_env, memory_keytab_name);
564  putenv(memory_keytab_name_env);
565  xfree(keytab_name);
566  keytab_name = xstrdup(memory_keytab_name);
567  debug((char *) "%s| %s: INFO: Changed keytab to %s\n",
568  LogTime(), PROGRAM, memory_keytab_name);
569  }
570  }
571  ret = krb5_free_kt_list(context,ktlist);
572  if (check_k5_err(context, "krb5_free_kt_list", ret)) {
573  debug((char *) "%s| %s: ERROR: Freeing list failed\n",
574  LogTime(), PROGRAM);
575  }
576  }
577  krb5_free_context(context);
578 #endif
579 #ifdef HAVE_HEIMDAL_KERBEROS
580  gsskrb5_register_acceptor_identity(keytab_name);
581 #endif
582  while (1) {
583  if (fgets(buf, sizeof(buf) - 1, stdin) == nullptr) {
584  if (ferror(stdin)) {
585  debug((char *) "%s| %s: FATAL: fgets() failed! dying..... errno=%d (%s)\n",
586  LogTime(), PROGRAM, ferror(stdin),
587  strerror(ferror(stdin)));
588 
589  fprintf(stdout, "BH input error\n");
590  exit(EXIT_FAILURE); /* BIIG buffer */
591  }
592  fprintf(stdout, "BH input error\n");
593  exit(EXIT_SUCCESS);
594  }
595  c = (char *) memchr(buf, '\n', sizeof(buf) - 1);
596  if (c) {
597  *c = '\0';
598  length = c - buf;
599  } else {
600  err = 1;
601  }
602  if (err) {
603  debug((char *) "%s| %s: ERROR: Oversized message\n", LogTime(), PROGRAM);
604  fprintf(stdout, "BH Oversized message\n");
605  err = 0;
606  continue;
607  }
608  debug((char *) "%s| %s: DEBUG: Got '%s' from squid (length: %ld).\n", LogTime(), PROGRAM, buf, length);
609 
610  if (buf[0] == '\0') {
611  debug((char *) "%s| %s: ERROR: Invalid request\n", LogTime(), PROGRAM);
612  fprintf(stdout, "BH Invalid request\n");
613  continue;
614  }
615  if (strlen(buf) < 2) {
616  debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
617  fprintf(stdout, "BH Invalid request\n");
618  continue;
619  }
620  if (!strncmp(buf, "QQ", 2)) {
621  gss_release_buffer(&minor_status, &input_token);
622  gss_release_buffer(&minor_status, &output_token);
623  gss_release_buffer(&minor_status, &service);
624  gss_release_cred(&minor_status, &server_creds);
625  if (server_name)
626  gss_release_name(&minor_status, &server_name);
627  if (client_name)
628  gss_release_name(&minor_status, &client_name);
629  if (gss_context != GSS_C_NO_CONTEXT)
630  gss_delete_sec_context(&minor_status, &gss_context, nullptr);
631  if (kerberosToken) {
632  /* Allocated by parseNegTokenInit, but no matching free function exists.. */
633  if (!spnego_flag)
634  xfree(kerberosToken);
635  }
636  if (spnego_flag) {
637  /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
638  xfree(spnegoToken);
639  }
640  xfree(token);
641  xfree(rcache_type);
642  xfree(rcache_type_env);
643  xfree(rcache_dir);
644  xfree(rcache_dir_env);
645  xfree(keytab_name);
646  xfree(keytab_name_env);
647 #if HAVE_KRB5_MEMORY_KEYTAB
648  krb5_kt_close(context, memory_keytab);
649  xfree(memory_keytab_name);
650  xfree(memory_keytab_name_env);
651 #endif
652  xfree(rfc_user);
653  fprintf(stdout, "BH quit command\n");
654  exit(EXIT_SUCCESS);
655  }
656  if (strncmp(buf, "YR", 2) && strncmp(buf, "KK", 2)) {
657  debug((char *) "%s| %s: ERROR: Invalid request [%s]\n", LogTime(), PROGRAM, buf);
658  fprintf(stdout, "BH Invalid request\n");
659  continue;
660  }
661  if (!strncmp(buf, "YR", 2)) {
662  if (gss_context != GSS_C_NO_CONTEXT)
663  gss_delete_sec_context(&minor_status, &gss_context, nullptr);
664  gss_context = GSS_C_NO_CONTEXT;
665  }
666  if (strlen(buf) <= 3) {
667  debug((char *) "%s| %s: ERROR: Invalid negotiate request [%s]\n", LogTime(), PROGRAM, buf);
668  fprintf(stdout, "BH Invalid negotiate request\n");
669  continue;
670  }
671  const char *b64Token = buf+3;
672  const size_t srcLen = strlen(buf+3);
673  input_token.length = BASE64_DECODE_LENGTH(srcLen);
674  debug((char *) "%s| %s: DEBUG: Decode '%s' (decoded length estimate: %d).\n",
675  LogTime(), PROGRAM, b64Token, (int) input_token.length);
676  input_token.value = xmalloc(input_token.length);
677 
678  struct base64_decode_ctx ctx;
679  base64_decode_init(&ctx);
680  size_t dstLen = 0;
681  if (!base64_decode_update(&ctx, &dstLen, static_cast<uint8_t*>(input_token.value), srcLen, b64Token) ||
682  !base64_decode_final(&ctx)) {
683  debug((char *) "%s| %s: ERROR: Invalid base64 token [%s]\n", LogTime(), PROGRAM, b64Token);
684  fprintf(stdout, "BH Invalid negotiate request token\n");
685  continue;
686  }
687  input_token.length = dstLen;
688 
689  if ((input_token.length >= sizeof ntlmProtocol + 1) &&
690  (!memcmp(input_token.value, ntlmProtocol, sizeof ntlmProtocol))) {
691  debug((char *) "%s| %s: WARNING: received type %d NTLM token\n",
692  LogTime(), PROGRAM,
693  (int) *((unsigned char *) input_token.value +
694  sizeof ntlmProtocol));
695  fprintf(stdout, "BH received type %d NTLM token\n",
696  (int) *((unsigned char *) input_token.value +
697  sizeof ntlmProtocol));
698  goto cleanup;
699  }
700  if (service_principal) {
701  if (strcasecmp(service_principal, "GSS_C_NO_NAME")) {
702  major_status = gss_import_name(&minor_status, &service,
703  (gss_OID) GSS_C_NULL_OID, &server_name);
704 
705  } else {
706  server_name = GSS_C_NO_NAME;
707  major_status = GSS_S_COMPLETE;
708  minor_status = 0;
709  }
710  } else {
711  major_status = gss_import_name(&minor_status, &service,
712  gss_nt_service_name, &server_name);
713  }
714 
715  if (check_gss_err(major_status, minor_status, "gss_import_name()", log, 1))
716  goto cleanup;
717 
718  major_status =
719  gss_acquire_cred(&minor_status, server_name, GSS_C_INDEFINITE,
720  GSS_C_NO_OID_SET, GSS_C_ACCEPT, &server_creds, nullptr, nullptr);
721  if (check_gss_err(major_status, minor_status, "gss_acquire_cred()", log, 1))
722  goto cleanup;
723 
724  major_status = gss_accept_sec_context(&minor_status,
725  &gss_context,
726  server_creds,
727  &input_token,
728  GSS_C_NO_CHANNEL_BINDINGS,
729  &client_name, nullptr, &output_token, &ret_flags, nullptr, nullptr);
730 
731  if (output_token.length) {
732  spnegoToken = (const unsigned char *) output_token.value;
733  spnegoTokenLength = output_token.length;
734  token = (char *) xmalloc((size_t)base64_encode_len(spnegoTokenLength));
735  if (token == nullptr) {
736  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
737  fprintf(stdout, "BH Not enough memory\n");
738  goto cleanup;
739  }
740  struct base64_encode_ctx tokCtx;
741  base64_encode_init(&tokCtx);
742  size_t blen = base64_encode_update(&tokCtx, token, spnegoTokenLength, reinterpret_cast<const uint8_t*>(spnegoToken));
743  blen += base64_encode_final(&tokCtx, token+blen);
744  token[blen] = '\0';
745 
746  if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
747  goto cleanup;
748  if (major_status & GSS_S_CONTINUE_NEEDED) {
749  debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
750  fprintf(stdout, "TT token=%s\n", token);
751  goto cleanup;
752  }
753  gss_release_buffer(&minor_status, &output_token);
754  major_status =
755  gss_display_name(&minor_status, client_name, &output_token,
756  nullptr);
757 
758  if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
759  goto cleanup;
760  user = (char *) xmalloc(output_token.length + 1);
761  if (user == nullptr) {
762  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
763  fprintf(stdout, "BH Not enough memory\n");
764  goto cleanup;
765  }
766  memcpy(user, output_token.value, output_token.length);
767  user[output_token.length] = '\0';
768  if (norealm && (p = strchr(user, '@')) != nullptr) {
769  *p = '\0';
770  }
771 
772 #if HAVE_PAC_SUPPORT
773  ret = krb5_init_context(&context);
774  if (!check_k5_err(context, "krb5_init_context", ret)) {
775 #if HAVE_LIBHEIMDAL_KRB5
776 #define ADWIN2KPAC 128
777  major_status = gsskrb5_extract_authz_data_from_sec_context(&minor_status,
778  gss_context, ADWIN2KPAC, &data_set);
779  if (!check_gss_err(major_status, minor_status,
780  "gsskrb5_extract_authz_data_from_sec_context()", log, 0)) {
781  ret = krb5_pac_parse(context, data_set.value, data_set.length, &pac);
782  gss_release_buffer(&minor_status, &data_set);
783  if (!check_k5_err(context, "krb5_pac_parse", ret)) {
784  ag = get_ad_groups((char *)&ad_groups, context, pac);
785  krb5_pac_free(context, pac);
786  }
787  krb5_free_context(context);
788  }
789 #else
790  type_id.value = (void *)"mspac";
791  type_id.length = strlen((char *)type_id.value);
792 #define KRB5PACLOGONINFO 1
793  major_status = gss_map_name_to_any(&minor_status, client_name, KRB5PACLOGONINFO, &type_id, (gss_any_t *)&pac);
794  if (!check_gss_err(major_status, minor_status, "gss_map_name_to_any()", log, 0)) {
795  ag = get_ad_groups((char *)&ad_groups,context, pac);
796  }
797  (void)gss_release_any_name_mapping(&minor_status, client_name, &type_id, (gss_any_t *)&pac);
798  krb5_free_context(context);
799 #endif
800  }
801  if (ag) {
802  debug((char *) "%s| %s: DEBUG: Groups %s\n", LogTime(), PROGRAM, ag);
803  }
804 #endif
805  rfc_user = rfc1738_escape(user);
806 #if HAVE_PAC_SUPPORT
807  fprintf(stdout, "OK token=%s user=%s %s\n", token, rfc_user, ag?ag:"group=");
808 #else
809  fprintf(stdout, "OK token=%s user=%s\n", token, rfc_user);
810 #endif
811  debug((char *) "%s| %s: DEBUG: OK token=%s user=%s\n", LogTime(), PROGRAM, token, rfc_user);
812  if (log)
813  fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
814  PROGRAM, rfc_user);
815  goto cleanup;
816  } else {
817  if (check_gss_err(major_status, minor_status, "gss_accept_sec_context()", log, 1))
818  goto cleanup;
819  if (major_status & GSS_S_CONTINUE_NEEDED) {
820  debug((char *) "%s| %s: INFO: continuation needed\n", LogTime(), PROGRAM);
821  // XXX: where to get the server token for delivery to client? token is nullptr here.
822  fprintf(stdout, "ERR\n");
823  goto cleanup;
824  }
825  gss_release_buffer(&minor_status, &output_token);
826  major_status =
827  gss_display_name(&minor_status, client_name, &output_token,
828  nullptr);
829 
830  if (check_gss_err(major_status, minor_status, "gss_display_name()", log, 1))
831  goto cleanup;
832  /*
833  * Return dummy token AA. May need an extra return tag then AF
834  */
835  user = (char *) xmalloc(output_token.length + 1);
836  if (user == nullptr) {
837  debug((char *) "%s| %s: ERROR: Not enough memory\n", LogTime(), PROGRAM);
838  fprintf(stdout, "BH Not enough memory\n");
839  goto cleanup;
840  }
841  memcpy(user, output_token.value, output_token.length);
842  user[output_token.length] = '\0';
843  if (norealm && (p = strchr(user, '@')) != nullptr) {
844  *p = '\0';
845  }
846  rfc_user = rfc1738_escape(user);
847 #if HAVE_PAC_SUPPORT
848  fprintf(stdout, "OK token=%s user=%s %s\n", "AA==", rfc_user, ag?ag:"group=");
849 #else
850  fprintf(stdout, "OK token=%s user=%s\n", "AA==", rfc_user);
851 #endif
852  debug((char *) "%s| %s: DEBUG: OK token=%s user=%s\n", LogTime(), PROGRAM, "AA==", rfc_user);
853  if (log)
854  fprintf(stderr, "%s| %s: INFO: User %s authenticated\n", LogTime(),
855  PROGRAM, rfc_user);
856  }
857 cleanup:
858  gss_release_buffer(&minor_status, &input_token);
859  gss_release_buffer(&minor_status, &output_token);
860  gss_release_cred(&minor_status, &server_creds);
861  if (server_name)
862  gss_release_name(&minor_status, &server_name);
863  if (client_name)
864  gss_release_name(&minor_status, &client_name);
865  if (kerberosToken) {
866  /* Allocated by parseNegTokenInit, but no matching free function exists.. */
867  if (!spnego_flag)
868  safe_free(kerberosToken);
869  }
870  if (spnego_flag) {
871  /* Allocated by makeNegTokenTarg, but no matching free function exists.. */
872  safe_free(spnegoToken);
873  }
874  safe_free(token);
875  safe_free(user);
876  continue;
877  }
878  return EXIT_SUCCESS;
879 }
880 #else
881 #include <cstdlib>
882 #ifndef MAX_AUTHTOKEN_LEN
883 #define MAX_AUTHTOKEN_LEN 65535
884 #endif
885 int
886 main(int argc, char *const argv[])
887 {
888  setbuf(stdout, nullptr);
889  setbuf(stdin, nullptr);
890  char buf[MAX_AUTHTOKEN_LEN];
891  while (1) {
892  if (fgets(buf, sizeof(buf) - 1, stdin) == NULL) {
893  fprintf(stdout, "BH input error\n");
894  exit(EXIT_SUCCESS);
895  }
896  fprintf(stdout, "BH Kerberos authentication not supported\n");
897  }
898  return EXIT_SUCCESS;
899 }
900 #endif /* HAVE_GSSAPI */
901 
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