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

 

Introduction

Documentation

Support

Miscellaneous