summaryrefslogtreecommitdiffstats
path: root/winsup/cygwin/ldap.cc
diff options
context:
space:
mode:
Diffstat (limited to 'winsup/cygwin/ldap.cc')
-rw-r--r--winsup/cygwin/ldap.cc443
1 files changed, 443 insertions, 0 deletions
diff --git a/winsup/cygwin/ldap.cc b/winsup/cygwin/ldap.cc
new file mode 100644
index 000000000..de09aa2f6
--- /dev/null
+++ b/winsup/cygwin/ldap.cc
@@ -0,0 +1,443 @@
+/* ldap.cc: Helper functions for ldap access to Active Directory.
+
+ Copyright 2014 Red Hat, Inc.
+
+This file is part of Cygwin.
+
+This software is a copyrighted work licensed under the terms of the
+Cygwin license. Please consult the file "CYGWIN_LICENSE" for
+details. */
+
+#include "winsup.h"
+#include "ldap.h"
+#include "cygerrno.h"
+#include "security.h"
+#include "path.h"
+#include "fhandler.h"
+#include "dtable.h"
+#include "cygheap.h"
+#include "registry.h"
+#include "pinfo.h"
+#include "lm.h"
+#include "dsgetdc.h"
+
+static LDAP_TIMEVAL tv = { 3, 0 };
+
+static PWCHAR rootdse_attr[] =
+{
+ (PWCHAR) L"defaultNamingContext",
+ (PWCHAR) L"supportedCapabilities",
+ NULL
+};
+
+static PWCHAR user_attr[] =
+{
+ (PWCHAR) L"uid",
+ (PWCHAR) L"primaryGroupID",
+ (PWCHAR) L"gecos",
+ (PWCHAR) L"unixHomeDirectory",
+ (PWCHAR) L"loginShell",
+ (PWCHAR) L"uidNumber",
+ NULL
+};
+
+static PWCHAR group_attr[] =
+{
+ (PWCHAR) L"cn",
+ (PWCHAR) L"gidNumber",
+ NULL
+};
+
+PWCHAR tdom_attr[] =
+{
+ (PWCHAR) L"trustPosixOffset",
+ NULL
+};
+
+PWCHAR nfs_attr[] =
+{
+ (PWCHAR) L"objectSid",
+ NULL
+};
+
+PWCHAR rfc2307_uid_attr[] =
+{
+ (PWCHAR) L"uid",
+ NULL
+};
+
+PWCHAR rfc2307_gid_attr[] =
+{
+ (PWCHAR) L"cn",
+ NULL
+};
+
+DWORD WINAPI
+rediscover_thread (LPVOID dummy)
+{
+ PDOMAIN_CONTROLLER_INFOW pdci;
+ DWORD ret = DsGetDcNameW (NULL, (PWCHAR) dummy, NULL, NULL,
+ DS_FORCE_REDISCOVERY | DS_ONLY_LDAP_NEEDED, &pdci);
+ if (ret == ERROR_SUCCESS)
+ NetApiBufferFree (pdci);
+ else
+ debug_printf ("DsGetDcNameW(%W) failed with error %u", dummy, ret);
+ return 0;
+}
+
+bool
+cyg_ldap::connect_ssl (PCWSTR domain)
+{
+ ULONG ret, timelimit = 3; /* secs */
+
+ if (!(lh = ldap_sslinitW ((PWCHAR) domain, LDAP_SSL_PORT, 1)))
+ {
+ debug_printf ("ldap_init(%W) error 0x%02x", domain, LdapGetLastError ());
+ return false;
+ }
+ if ((ret = ldap_set_option (lh, LDAP_OPT_TIMELIMIT, &timelimit))
+ != LDAP_SUCCESS)
+ debug_printf ("ldap_set_option(LDAP_OPT_TIMELIMIT) error 0x%02x", ret);
+ if ((ret = ldap_bind_s (lh, NULL, NULL, LDAP_AUTH_NEGOTIATE)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_bind(%W) 0x%02x", domain, ret);
+ ldap_unbind (lh);
+ lh = NULL;
+ return false;
+ }
+ return true;
+}
+
+bool
+cyg_ldap::connect_non_ssl (PCWSTR domain)
+{
+ ULONG ret, timelimit = 3; /* secs */
+
+ if (!(lh = ldap_initW ((PWCHAR) domain, LDAP_PORT)))
+ {
+ debug_printf ("ldap_init(%W) error 0x%02x", domain, LdapGetLastError ());
+ return false;
+ }
+ if ((ret = ldap_set_option (lh, LDAP_OPT_SIGN, LDAP_OPT_ON))
+ != LDAP_SUCCESS)
+ debug_printf ("ldap_set_option(LDAP_OPT_SIGN) error 0x%02x", ret);
+ if ((ret = ldap_set_option (lh, LDAP_OPT_ENCRYPT, LDAP_OPT_ON))
+ != LDAP_SUCCESS)
+ debug_printf ("ldap_set_option(LDAP_OPT_ENCRYPT) error 0x%02x", ret);
+ if ((ret = ldap_set_option (lh, LDAP_OPT_TIMELIMIT, &timelimit))
+ != LDAP_SUCCESS)
+ debug_printf ("ldap_set_option(LDAP_OPT_TIMELIMIT) error 0x%02x", ret);
+ if ((ret = ldap_bind_s (lh, NULL, NULL, LDAP_AUTH_NEGOTIATE)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_bind(%W) 0x%02x", domain, ret);
+ ldap_unbind (lh);
+ lh = NULL;
+ return false;
+ }
+ return true;
+}
+
+bool
+cyg_ldap::open (PCWSTR domain)
+{
+ LARGE_INTEGER start, stop;
+ static LARGE_INTEGER last_rediscover;
+ ULONG ret;
+
+ close ();
+ GetSystemTimeAsFileTime ((LPFILETIME) &start);
+ /* FIXME? connect_ssl can take ages even when failing, so we're trying to
+ do everything the non-SSL (but still encrypted) way. */
+ if (/*!connect_ssl (NULL) && */ !connect_non_ssl (domain))
+ return false;
+ /* For some obscure reason, there's a chance that the ldap_bind_s call takes
+ a long time, if the current primary DC is... well, burping or something.
+ If so, we rediscover in the background which usually switches to the next
+ fastest DC. */
+ GetSystemTimeAsFileTime ((LPFILETIME) &stop);
+ if ((stop.QuadPart - start.QuadPart) >= 3000000LL /* 0.3s */
+ && (stop.QuadPart - last_rediscover.QuadPart) >= 30000000LL) /* 3s */
+ {
+ debug_printf ("ldap_bind_s is laming. Try to rediscover.");
+ HANDLE thr = CreateThread (&sec_none_nih, 4 * PTHREAD_STACK_MIN,
+ rediscover_thread, (LPVOID) domain,
+ STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
+ if (!thr)
+ debug_printf ("Couldn't start rediscover thread.");
+ else
+ {
+ last_rediscover = stop;
+ CloseHandle (thr);
+ }
+ }
+
+ if ((ret = ldap_search_stW (lh, NULL, LDAP_SCOPE_BASE,
+ (PWCHAR) L"(objectclass=*)", rootdse_attr,
+ 0, &tv, &msg))
+ != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_search(%W, ROOTDSE) error 0x%02x", domain, ret);
+ goto err;
+ }
+ if (!(entry = ldap_first_entry (lh, msg)))
+ {
+ debug_printf ("No ROOTDSE entry for %W", domain);
+ goto err;
+ }
+ if (!(val = ldap_get_valuesW (lh, entry, rootdse_attr[0])))
+ {
+ debug_printf ("No ROOTDSE value for %W", domain);
+ goto err;
+ }
+ if (!(rootdse = wcsdup (val[0])))
+ {
+ debug_printf ("wcsdup(%W, ROOTDSE) %d", domain, get_errno ());
+ goto err;
+ }
+ ldap_value_freeW (val);
+ if ((val = ldap_get_valuesW (lh, entry, rootdse_attr[1])))
+ {
+ for (ULONG idx = 0; idx < ldap_count_valuesW (val); ++idx)
+ if (!wcscmp (val[idx], LDAP_CAP_ACTIVE_DIRECTORY_OID_W))
+ {
+ isAD = true;
+ break;
+ }
+ }
+ ldap_value_freeW (val);
+ val = NULL;
+ ldap_memfreeW ((PWCHAR) msg);
+ msg = entry = NULL; return true;
+err:
+ close ();
+ return false;
+}
+
+void
+cyg_ldap::close ()
+{
+ if (lh)
+ ldap_unbind (lh);
+ if (msg)
+ ldap_memfreeW ((PWCHAR) msg);
+ if (val)
+ ldap_value_freeW (val);
+ if (rootdse)
+ free (rootdse);
+ lh = NULL;
+ msg = entry = NULL;
+ val = NULL;
+ rootdse = NULL;
+}
+
+bool
+cyg_ldap::fetch_ad_account (PSID sid, bool group)
+{
+ WCHAR filter[512], *f;
+ LONG len = (LONG) RtlLengthSid (sid);
+ PBYTE s = (PBYTE) sid;
+ static WCHAR hex_wchars[] = L"0123456789abcdef";
+ ULONG ret;
+
+ if (msg)
+ {
+ ldap_memfreeW ((PWCHAR) msg);
+ msg = entry = NULL;
+ }
+ if (val)
+ {
+ ldap_value_freeW (val);
+ val = NULL;
+ }
+ f = wcpcpy (filter, L"(objectSid=");
+ while (len-- > 0)
+ {
+ *f++ = L'\\';
+ *f++ = hex_wchars[*s >> 4];
+ *f++ = hex_wchars[*s++ & 0xf];
+ }
+ wcpcpy (f, L")");
+ attr = group ? group_attr : user_attr;
+ if ((ret = ldap_search_stW (lh, rootdse, LDAP_SCOPE_SUBTREE, filter,
+ attr, 0, &tv, &msg)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_search_stW(%W,%W) error 0x%02x",
+ rootdse, filter, ret);
+ return false;
+ }
+ if (!(entry = ldap_first_entry (lh, msg)))
+ {
+ debug_printf ("No entry for %W in rootdse %W", filter, rootdse);
+ return false;
+ }
+ return true;
+}
+
+uint32_t
+cyg_ldap::fetch_posix_offset_for_domain (PCWSTR domain)
+{
+ WCHAR filter[512];
+ ULONG ret;
+
+ if (msg)
+ {
+ ldap_memfreeW ((PWCHAR) msg);
+ msg = entry = NULL;
+ }
+ if (val)
+ {
+ ldap_value_freeW (val);
+ val = NULL;
+ }
+ __small_swprintf (filter, L"(&(objectClass=trustedDomain)(name=%W))", domain);
+ if ((ret = ldap_search_stW (lh, rootdse, LDAP_SCOPE_SUBTREE, filter,
+ attr = tdom_attr, 0, &tv, &msg)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_search_stW(%W,%W) error 0x%02x",
+ rootdse, filter, ret);
+ return 0;
+ }
+ if (!(entry = ldap_first_entry (lh, msg)))
+ {
+ debug_printf ("No entry for %W in rootdse %W", filter, rootdse);
+ return 0;
+ }
+ return get_num_attribute (0);
+}
+
+PWCHAR
+cyg_ldap::get_string_attribute (int idx)
+{
+ if (val)
+ ldap_value_freeW (val);
+ val = ldap_get_valuesW (lh, entry, attr[idx]);
+ if (val)
+ return val[0];
+ return NULL;
+}
+
+uint32_t
+cyg_ldap::get_num_attribute (int idx)
+{
+ PWCHAR ret = get_string_attribute (idx);
+ if (ret)
+ return (uint32_t) wcstoul (ret, NULL, 10);
+ return (uint32_t) -1;
+}
+
+bool
+cyg_ldap::fetch_unix_sid_from_ad (uint32_t id, cygsid &sid, bool group)
+{
+ WCHAR filter[512];
+ ULONG ret;
+ PLDAP_BERVAL *bval;
+
+ if (msg)
+ {
+ ldap_memfreeW ((PWCHAR) msg);
+ msg = entry = NULL;
+ }
+ if (group)
+ __small_swprintf (filter, L"(&(objectClass=Group)(gidNumber=%u))", id);
+ else
+ __small_swprintf (filter, L"(&(objectClass=User)(uidNumber=%u))", id);
+ if ((ret = ldap_search_stW (lh, rootdse, LDAP_SCOPE_SUBTREE, filter,
+ nfs_attr, 0, &tv, &msg)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_search_stW(%W,%W) error 0x%02x",
+ rootdse, filter, ret);
+ return false;
+ }
+ if ((entry = ldap_first_entry (lh, msg))
+ && (bval = ldap_get_values_lenW (lh, entry, nfs_attr[0])))
+ {
+ sid = (PSID) bval[0]->bv_val;
+ ldap_value_free_len (bval);
+ }
+ return true;
+}
+
+PWCHAR
+cyg_ldap::fetch_unix_name_from_rfc2307 (uint32_t id, bool group)
+{
+ WCHAR filter[512];
+ ULONG ret;
+
+ if (msg)
+ {
+ ldap_memfreeW ((PWCHAR) msg);
+ msg = entry = NULL;
+ }
+ if (val)
+ {
+ ldap_value_freeW (val);
+ val = NULL;
+ }
+ attr = group ? rfc2307_gid_attr : rfc2307_uid_attr;
+ if (group)
+ __small_swprintf (filter, L"(&(objectClass=posixGroup)(gidNumber=%u))", id);
+ else
+ __small_swprintf (filter, L"(&(objectClass=posixAccount)(uidNumber=%u))",
+ id);
+ if ((ret = ldap_search_stW (lh, rootdse, LDAP_SCOPE_SUBTREE, filter, attr,
+ 0, &tv, &msg)) != LDAP_SUCCESS)
+ {
+ debug_printf ("ldap_search_stW(%W,%W) error 0x%02x",
+ rootdse, filter, ret);
+ return NULL;
+ }
+ if (!(entry = ldap_first_entry (lh, msg)))
+ {
+ debug_printf ("No entry for %W in rootdse %W", filter, rootdse);
+ return NULL;
+ }
+ return get_string_attribute (0);
+}
+
+uid_t
+cyg_ldap::remap_uid (uid_t uid)
+{
+ cygsid user (NO_SID);
+ PWCHAR name;
+ struct passwd *pw;
+
+ if (isAD)
+ {
+ if (fetch_unix_sid_from_ad (uid, user, false)
+ && user != NO_SID
+ && (pw = internal_getpwsid (user)))
+ return pw->pw_uid;
+ }
+ else if ((name = fetch_unix_name_from_rfc2307 (uid, false)))
+ {
+ char *mbname = NULL;
+ sys_wcstombs_alloc (&mbname, HEAP_NOTHEAP, name);
+ if ((pw = internal_getpwnam (mbname)))
+ return pw->pw_uid;
+ }
+ return ILLEGAL_UID;
+}
+
+gid_t
+cyg_ldap::remap_gid (gid_t gid)
+{
+ cygsid group (NO_SID);
+ PWCHAR name;
+ struct group *gr;
+
+ if (isAD)
+ {
+ if (fetch_unix_sid_from_ad (gid, group, true)
+ && group != NO_SID
+ && (gr = internal_getgrsid (group)))
+ return gr->gr_gid;
+ }
+ else if ((name = fetch_unix_name_from_rfc2307 (gid, true)))
+ {
+ char *mbname = NULL;
+ sys_wcstombs_alloc (&mbname, HEAP_NOTHEAP, name);
+ if ((gr = internal_getgrnam (mbname)))
+ return gr->gr_gid;
+ }
+ return ILLEGAL_GID;
+}