Cygwin: (mostly) drop NT4 and Samba < 3.0 support
[newlib-cygwin.git] / winsup / cygwin / sec / auth.cc
blob43b58038936e26d4491dc63564ff63f53f7d17ce
1 /* sec/auth.cc: NT authentication functions
3 This file is part of Cygwin.
5 This software is a copyrighted work licensed under the terms of the
6 Cygwin license. Please consult the file "CYGWIN_LICENSE" for
7 details. */
9 #include "winsup.h"
10 #include <stdlib.h>
11 #include <wchar.h>
12 #include <wininet.h>
13 #include <ntsecapi.h>
14 #include "cygerrno.h"
15 #include "security.h"
16 #include "path.h"
17 #include "fhandler.h"
18 #include "dtable.h"
19 #include "cygheap.h"
20 #include "registry.h"
21 #include "ntdll.h"
22 #include "tls_pbuf.h"
23 #include <lm.h>
24 #include <iptypes.h>
25 #include <userenv.h>
26 #define SECURITY_WIN32
27 #include <secext.h>
28 #include "cygserver_setpwd.h"
29 #include <cygwin/version.h>
31 /* OpenBSD 2.0 and later. */
32 extern "C"
33 int
34 issetugid (void)
36 return cygheap->user.issetuid () ? 1 : 0;
39 /* The token returned by system functions is a restricted token. The full
40 admin token is linked to it and can be fetched with NtQueryInformationToken.
41 This function returns the elevated token if available, the original token
42 otherwise. The token handle is also made inheritable since that's necessary
43 anyway. */
44 static HANDLE
45 get_full_privileged_inheritable_token (HANDLE token)
47 TOKEN_LINKED_TOKEN linked;
48 ULONG size;
50 /* When fetching the linked token without TCB privs, then the linked
51 token is not a primary token, only an impersonation token, which is
52 not suitable for CreateProcessAsUser. Converting it to a primary
53 token using DuplicateTokenEx does NOT work for the linked token in
54 this case. So we have to switch on TCB privs to get a primary token.
55 This is generally performed in the calling functions. */
56 if (NT_SUCCESS (NtQueryInformationToken (token, TokenLinkedToken,
57 (PVOID) &linked, sizeof linked,
58 &size)))
60 debug_printf ("Linked Token: %p", linked.LinkedToken);
61 if (linked.LinkedToken)
63 TOKEN_TYPE type;
65 /* At this point we don't know if the user actually had TCB
66 privileges. Check if the linked token is a primary token.
67 If not, just return the original token. */
68 if (NT_SUCCESS (NtQueryInformationToken (linked.LinkedToken,
69 TokenType, (PVOID) &type,
70 sizeof type, &size))
71 && type != TokenPrimary)
72 debug_printf ("Linked Token is not a primary token!");
73 else
75 CloseHandle (token);
76 token = linked.LinkedToken;
80 if (!SetHandleInformation (token, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
82 __seterrno ();
83 CloseHandle (token);
84 token = NULL;
86 return token;
89 void
90 set_imp_token (HANDLE token, int type)
92 debug_printf ("set_imp_token (%p, %d)", token, type);
93 cygheap->user.external_token = (token == INVALID_HANDLE_VALUE
94 ? NO_IMPERSONATION : token);
95 cygheap->user.ext_token_is_restricted = (type == CW_TOKEN_RESTRICTED);
98 extern "C" void
99 cygwin_set_impersonation_token (const HANDLE hToken)
101 set_imp_token (hToken, CW_TOKEN_IMPERSONATION);
104 void
105 extract_nt_dom_user (const struct passwd *pw, PWCHAR domain, PWCHAR user)
108 cygsid psid;
109 DWORD ulen = UNLEN + 1;
110 DWORD dlen = MAX_DOMAIN_NAME_LEN + 1;
111 SID_NAME_USE use;
113 debug_printf ("pw_gecos %p (%s)", pw->pw_gecos, pw->pw_gecos);
115 /* The incoming passwd entry is not necessarily a pointer to the
116 internal passwd buffers, thus we must not rely on being able to
117 cast it to pg_pwd. */
118 if (psid.getfrompw_gecos (pw)
119 && LookupAccountSidW (NULL, psid, user, &ulen, domain, &dlen, &use))
120 return;
122 char *d, *u, *c;
123 domain[0] = L'\0';
124 sys_mbstowcs (user, UNLEN + 1, pw->pw_name);
125 if ((d = strstr (pw->pw_gecos, "U-")) != NULL &&
126 (d == pw->pw_gecos || d[-1] == ','))
128 c = strchrnul (d + 2, ',');
129 if ((u = strchrnul (d + 2, '\\')) >= c)
130 u = d + 1;
131 else if (u - d <= MAX_DOMAIN_NAME_LEN + 2)
132 sys_mbstowcs (domain, MAX_DOMAIN_NAME_LEN + 1, d + 2, u - d - 1);
133 if (c - u <= UNLEN + 1)
134 sys_mbstowcs (user, UNLEN + 1, u + 1, c - u);
138 extern "C" HANDLE
139 cygwin_logon_user (const struct passwd *pw, const char *password)
141 if (!pw || !password)
143 set_errno (EINVAL);
144 return INVALID_HANDLE_VALUE;
147 WCHAR nt_domain[MAX_DOMAIN_NAME_LEN + 1];
148 WCHAR nt_user[UNLEN + 1];
149 PWCHAR passwd;
150 HANDLE hToken;
151 tmp_pathbuf tp;
153 extract_nt_dom_user (pw, nt_domain, nt_user);
154 debug_printf ("LogonUserW (%W, %W, ...)", nt_user, nt_domain);
155 sys_mbstowcs (passwd = tp.w_get (), NT_MAX_PATH, password);
156 /* CV 2005-06-08: LogonUser should run under the primary process token,
157 otherwise it returns with ERROR_ACCESS_DENIED. */
158 cygheap->user.deimpersonate ();
159 if (!LogonUserW (nt_user, *nt_domain ? nt_domain : NULL, passwd,
160 LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
161 &hToken))
163 __seterrno ();
164 hToken = INVALID_HANDLE_VALUE;
166 else
168 HANDLE hPrivToken = NULL;
170 /* See the comment in get_full_privileged_inheritable_token for a
171 description why we enable TCB privileges here. */
172 push_self_privilege (SE_TCB_PRIVILEGE, true);
173 hPrivToken = get_full_privileged_inheritable_token (hToken);
174 pop_self_privilege ();
175 if (!hPrivToken)
176 debug_printf ("Can't fetch linked token (%E), use standard token");
177 else
178 hToken = hPrivToken;
180 RtlSecureZeroMemory (passwd, NT_MAX_PATH);
181 cygheap->user.reimpersonate ();
182 debug_printf ("%R = logon_user(%s,...)", hToken, pw->pw_name);
183 return hToken;
186 /* The buffer path points to should be at least MAX_PATH bytes. */
187 PWCHAR
188 get_user_profile_directory (PCWSTR sidstr, PWCHAR path, SIZE_T path_len)
190 if (!sidstr || !path)
191 return NULL;
193 UNICODE_STRING buf;
194 tmp_pathbuf tp;
195 tp.u_get (&buf);
196 NTSTATUS status;
198 RTL_QUERY_REGISTRY_TABLE tab[2] = {
199 { NULL, RTL_QUERY_REGISTRY_NOEXPAND | RTL_QUERY_REGISTRY_DIRECT
200 | RTL_QUERY_REGISTRY_REQUIRED,
201 L"ProfileImagePath", &buf, REG_NONE, NULL, 0 },
202 { NULL, 0, NULL, NULL, 0, NULL, 0 }
205 WCHAR key[wcslen (sidstr) + 16];
206 wcpcpy (wcpcpy (key, L"ProfileList\\"), sidstr);
207 status = RtlQueryRegistryValues (RTL_REGISTRY_WINDOWS_NT, key, tab,
208 NULL, NULL);
209 if (!NT_SUCCESS (status) || buf.Length == 0)
211 debug_printf ("ProfileImagePath for %W not found, status %y", sidstr,
212 status);
213 return NULL;
215 ExpandEnvironmentStringsW (buf.Buffer, path, path_len);
216 debug_printf ("ProfileImagePath for %W: %W", sidstr, path);
217 return path;
220 /* Load user profile if it's not already loaded. If the user profile doesn't
221 exist on the machine try to create it.
223 Return a handle to the loaded user registry hive only if it got actually
224 loaded here, not if it already existed. There's no reliable way to know
225 when to unload the hive yet, so we're leaking this registry handle for now.
226 TODO: Try to find a way to reliably unload the user profile again. */
227 HANDLE
228 load_user_profile (HANDLE token, struct passwd *pw, cygpsid &usersid)
230 WCHAR domain[DNLEN + 1];
231 WCHAR username[UNLEN + 1];
232 WCHAR sid[128];
233 WCHAR userpath[MAX_PATH];
234 PROFILEINFOW pi;
236 /* Initialize */
237 if (!cygheap->dom.init ())
238 return NULL;
240 extract_nt_dom_user (pw, domain, username);
241 usersid.string (sid);
242 debug_printf ("user: <%W> <%W> <%W>", username, domain, sid);
243 /* Check if the local profile dir has already been created. */
244 if (!get_user_profile_directory (sid, userpath, MAX_PATH))
246 /* No, try to create it. */
247 HRESULT res = CreateProfile (sid, username, userpath, MAX_PATH);
248 if (res != S_OK)
250 debug_printf ("CreateProfile, HRESULT %x", res);
251 return NULL;
254 /* Fill PROFILEINFO */
255 memset (&pi, 0, sizeof pi);
256 pi.dwSize = sizeof pi;
257 pi.dwFlags = PI_NOUI;
258 pi.lpUserName = username;
259 /* Check if user has a roaming profile and fill in lpProfilePath, if so.
260 Call NetUserGetInfo only for local machine accounts, use LDAP otherwise. */
261 if (!wcscasecmp (domain, cygheap->dom.account_flat_name ()))
263 NET_API_STATUS nas;
264 PUSER_INFO_3 ui;
266 nas = NetUserGetInfo (NULL, username, 3, (PBYTE *) &ui);
267 if (nas != NERR_Success)
268 debug_printf ("NetUserGetInfo, %u", nas);
269 else
271 if (ui->usri3_profile && *ui->usri3_profile)
273 wcsncpy (userpath, ui->usri3_profile, MAX_PATH - 1);
274 userpath[MAX_PATH - 1] = L'\0';
275 pi.lpProfilePath = userpath;
277 NetApiBufferFree (ui);
280 else
282 cyg_ldap cldap;
283 PCWSTR dnsdomain = NULL;
285 if (wcscasecmp (domain, cygheap->dom.primary_flat_name ()))
287 PDS_DOMAIN_TRUSTSW td = NULL;
289 for (ULONG idx = 0; (td = cygheap->dom.trusted_domain (idx)); ++idx)
290 if (!wcscasecmp (domain, td->NetbiosDomainName))
292 dnsdomain = td->DnsDomainName;
293 break;
296 if (cldap.fetch_ad_account (usersid, false, dnsdomain))
298 PWCHAR val = cldap.get_profile_path ();
299 if (val && *val)
301 wcsncpy (userpath, val, MAX_PATH - 1);
302 userpath[MAX_PATH - 1] = L'\0';
303 pi.lpProfilePath = userpath;
308 if (!LoadUserProfileW (token, &pi))
309 debug_printf ("LoadUserProfileW, %E");
310 return pi.hProfile;
313 HANDLE
314 lsa_open_policy (PWCHAR server, ACCESS_MASK access)
316 LSA_UNICODE_STRING srvbuf;
317 PLSA_UNICODE_STRING srv = NULL;
318 static LSA_OBJECT_ATTRIBUTES oa = { 0, 0, 0, 0, 0, 0 };
319 HANDLE lsa;
321 if (server)
323 srv = &srvbuf;
324 RtlInitUnicodeString (srv, server);
326 NTSTATUS status = LsaOpenPolicy (srv, &oa, access, &lsa);
327 if (!NT_SUCCESS (status))
329 __seterrno_from_nt_status (status);
330 lsa = NULL;
332 return lsa;
335 void
336 lsa_close_policy (HANDLE lsa)
338 if (lsa)
339 LsaClose (lsa);
342 bool
343 get_logon_server (PCWSTR domain, PWCHAR server, ULONG flags)
345 DWORD ret;
346 PDOMAIN_CONTROLLER_INFOW pci;
348 /* Empty domain is interpreted as local system */
349 if (cygheap->dom.init ()
350 && (!domain[0]
351 || !wcscasecmp (domain, cygheap->dom.account_flat_name ())))
353 wcpcpy (wcpcpy (server, L"\\\\"), cygheap->dom.account_flat_name ());
354 return true;
357 /* Try to get any available domain controller for this domain */
358 ret = DsGetDcNameW (NULL, domain, NULL, NULL, flags, &pci);
359 if (ret == ERROR_SUCCESS)
361 wcscpy (server, pci->DomainControllerName);
362 NetApiBufferFree (pci);
363 debug_printf ("DC: server: %W", server);
364 return true;
366 __seterrno_from_win_error (ret);
367 return false;
370 static bool
371 sid_in_token_groups (PTOKEN_GROUPS grps, cygpsid sid)
373 if (!grps)
374 return false;
375 for (DWORD i = 0; i < grps->GroupCount; ++i)
376 if (sid == grps->Groups[i].Sid)
377 return true;
378 return false;
381 bool
382 get_server_groups (cygsidlist &grp_list, PSID usersid,
383 acct_disabled_chk_t check_account_disabled)
385 WCHAR user[UNLEN + 1];
386 WCHAR domain[MAX_DOMAIN_NAME_LEN + 1];
387 DWORD ulen = UNLEN + 1;
388 DWORD dlen = MAX_DOMAIN_NAME_LEN + 1;
389 SID_NAME_USE use;
391 if (well_known_system_sid == usersid)
393 grp_list *= well_known_admins_sid;
394 return true;
397 if (!LookupAccountSidW (NULL, usersid, user, &ulen, domain, &dlen, &use))
399 __seterrno ();
400 return false;
402 /* If the SID does NOT start with S-1-5-21, the domain is some builtin
403 domain. We don't fetch a group list then. */
404 if (sid_id_auth (usersid) == 5 /* SECURITY_NT_AUTHORITY */
405 && sid_sub_auth (usersid, 0) == SECURITY_NT_NON_UNIQUE)
407 tmp_pathbuf tp;
408 HANDLE token;
409 NTSTATUS status;
410 PTOKEN_GROUPS groups;
411 ULONG size;
413 token = s4uauth (false, domain, user, status);
414 if (!token)
415 return false;
417 groups = (PTOKEN_GROUPS) tp.w_get ();
418 status = NtQueryInformationToken (token, TokenGroups, groups,
419 2 * NT_MAX_PATH, &size);
420 if (NT_SUCCESS (status))
421 for (DWORD pg = 0; pg < groups->GroupCount; ++pg)
423 if (groups->Groups[pg].Attributes & SE_GROUP_USE_FOR_DENY_ONLY)
424 continue;
425 cygpsid grpsid = groups->Groups[pg].Sid;
426 if (sid_id_auth (grpsid) == 5 /* SECURITY_NT_AUTHORITY */
427 && sid_sub_auth (grpsid, 0) == SECURITY_NT_NON_UNIQUE)
428 grp_list += grpsid;
429 else
430 grp_list *= grpsid;
432 NtClose (token);
434 return true;
437 /* Accept a token if
438 - the requested usersid matches the TokenUser and
439 - if setgroups has been called:
440 the token groups that are listed in /etc/group match the union of
441 the requested primary and supplementary groups in gsids.
442 - else the (unknown) implicitly requested supplementary groups and those
443 in the token are the groups associated with the usersid. We assume
444 they match and verify only the primary groups.
445 The requested primary group must appear in the token.
446 The primary group in the token is a group associated with the usersid,
447 except if the token is internal and the group is in the token SD. In
448 that latter case that group must match the requested primary group. */
449 bool
450 verify_token (HANDLE token, cygsid &usersid, user_groups &groups, bool *pintern)
452 NTSTATUS status;
453 ULONG size;
454 bool intern = false;
455 tmp_pathbuf tp;
457 if (pintern)
459 TOKEN_SOURCE ts;
460 status = NtQueryInformationToken (token, TokenSource, &ts, sizeof ts,
461 &size);
462 if (!NT_SUCCESS (status))
463 debug_printf ("NtQueryInformationToken(), %y", status);
464 else
465 *pintern = intern = !memcmp (ts.SourceName, "Cygwin.1", 8);
467 /* Verify usersid */
468 cygsid tok_usersid (NO_SID);
469 status = NtQueryInformationToken (token, TokenUser, &tok_usersid,
470 sizeof tok_usersid, &size);
471 if (!NT_SUCCESS (status))
472 debug_printf ("NtQueryInformationToken(), %y", status);
473 if (usersid != tok_usersid)
474 return false;
476 /* For an internal token, if setgroups was not called and if the sd group
477 is not well_known_null_sid, it must match pgrpsid */
478 if (intern && !groups.issetgroups ())
480 const DWORD sd_buf_siz = SECURITY_MAX_SID_SIZE
481 + sizeof (SECURITY_DESCRIPTOR);
482 PSECURITY_DESCRIPTOR sd_buf = (PSECURITY_DESCRIPTOR) alloca (sd_buf_siz);
483 cygpsid gsid (NO_SID);
484 NTSTATUS status;
485 status = NtQuerySecurityObject (token, GROUP_SECURITY_INFORMATION,
486 sd_buf, sd_buf_siz, &size);
487 if (!NT_SUCCESS (status))
488 debug_printf ("NtQuerySecurityObject(), %y", status);
489 else
491 BOOLEAN dummy;
492 status = RtlGetGroupSecurityDescriptor (sd_buf, (PSID *) &gsid,
493 &dummy);
494 if (!NT_SUCCESS (status))
495 debug_printf ("RtlGetGroupSecurityDescriptor(), %y", status);
497 if (well_known_null_sid != gsid)
498 return gsid == groups.pgsid;
501 PTOKEN_GROUPS my_grps = (PTOKEN_GROUPS) tp.w_get ();
503 status = NtQueryInformationToken (token, TokenGroups, my_grps,
504 2 * NT_MAX_PATH, &size);
505 if (!NT_SUCCESS (status))
507 debug_printf ("NtQueryInformationToken(my_token, TokenGroups), %y",
508 status);
509 return false;
512 bool sawpg = false;
514 if (groups.issetgroups ()) /* setgroups was called */
516 cygpsid gsid;
517 bool saw[groups.sgsids.count ()];
519 /* Check that all groups in the setgroups () list are in the token.
520 A token created through ADVAPI should be allowed to contain more
521 groups than requested through setgroups(), especially since the
522 addition of integrity groups. */
523 memset (saw, 0, sizeof(saw));
524 for (int gidx = 0; gidx < groups.sgsids.count (); gidx++)
526 gsid = groups.sgsids.sids[gidx];
527 if (sid_in_token_groups (my_grps, gsid))
529 int pos = groups.sgsids.position (gsid);
530 if (pos >= 0)
531 saw[pos] = true;
532 else if (groups.pgsid == gsid)
533 sawpg = true;
536 /* user.sgsids groups must be in the token, except for builtin groups.
537 These can be different on domain member machines compared to
538 domain controllers, so these builtin groups may be validly missing
539 from a token created through password or lsaauth logon. */
540 for (int gidx = 0; gidx < groups.sgsids.count (); gidx++)
541 if (!saw[gidx]
542 && !groups.sgsids.sids[gidx].is_well_known_sid ()
543 && !sid_in_token_groups (my_grps, groups.sgsids.sids[gidx]))
544 return false;
546 /* The primary group must be in the token */
547 return sawpg
548 || sid_in_token_groups (my_grps, groups.pgsid)
549 || groups.pgsid == usersid;
552 const char *
553 account_restriction (NTSTATUS status)
555 const char *type;
557 switch (status)
559 case STATUS_INVALID_LOGON_HOURS:
560 type = "Logon outside allowed hours";
561 break;
562 case STATUS_INVALID_WORKSTATION:
563 type = "Logon at this machine not allowed";
564 break;
565 case STATUS_PASSWORD_EXPIRED:
566 type = "Password expired";
567 break;
568 case STATUS_ACCOUNT_DISABLED:
569 type = "Account disabled";
570 break;
571 default:
572 type = "Unknown";
573 break;
575 return type;
578 #define SFU_LSA_KEY_SUFFIX L"_microsoft_sfu_utility"
580 HANDLE
581 lsaprivkeyauth (struct passwd *pw)
583 NTSTATUS status;
584 HANDLE lsa = NULL;
585 HANDLE token = NULL;
586 WCHAR sid[256];
587 WCHAR domain[MAX_DOMAIN_NAME_LEN + 1];
588 WCHAR user[UNLEN + 1];
589 WCHAR key_name[MAX_DOMAIN_NAME_LEN + UNLEN + wcslen (SFU_LSA_KEY_SUFFIX) + 2];
590 UNICODE_STRING key;
591 PUNICODE_STRING data = NULL;
592 cygsid psid;
593 BOOL ret;
595 push_self_privilege (SE_TCB_PRIVILEGE, true);
597 /* Open policy object. */
598 if (!(lsa = lsa_open_policy (NULL, POLICY_GET_PRIVATE_INFORMATION)))
599 goto out;
601 /* Needed for Interix key and LogonUser. */
602 extract_nt_dom_user (pw, domain, user);
604 /* First test for a Cygwin entry. */
605 if (psid.getfrompw (pw) && psid.string (sid))
607 wcpcpy (wcpcpy (key_name, CYGWIN_LSA_KEY_PREFIX), sid);
608 RtlInitUnicodeString (&key, key_name);
609 status = LsaRetrievePrivateData (lsa, &key, &data);
610 if (!NT_SUCCESS (status))
611 data = NULL;
613 /* No Cygwin key, try Interix key. */
614 if (!data && *domain)
616 __small_swprintf (key_name, L"%W_%W%W",
617 domain, user, SFU_LSA_KEY_SUFFIX);
618 RtlInitUnicodeString (&key, key_name);
619 status = LsaRetrievePrivateData (lsa, &key, &data);
620 if (!NT_SUCCESS (status))
621 data = NULL;
623 /* Found an entry? Try to logon. */
624 if (data)
626 /* The key is not 0-terminated. */
627 PWCHAR passwd;
628 size_t pwdsize = data->Length + sizeof (WCHAR);
630 passwd = (PWCHAR) alloca (pwdsize);
631 *wcpncpy (passwd, data->Buffer, data->Length / sizeof (WCHAR)) = L'\0';
632 /* Weird: LsaFreeMemory invalidates the content of the UNICODE_STRING
633 structure, but it does not invalidate the Buffer content. */
634 RtlSecureZeroMemory (data->Buffer, data->Length);
635 LsaFreeMemory (data);
636 debug_printf ("Try logon for %W\\%W", domain, user);
637 ret = LogonUserW (user, domain, passwd, LOGON32_LOGON_INTERACTIVE,
638 LOGON32_PROVIDER_DEFAULT, &token);
639 RtlSecureZeroMemory (passwd, pwdsize);
640 if (!ret)
642 __seterrno ();
643 token = NULL;
645 else
646 token = get_full_privileged_inheritable_token (token);
648 lsa_close_policy (lsa);
650 out:
651 pop_self_privilege ();
652 return token;
655 /* The following code is inspired by the generate_s4u_user_token
656 and lookup_principal_name functions from
657 https://github.com/PowerShell/openssh-portable
659 Thanks guys! For courtesy here's the original copyright disclaimer: */
662 * Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
663 * Utilities to generate user tokens
665 * Author: Bryan Berns <berns@uwalumni.com>
666 * Updated s4u, logon, and profile loading routines to use
667 * normalized login names.
669 * Copyright (c) 2015 Microsoft Corp.
670 * All rights reserved
672 * Microsoft openssh win32 port
674 * Redistribution and use in source and binary forms, with or without
675 * modification, are permitted provided that the following conditions
676 * are met:
678 * 1. Redistributions of source code must retain the above copyright
679 * notice, this list of conditions and the following disclaimer.
680 * 2. Redistributions in binary form must reproduce the above copyright
681 * notice, this list of conditions and the following disclaimer in the
682 * documentation and/or other materials provided with the distribution.
684 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
685 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
686 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
687 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
688 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
689 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
690 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
691 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
692 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
693 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
696 /* In w32api prior to 10.0.0, MsV1_0S4ULogon and MSV1_0_S4U_LOGON are only
697 defined in ddk/ntifs.h, which we can't include. */
698 #if (__MINGW64_VERSION_MAJOR < 10)
700 #define MsV1_0S4ULogon ((MSV1_0_LOGON_SUBMIT_TYPE) 12)
702 typedef struct _MSV1_0_S4U_LOGON
704 MSV1_0_LOGON_SUBMIT_TYPE MessageType;
705 ULONG Flags;
706 UNICODE_STRING UserPrincipalName;
707 UNICODE_STRING DomainName;
708 } MSV1_0_S4U_LOGON, *PMSV1_0_S4U_LOGON;
710 /* Missing in Mingw-w64 */
711 #define KERB_S4U_LOGON_FLAG_IDENTIFY 0x08
713 #endif
715 /* If logon is true we need an impersonation token. Otherwise we just
716 need an identification token, e. g. to fetch the group list. */
717 HANDLE
718 s4uauth (bool logon, PCWSTR domain, PCWSTR user, NTSTATUS &ret_status)
720 LSA_STRING name;
721 HANDLE lsa_hdl = NULL;
722 LSA_OPERATIONAL_MODE sec_mode;
723 NTSTATUS status, sub_status;
724 bool kerberos_auth;
725 ULONG package_id, size;
726 struct {
727 LSA_STRING str;
728 CHAR buf[16];
729 } origin;
731 tmp_pathbuf tp;
732 PVOID authinf = NULL;
733 ULONG authinf_size;
734 TOKEN_SOURCE ts;
735 PKERB_INTERACTIVE_PROFILE profile = NULL;
736 LUID luid;
737 QUOTA_LIMITS quota;
738 HANDLE token = NULL;
740 /* Initialize */
741 if (!cygheap->dom.init ())
742 return NULL;
744 push_self_privilege (SE_TCB_PRIVILEGE, true);
746 if (logon)
748 /* Register as logon process. */
749 debug_printf ("Impersonation requested");
750 RtlInitAnsiString (&name, "Cygwin");
751 status = LsaRegisterLogonProcess (&name, &lsa_hdl, &sec_mode);
753 else
755 /* Connect untrusted to just create a identification token */
756 debug_printf ("Identification requested");
757 status = LsaConnectUntrusted (&lsa_hdl);
759 if (status != STATUS_SUCCESS)
761 debug_printf ("%s: %y", logon ? "LsaRegisterLogonProcess"
762 : "LsaConnectUntrusted", status);
763 /* If the privilege is not held, set the proper error code. */
764 if (status == STATUS_PORT_CONNECTION_REFUSED)
765 status = STATUS_PRIVILEGE_NOT_HELD;
766 __seterrno_from_nt_status (status);
767 goto out;
770 /* Check if this is a domain user. If so, use Kerberos. */
771 kerberos_auth = cygheap->dom.member_machine ()
772 && wcscasecmp (domain, cygheap->dom.account_flat_name ());
773 debug_printf ("kerb %d, domain member %d, user domain <%W>, machine <%W>",
774 kerberos_auth, cygheap->dom.member_machine (), domain,
775 cygheap->dom.account_flat_name ());
777 /* Connect to authentication package. */
778 RtlInitAnsiString (&name, kerberos_auth ? MICROSOFT_KERBEROS_NAME_A
779 : MSV1_0_PACKAGE_NAME);
780 status = LsaLookupAuthenticationPackage (lsa_hdl, &name, &package_id);
781 if (status != STATUS_SUCCESS)
783 debug_printf ("LsaLookupAuthenticationPackage: %y", status);
784 __seterrno_from_nt_status (status);
785 goto out;
788 /* Create origin. */
789 stpcpy (origin.buf, "Cygwin");
790 RtlInitAnsiString (&origin.str, origin.buf);
792 /* Create token source. */
793 memcpy (ts.SourceName, "Cygwin.1", 8);
794 ts.SourceIdentifier.HighPart = 0;
795 ts.SourceIdentifier.LowPart = kerberos_auth ? 0x0105 : 0x0106;
797 if (kerberos_auth)
799 PWCHAR sam_name = tp.w_get ();
800 PWCHAR upn_name = tp.w_get ();
801 size = NT_MAX_PATH;
802 KERB_S4U_LOGON *s4u_logon;
803 USHORT name_len;
805 wcpcpy (wcpcpy (wcpcpy (sam_name, domain), L"\\"), user);
806 if (TranslateNameW (sam_name, NameSamCompatible, NameUserPrincipal,
807 upn_name, &size) == 0)
809 PWCHAR translated_name = tp.w_get ();
810 PWCHAR p;
812 debug_printf ("TranslateNameW(%W, NameUserPrincipal) %E", sam_name);
813 size = NT_MAX_PATH;
814 if (TranslateNameW (sam_name, NameSamCompatible, NameCanonical,
815 translated_name, &size) == 0)
817 debug_printf ("TranslateNameW(%W, NameCanonical) %E", sam_name);
818 goto out;
820 p = wcschr (translated_name, L'/');
821 if (p)
822 *p = '\0';
823 wcpcpy (wcpcpy (wcpcpy (upn_name, user), L"@"), translated_name);
826 name_len = wcslen (upn_name) * sizeof (WCHAR);
827 authinf_size = sizeof (KERB_S4U_LOGON) + name_len;
828 authinf = tp.c_get ();
829 RtlSecureZeroMemory (authinf, authinf_size);
830 s4u_logon = (KERB_S4U_LOGON *) authinf;
831 s4u_logon->MessageType = KerbS4ULogon;
832 s4u_logon->Flags = logon ? 0 : KERB_S4U_LOGON_FLAG_IDENTIFY;
833 /* Append user to login info */
834 RtlInitEmptyUnicodeString (&s4u_logon->ClientUpn,
835 (PWCHAR) (s4u_logon + 1),
836 name_len);
837 RtlAppendUnicodeToString (&s4u_logon->ClientUpn, upn_name);
838 debug_printf ("KerbS4ULogon: ClientUpn: <%S>", &s4u_logon->ClientUpn);
840 else
842 MSV1_0_S4U_LOGON *s4u_logon;
843 USHORT user_len, domain_len;
845 user_len = wcslen (user) * sizeof (WCHAR);
846 domain_len = wcslen (domain) * sizeof (WCHAR); /* Local machine */
847 authinf_size = sizeof (MSV1_0_S4U_LOGON) + user_len + domain_len;
848 if (!authinf)
849 authinf = tp.c_get ();
850 RtlSecureZeroMemory (authinf, authinf_size);
851 s4u_logon = (MSV1_0_S4U_LOGON *) authinf;
852 s4u_logon->MessageType = MsV1_0S4ULogon;
853 s4u_logon->Flags = 0;
854 /* Append user and domain to login info */
855 RtlInitEmptyUnicodeString (&s4u_logon->UserPrincipalName,
856 (PWCHAR) (s4u_logon + 1),
857 user_len);
858 RtlInitEmptyUnicodeString (&s4u_logon->DomainName,
859 (PWCHAR) ((PBYTE) (s4u_logon + 1) + user_len),
860 domain_len);
861 RtlAppendUnicodeToString (&s4u_logon->UserPrincipalName, user);
862 RtlAppendUnicodeToString (&s4u_logon->DomainName, domain);
863 debug_printf ("MsV1_0S4ULogon: DomainName: <%S> UserPrincipalName: <%S>",
864 &s4u_logon->DomainName, &s4u_logon->UserPrincipalName);
867 /* Try to logon. */
868 status = LsaLogonUser (lsa_hdl, (PLSA_STRING) &origin, Network, package_id,
869 authinf, authinf_size, NULL, &ts, (PVOID *) &profile,
870 &size, &luid, &token, &quota, &sub_status);
871 switch (status)
873 case STATUS_SUCCESS:
874 break;
875 case STATUS_ACCOUNT_RESTRICTION:
876 debug_printf ("%s S4U LsaLogonUser failed: %y (%s)",
877 kerberos_auth ? "Kerberos" : "MsV1_0", status,
878 account_restriction (sub_status));
879 break;
880 default:
881 debug_printf ("%s S4U LsaLogonUser failed: %y",
882 kerberos_auth ? "Kerberos" : "MsV1_0", status);
883 break;
886 out:
887 if (lsa_hdl)
888 LsaDeregisterLogonProcess (lsa_hdl);
889 if (profile)
890 LsaFreeReturnBuffer (profile);
892 if (token && logon)
894 /* Convert to primary token. CreateProcessAsUser takes impersonation
895 tokens since Windows 7 but MSDN still claims a primary token is
896 required. Better safe than sorry. */
897 HANDLE tmp_token;
899 if (DuplicateTokenEx (token, MAXIMUM_ALLOWED, &sec_none,
900 SecurityImpersonation, TokenPrimary, &tmp_token))
902 CloseHandle (token);
903 token = tmp_token;
905 else
907 __seterrno ();
908 debug_printf ("DuplicateTokenEx %E");
909 /* Make sure not to allow create_token. */
910 status = STATUS_INVALID_HANDLE;
911 CloseHandle (token);
912 token = NULL;
916 pop_self_privilege ();
917 ret_status = status;
918 return token;