text: /*
   * Copyright 2020 Dmitry Timoshkov
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
   */
  
  #include <stdarg.h>
  
  #include "windef.h"
  #include "winbase.h"
  #include "iads.h"
  #include "winldap.h"
  
  #include "adsldp_private.h"
  
  #include "wine/heap.h"
  #include "wine/debug.h"
  
  WINE_DEFAULT_DEBUG_CHANNEL(adsldp);
  
  static const struct attribute_type *find_schema_type_sorted(const WCHAR *name, const struct attribute_type *at, ULONG count)
  {
      int idx, min, max, res;
  
      if (!count) return NULL;
  
      min = 0;
      max = count - 1;
  
      while (min <= max)
      {
          idx = (min + max) / 2;
          res = wcsicmp(name, at[idx].name);
          if (!res) return &at[idx];
          if (res > 0) min = idx + 1;
          else max = idx - 1;
      }
  
      return NULL;
  }
  
  
  static const struct attribute_type *find_schema_type(const WCHAR *name, const struct attribute_type *at, ULONG single, ULONG multiple)
  {
      const struct attribute_type *found;
      ULONG i, n, off;
  
      /* Perform binary search within definitions with single name */
      found = find_schema_type_sorted(name, at, single);
      if (found) return found;
  
      /* Perform linear search within definitions with multiple names */
      at += single;
  
      for (i = 0; i < multiple; i++)
      {
          off = 0;
  
          for (n = 0; n < at[i].name_count; n++)
          {
              if (!wcsicmp(at[i].name + off, name)) return &at[i];
              off += wcslen(at[i].name + off) + 1;
          }
      }
  
      FIXME("%s not found\n", debugstr_w(name));
      return NULL;
  }
  
  /* RFC 4517 */
  ADSTYPEENUM get_schema_type(const WCHAR *name, const struct attribute_type *at, ULONG single, ULONG multiple)
  {
      const struct attribute_type *type;
  
      type = find_schema_type(name, at, single, multiple);
      if (!type || !type->syntax) return ADSTYPE_CASE_IGNORE_STRING;
  
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.7"))
          return ADSTYPE_BOOLEAN;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.12"))
          return ADSTYPE_DN_STRING;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.15"))
          return ADSTYPE_CASE_IGNORE_STRING;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.24"))
          return ADSTYPE_UTC_TIME;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.26"))
          return ADSTYPE_CASE_EXACT_STRING;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.27"))
          return ADSTYPE_INTEGER;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.38"))
          return ADSTYPE_CASE_IGNORE_STRING;
      if (!wcscmp(type->syntax, L"1.3.6.1.4.1.1466.115.121.1.40"))
          return ADSTYPE_OCTET_STRING;
      if (!wcscmp(type->syntax, L"1.2.840.113556.1.4.903"))
          return ADSTYPE_DN_WITH_BINARY;
      if (!wcscmp(type->syntax, L"1.2.840.113556.1.4.906"))
          return ADSTYPE_LARGE_INTEGER;
      if (!wcscmp(type->syntax, L"1.2.840.113556.1.4.907"))
          return ADSTYPE_NT_SECURITY_DESCRIPTOR;
  
      FIXME("not handled type syntax %s for %s\n", debugstr_w(type->syntax), debugstr_w(name));
      return ADSTYPE_CASE_IGNORE_STRING;
  }
  
  static void free_attribute_type(struct attribute_type *at)
  {
      heap_free(at->oid);
      heap_free(at->name);
      heap_free(at->syntax);
  }
  
  void free_attribute_types(struct attribute_type *at, ULONG count)
  {
      ULONG i;
  
      for (i = 0; i < count; i++)
          free_attribute_type(&at[i]);
  
      heap_free(at);
  }
  
  static BOOL is_space(WCHAR c)
  {
      return c == ' ' || c == '\t' || c == '\r' || c == '\n';
  }
  
  static WCHAR *parse_oid(WCHAR **str)
  {
      WCHAR *oid, *p = *str, *end;
      int count;
  
      while (is_space(*p)) p++;
  
      if (*p == '\'')
      {
          p++;
          end = wcschr(p, '\'');
          if (!end) return NULL;
      }
      else
      {
          end = p;
          while (!is_space(*end)) end++;
      }
  
      count = end - p;
      oid = heap_alloc((count + 1) * sizeof(WCHAR));
      if (!oid) return NULL;
  
      memcpy(oid, p, count * sizeof(WCHAR));
      oid[count] = 0;
  
      *str = end + 1;
  
      return oid;
  }
  
  static WCHAR *parse_name(WCHAR **str, ULONG *name_count)
  {
      WCHAR *name, *p = *str, *end;
      int count;
  
      *name_count = 0;
  
      while (is_space(*p)) p++;
  
      if (*p == '(')
      {
          int total_count = 0;
  
          p++;
          name = NULL;
  
          while (*p)
          {
              WCHAR *tmp_name, *new_name;
              ULONG dummy;
  
              while (is_space(*p)) p++;
              if (*p == ')')
              {
                  *str = p + 1;
                  return name;
              }
  
              tmp_name = parse_name(&p, &dummy);
              if (!tmp_name) break;
  
              TRACE("NAME[%u] %s\n", *name_count, debugstr_w(tmp_name));
  
              count = wcslen(tmp_name);
  
              if (!name)
                  new_name = heap_alloc((count + 1) * sizeof(WCHAR));
              else
                  new_name = heap_realloc(name, (total_count + count + 1) * sizeof(WCHAR));
  
              if (!new_name) break;
  
              memcpy(new_name + total_count, tmp_name, (count + 1) * sizeof(WCHAR));
  
              name = new_name;
              heap_free(tmp_name);
              total_count += count + 1;
  
              *name_count += 1;
              *str = p;
          }
  
          *str = *p ? p + 1 : p;
  
          heap_free(name);
          return NULL;
      }
  
      if (*p != '\'')
      {
          FIXME("not suported NAME start at %s\n", debugstr_w(p));
          return NULL;
      }
  
      p++;
      end = wcschr(p, '\'');
      if (!end) return NULL;
  
      count = end - p;
      name = heap_alloc((count + 1) * sizeof(WCHAR));
      if (!name) return NULL;
  
      memcpy(name, p, count * sizeof(WCHAR));
      name[count] = 0;
  
      *name_count = 1;
  
      *str = end + 1;
  
      return name;
  }
  
  static void skip_token(WCHAR **str)
  {
      WCHAR *p = *str;
  
      while (is_space(*p)) p++;
  
      if (*p == '\'')
      {
          p++;
          while (*p && *p != '\'') p++;
      }
      else
      {
          while (*p && !is_space(*p)) p++;
      }
  
      *str = *p ? p + 1 : p;
  }
  
  static BOOL parse_attribute_type(WCHAR *str, struct attribute_type *at)
  {
      static const WCHAR * const not_supported[] = { L"DESC", L"EQUALITY",
          L"ORDERING", L"SUBSTR", L"SUP", L"USAGE", L"X-ORDERED" };
      int i;
      WCHAR *p = str;
  
      at->oid = NULL;
      at->name = NULL;
      at->name_count = 0;
      at->syntax = NULL;
      at->single_value = 0;
  
      while (is_space(*p)) p++;
      if (*p++ != '(') return FALSE;
  
      at->oid = parse_oid(&p);
      if (!at->oid) return FALSE;
  
      while (*p)
      {
          while (is_space(*p)) p++;
          if (*p == ')') return TRUE;
  
          if (!wcsnicmp(p, L"NAME", 4))
          {
              p += 4;
              at->name = parse_name(&p, &at->name_count);
          }
          else if (!wcsnicmp(p, L"SYNTAX", 6))
          {
              p += 6;
              at->syntax = parse_oid(&p);
          }
          else if (!wcsnicmp(p, L"SINGLE-VALUE", 12))
          {
              p += 12;
              at->single_value = 1;
          }
          else if (!wcsnicmp(p, L"NO-USER-MODIFICATION", 20))
          {
              p += 20;
          }
          else
          {
              BOOL recognized = FALSE;
  
              for (i = 0; i < ARRAY_SIZE(not_supported); i++)
              {
                  if (!wcsnicmp(p, not_supported[i], wcslen(not_supported[i])))
                  {
                      p += wcslen(not_supported[i]);
                      skip_token(&p);
                      recognized = TRUE;
                      break;
                  }
              }
  
              if (!recognized)
              {
                  FIXME("not supported token at %s\n", debugstr_w(p));
                  free_attribute_type(at);
                  return FALSE;
              }
          }
      }
  
      WARN("attribute definition is not terminated\n");
  
      free_attribute_type(at);
      return FALSE;
  }
  
  static int __cdecl at_cmp(const void *a1, const void *a2)
  {
      const struct attribute_type *at1 = a1;
      const struct attribute_type *at2 = a2;
  
      if (at1->name_count == 1 && at2->name_count == 1)
          return wcsicmp(at1->name, at2->name);
  
      /* put definitions with multiple names at the end */
      return at1->name_count - at2->name_count;
  }
  
  struct attribute_type *load_schema(LDAP *ld, ULONG *at_single_count, ULONG *at_multiple_count)
  {
      WCHAR *subschema[] = { (WCHAR *)L"subschemaSubentry", NULL };
      WCHAR *attribute_types[] = { (WCHAR *)L"attributeTypes", NULL };
      ULONG err, count, multiple_count;
      LDAPMessage *res, *entry;
      WCHAR **schema = NULL;
      struct attribute_type *at = NULL;
  
      *at_single_count = 0;
      *at_multiple_count = 0;
  
      err = ldap_search_sW(ld, NULL, LDAP_SCOPE_BASE, (WCHAR *)L"(objectClass=*)", subschema, FALSE, &res);
      if (err != LDAP_SUCCESS)
      {
          TRACE("ldap_search_sW error %#x\n", err);
          return NULL;
      }
  
      entry = ldap_first_entry(ld, res);
      if (entry)
          schema = ldap_get_valuesW(ld, entry, subschema[0]);
  
      ldap_msgfree(res);
      if (!schema) return NULL;
  
      err = ldap_search_sW(ld, schema[0], LDAP_SCOPE_BASE, (WCHAR *)L"(objectClass=*)", attribute_types, FALSE, &res);
      if (err != LDAP_SUCCESS)
      {
          TRACE("ldap_search_sW error %#x\n", err);
          ldap_value_freeW(schema);
          return NULL;
      }
  
      count = 0;
      multiple_count = 0;
  
      entry = ldap_first_entry(ld, res);
      if (entry)
      {
          WCHAR **types;
  
          types = ldap_get_valuesW(ld, entry, attribute_types[0]);
          if (types)
          {
              ULONG i, total = ldap_count_valuesW(types);
  
              at = heap_alloc(total * sizeof(*at));
              if (!at) goto exit;
  
              for (i = 0; i < total; i++)
              {
                  TRACE("%s\n", debugstr_w(types[i]));
  
                  if (!parse_attribute_type(types[i], &at[count]))
                  {
                      WARN("parse_attribute_type failed\n");
                      continue;
                  }
  
                  if (!at[count].name)
                  {
                      free_attribute_type(&at[count]);
                      continue;
                  }
  
                  TRACE("oid %s, name %s, name_count %u, syntax %s, single-value %d\n", debugstr_w(at[count].oid),
                        debugstr_w(at[count].name), at[count].name_count, debugstr_w(at[count].syntax), at[count].single_value);
  
                  if (at[count].name_count > 1)
                      multiple_count++;
  
                  count++;
              }
  
              ldap_value_freeW(types);
          }
      }
  
  exit:
      ldap_msgfree(res);
      if (at)
      {
          *at_single_count = count - multiple_count;
          *at_multiple_count = multiple_count;
  
          qsort(at, count, sizeof(at[0]), at_cmp);
      }
  
      return at;
  }
  