public inbox for cygwin@cygwin.com
 help / color / mirror / Atom feed
* PATCH: login under privileged user != SYSTEM
@ 2008-04-17 12:57 Charles Wilson
  2008-04-17 13:06 ` Corinna Vinschen
  0 siblings, 1 reply; 6+ messages in thread
From: Charles Wilson @ 2008-04-17 12:57 UTC (permalink / raw)
  To: Cygwin Mailing List

[-- Attachment #1: Type: text/plain, Size: 2052 bytes --]

I've been trying to get all the bugs in inetutils-1.5 squashed, and I 
ran into an issue with rlogin when rlogind was running under a 
privileged user (that is, not SYSTEM), as is required for Windows Server 
2003, 2008, and Vista.

The problem was, although rsh would honor my .rhosts and allow 
passwordless operation, rlogin would not. It always asked for my password.

Internally, rlogind *knew* that the incoming connection was 
"authenticated" via .rhosts, so it invoked login thus:

login -p -h <incoming hostname> -f -- <username>

where the '-f' SHOULD mean "this is already authenticated, don't ask for 
the password again".  But it wasn't working, because login was hardcoded 
to compare the current uid to 18 (that is, SYSTEM), before allowing 
passwordless auth.  But rlogind/login were not running under SYSTEM.


I don't think you can simply replace the code in login, the way we did 
in many of the servers, tho:

  #ifdef __CYGWIN__
-#define  ROOT_UID    18
+#define  ROOT_UID    getuid()
  #else
  #define  ROOT_UID     0
  #endif

because then you'd allow passwordless auth no matter what account login 
was running under. Now, it might fail later, assuming we added code to 
check whether some future setuid() succeeded or not, but I think that's 
too late in the process.

So, for *login*, I changed the code from
    if (uid == ROOT_UID)
to
    if (is_a_ROOT_UID(uid))

and implemented a function that, depending on the underlying windows 
version, either
   (1) compares to 18
   (2) checks that the account with the specified uid has the following 
privileges:
+        SeAssignPrimaryTokenPrivilege
+        SeCreateTokenPrivilege
+        SeTcbPrivilege
+        SeIncreaseQuotaPrivilege
+        SeServiceLogonRight
(On NT/2k/XP, uid = 18 is an automatic "yes", but if uid != 18, then we 
fall back to the Vista check-privileges procedure)

With these changes, I can now get passwordless rlogin when inetd is 
running under a privileged user, and not SYSTEM.

Most of the code was adapted from editrights/main.c...

--
Chuck

[-- Attachment #2: login-1.9-7.vista.patch --]
[-- Type: text/plain, Size: 18741 bytes --]

diff -urN old/login-1.9-7/Makefile new/login-1.9-7/Makefile
--- old/login-1.9-7/Makefile	2003-08-11 15:01:41.000000000 -0400
+++ new/login-1.9-7/Makefile	2008-04-17 00:30:15.515625000 -0400
@@ -18,8 +18,8 @@
 #
 
 CFLAGS=	-O
-SRCS=	login.c
-OBJS=	login.o
+SRCS=	login.c testWindowsPrivileges.c
+OBJS=	login.o testWindowsPrivileges.o
 MAN=	login.1
 prefix=/usr
 docdir=/usr/share/doc/Cygwin
@@ -27,28 +27,28 @@
 DESTDIR=
 EXEEXT=.exe
 
-all: login${EXEEXT}
+all: login$(EXEEXT)
 
-login${EXEEXT}: login.o
-	${CC} -o $@ ${CFLAGS} $< -lcrypt
+login$(EXEEXT): login.o testWindowsPrivileges.o
+	$(CC) -o $@ $(CFLAGS) $^ -lcrypt
 
 clean:
-	rm -f ${OBJS} core login${EXEEXT}.stackdump login${EXEEXT}
+	rm -f $(OBJS) core login$(EXEEXT).stackdump login$(EXEEXT)
 
 cleandir: clean
-	rm -f ${MAN} tags .depend
+	rm -f $(MAN) tags .depend
 
-depend: ${SRCS}
-	mkdep -p ${CFLAGS} ${SRCS}
+depend: $(SRCS)
+	mkdep -p $(CFLAGS) $(SRCS)
 
-install: login${EXEEXT}
-	-mkdir -p ${DESTDIR}${prefix}/bin ${DESTDIR}${docdir} ${DESTDIR}${mandir}
-	install -s -m 4755 login${EXEEXT} ${DESTDIR}${prefix}/bin/login${EXEEXT}
-	install -m 644 login.README ${DESTDIR}${docdir}/login.README
-	install -m 644 login.1 ${DESTDIR}${mandir}/login.1
+install: login$(EXEEXT)
+	-mkdir -p $(DESTDIR)$(prefix)/bin $(DESTDIR)$(docdir) $(DESTDIR)$(mandir)
+	install -s -m 4755 login$(EXEEXT) $(DESTDIR)$(prefix)/bin/login$(EXEEXT)
+	install -m 644 login.README $(DESTDIR)$(docdir)/login.README
+	install -m 644 login.1 $(DESTDIR)$(mandir)/login.1
 
-lint: ${SRCS}
-	lint ${CFLAGS} ${SRCS}
+lint: $(SRCS)
+	lint $(CFLAGS) $(SRCS)
 
-tags: ${SRCS}
-	ctags ${SRCS}
+tags: $(SRCS)
+	ctags $(SRCS)
diff -urN old/login-1.9-7/login.README new/login-1.9-7/login.README
--- old/login-1.9-7/login.README	2002-08-07 14:14:53.000000000 -0400
+++ new/login-1.9-7/login.README	2008-04-17 01:26:49.968750000 -0400
@@ -8,9 +8,29 @@
 ======================================================================
 
 This version now supports login w/o password, supported beginning with
-Cygwin version 1.3.6.  Note that this is explicitely allowed for the
-SYSTEM user only!  This change now allows interactive login with rlogin(1)
-and rsh(1) using rhosts authentication.
+Cygwin version 1.3.6.  Note that this is explicitely allowed only in 
+limited cases:
+  (1) NT/2k/XP: SYSTEM user, or a specially privileged user (see below)
+  (2) Windows Server 2003, 2008, and Vista: a specially privileged user
+
+The user account that login is invoked by must have the following 
+privileges:
+        SeAssignPrimaryTokenPrivilege
+        SeCreateTokenPrivilege
+        SeTcbPrivilege
+        SeIncreaseQuotaPrivilege
+        SeServiceLogonRight
+On NT/2k/XP, the SYSTEM user (aka LocalSystem) has these privileges.
+However, on newer versions of Windows, it does not, and a special 
+user account must be created. Most of the <server>-config scripts 
+can create this account for you (such as ssh-host-config from the 
+openssh package, or iu-config from the inetutils package, etc).
+
+The user account should also be a member of the local Administrators
+group (unless you are on NT/2k/XP, and are using SYSTEM).
+
+This change now allows interactive login with rlogin(1) and rsh(1) using
+rhosts authentication.
 
 Login(1) uses all new logon features of 1.1.3. Moreover it's code is
 slighty cleaned up to avoid compiler warnings.
@@ -20,7 +40,7 @@
 can use `crypt.exe' from the crypt package on the same site or you
 copy your encrypted password from your Linux box.
 
-The rest of the description is for NT/W2K.
+The rest of the description is for NT/W2K/XP/Vista/...
 
 For usage with NT/W2K security, `login' is patched to allow login of
 domain users.  Setting CYGWIN=ntsec is mandatory for that feature.
diff -urN old/login-1.9-7/login.c new/login-1.9-7/login.c
--- old/login-1.9-7/login.c	2003-08-11 15:01:41.000000000 -0400
+++ new/login-1.9-7/login.c	2008-04-17 01:16:37.343750000 -0400
@@ -71,11 +71,22 @@
 #endif
 
 #ifdef __CYGWIN__
-#define ROOT_UID	18		// system
+static int is_a_ROOT_UID(uid_t uid);
+extern int testUserRightsByUID(uid_t uid, const char** strRightsToTest, ULONG intRightsToTestCount);
 #else
-#define ROOT_UID	0
+#define is_a_ROOT_UID(uid) ((int) ((uid) == 0))
 #endif
 
+static void getloginname();
+static void timedout();
+static void motd();
+static void sigint();
+static void checknologin();
+static void dolastlog(int quiet);
+static void badlogin(char *name);
+static void getstr(char *buf, int cnt, char *err);
+static void sleepexit(int eval);
+
 /*
  * This bounds the time given to login.  Not a define so it can
  * be patched on machines where it's too small.
@@ -101,7 +112,6 @@
 	gid_t priv_gid;
 	uid_t priv_uid;
 #endif
-	void timedout();
 	char *domain, *salt, *envinit[1], *ttyn, *pp;
 	char tbuf[MAXPATHLEN + 2];
 	char *ttyname(), *crypt(), *getpass();
@@ -131,7 +141,7 @@
 			break;
 		case 'h':
 #ifndef __CYGWIN__
-			if (getuid()) {
+			if (!is_a_ROOT_UID(getuid())) {
 				fprintf(stderr,
 				    "login: -h for super-user only.\n");
 				exit(1);
@@ -198,7 +208,7 @@
 		else
 			salt = "xx";
 		/* if user not super-user, check for disabled logins */
-		if (pwd == NULL || pwd->pw_uid)
+		if (pwd == NULL || !is_a_ROOT_UID(pwd->pw_uid))
 			checknologin();
 
 		/*
@@ -208,8 +218,8 @@
 		if (fflag && pwd) {
 			int uid = getuid();
 
-			passwd_req = pwd->pw_uid == ROOT_UID ||
-			    (uid != ROOT_UID && uid != pwd->pw_uid);
+			passwd_req = is_a_ROOT_UID(pwd->pw_uid) ||
+			    ((!is_a_ROOT_UID(uid)) && (uid != pwd->pw_uid));
 		}
 
 		/*
@@ -342,7 +352,7 @@
 
 	if (tty[sizeof("tty")-1] == 'd')
 		syslog(LOG_INFO, "DIALUP %s, %s", tty, pwd->pw_name);
-	if (pwd->pw_uid == 0)
+	if (is_a_ROOT_UID(pwd->pw_uid))
 		if (hostname)
 			syslog(LOG_NOTICE, "ROOT LOGIN ON %s FROM %s",
 			    tty, hostname);
@@ -373,7 +383,7 @@
 	exit(0);
 }
 
-getloginname()
+static void getloginname()
 {
 	register int ch;
 	register char *p;
@@ -401,7 +411,7 @@
 	}
 }
 
-timedout()
+static void timedout()
 {
 	fprintf(stderr, "Login timed out after %d seconds\n", timeout);
 	exit(0);
@@ -409,10 +419,10 @@
 
 jmp_buf motdinterrupt;
 
-motd()
+static void motd()
 {
 	register int fd, nchars;
-	void (*oldint)(), sigint();
+	void (*oldint)();
 	char tbuf[8192];
 
 	if ((fd = open(MOTDFILE, O_RDONLY, 0)) < 0)
@@ -425,12 +435,12 @@
 	(void)close(fd);
 }
 
-sigint()
+static void sigint()
 {
 	longjmp(motdinterrupt, 1);
 }
 
-checknologin()
+static void checknologin()
 {
 	register int fd, nchars;
 	char tbuf[8192];
@@ -442,8 +452,7 @@
 	}
 }
 
-dolastlog(quiet)
-	int quiet;
+static void dolastlog(int quiet)
 {
 	struct lastlog ll;
 	int fd;
@@ -474,8 +483,7 @@
 	}
 }
 
-badlogin(name)
-	char *name;
+static void badlogin(char *name)
 {
 	if (failures == 0)
 		return;
@@ -488,9 +496,7 @@
 }
 
 
-getstr(buf, cnt, err)
-	char *buf, *err;
-	int cnt;
+static void getstr(char *buf, int cnt, char *err)
 {
 	char ch;
 
@@ -505,10 +511,103 @@
 	} while (ch);
 }
 
-sleepexit(eval)
-	int eval;
+static void sleepexit(int eval)
 {
 	sleep((u_int)5);
 	exit(eval);
 }
 
+#ifdef __CYGWIN__
+static int is_a_ROOT_UID(uid_t uid)
+{
+    static const uid_t SYSTEM_UID = 18;
+    static const gid_t ADMIN_GID = 544;
+    static const char* REQUIRED_PRIVS[] = {
+        "SeAssignPrimaryTokenPrivilege",
+        "SeCreateTokenPrivilege",
+        "SeTcbPrivilege",
+        //"SeDenyInteractiveLogonRight",
+        //"SeDenyNetworkLogonRight",
+        //"SeDenyRemoteInteractiveLogonRight",
+        "SeIncreaseQuotaPrivilege",
+        "SeServiceLogonRight"
+    };
+    static const ULONG NUM_REQUIRED_PRIV = 5;
+
+    int check_capabilities = 0;
+    OSVERSIONINFOEX osvi;
+    BOOL bOsVersionInfoEx;
+    struct passwd *pw;
+
+    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+    if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
+        return 0; /* not a root id */
+        
+    if (VER_PLATFORM_WIN32_WINDOWS==osvi.dwPlatformId)
+        return 1; /* everybody is a root user on Win9x */
+
+    if ((VER_PLATFORM_WIN32_NT==osvi.dwPlatformId) &&
+        (osvi.dwMajorVersion == 4))
+    {
+       /* NT kernel, but older than W2k */
+       /* only check for SYSTEM_UID */
+       return (uid == SYSTEM_UID);
+    }
+
+    if ((VER_PLATFORM_WIN32_NT==osvi.dwPlatformId) &&
+        (osvi.dwMajorVersion == 5))
+    {
+        /* Windows Server 2003 R2, Windows Server 2003,
+           Windows XP, or Windows 2000. */
+        if (osvi.dwMinorVersion < 2)
+        {
+            /* 2k or XP; check for SYSTEM_UID */
+            if (uid == SYSTEM_UID)
+                return 1;
+            /* otherwise, need to check capabilities */
+        }
+        check_capabilities = 1;
+    }
+
+    if ((VER_PLATFORM_WIN32_NT==osvi.dwPlatformId) &&
+        (osvi.dwMajorVersion == 6))
+    {
+        /* Vista, Server2008, and above */
+        check_capabilities = 1;
+    }
+
+    if (!check_capabilities)
+    {
+        /* how did we get here? Some unsupported OS -- either 
+           too new, or too old. Just say it's not a root user,
+           which will effectively force the client to re-
+           authenticate */
+        return 0;
+    }
+
+    /* check capabilities */
+    pw = getpwuid(uid);
+
+    /* not in /etc/passwd. say it is not root. */
+    if (!pw)
+        return 0;
+
+    /* Should make sure gid is Administrators (544)
+     * However, by default cyg_server is a member of both
+     * Administrators and Users, and the primary one --
+     * which is easy to get to via struct passwd -- is Users.
+     * So, we really have to crib code from mkgroups, and 
+     * enumerate through all the members of the Administrators
+     * group, then use cygwin_internal(CW_GET_UID_FROM_SID, ...)
+     * and compare see if one of those uid's matches this one.
+     * for now, punt.
+     *
+     *  if (pw->pw_gid != ADMIN_GID)
+     *     return 0;
+     */
+
+    /* returns non-zero if the account DOES have all specified privileges */
+    return testUserRightsByUID(uid, REQUIRED_PRIVS, NUM_REQUIRED_PRIV);
+}
+#endif
diff -urN old/login-1.9-7/testWindowsPrivileges.c new/login-1.9-7/testWindowsPrivileges.c
--- old/login-1.9-7/testWindowsPrivileges.c	1969-12-31 19:00:00.000000000 -0500
+++ new/login-1.9-7/testWindowsPrivileges.c	2008-04-17 00:50:55.171875000 -0400
@@ -0,0 +1,229 @@
+/***************************************************************************
+* testWindowsPrivileges -- adapted from editrights/main.c. Original
+* copyright notice and license text follows.
+*
+* ==========================================================================
+* editrights version 1.01: a cygwin application to edit user
+*                          rights on a windows NT system
+* 
+* Copyright (c) 2003, Chris Rodgers <editrights-at-bulk.rodgers.org.uk>
+* All rights reserved.
+* 
+* Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met:
+* 
+* Redistributions of source code must retain the above copyright notice,
+* this list of conditions and the following disclaimer.
+* 
+* Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* 
+* The name of Chris Rodgers may not be used to endorse or promote
+* products derived from this software without specific prior written
+* permission.
+* 
+* THIS SOFTWARE IS PROVIDED BY CHRIS RODGERS "AS IS" AND ANY EXPRESS
+* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+* ARE DISCLAIMED. IN NO EVENT SHALL CHRIS RODGERS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+* ==========================================================================
+*
+****************************************************************************/
+
+/* Copyright (C) 2007 Jari Aalto; Licenced under GPL v2 or later */
+
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/cygwin.h>
+#include <syslog.h>
+#include <wchar.h>
+#include <windows.h>
+#include <wininet.h>
+#include <lmcons.h>
+#include <ntsecapi.h>
+#include <ddk/ntstatus.h>
+
+/* Macros */
+#define NTCHECKERROR(StatusWanted,LogLevel,Function,API,retval) if(retval!=StatusWanted) { \
+		syslog(LogLevel,"Error in " Function " (" API " returned 0x%x)",retval); \
+                NTCHECKERROR_FAILED = 1; }
+
+/* Prototypes */
+PSID getSID(LSA_HANDLE hLSA, const char* strUser);
+PLSA_UNICODE_STRING makeLSAStringA(PLSA_UNICODE_STRING strLSA, const char* strIn, size_t len);
+LSA_HANDLE openPolicy(const char* strMachine, ACCESS_MASK access);
+int testUserRights(LSA_HANDLE hLSA, const char* strUser, const char** strRightsToTest, ULONG intRightsToTestCount);
+int testUserRightsByUID(uid_t uid, const char** strRightsToTest, ULONG intRightsToTestCount);
+
+/* like testUserRights, returns 0 if uid does NOT have all of the required privileged,
+   and returns non-zero if it DOES. */
+int testUserRightsByUID(uid_t uid, const char** strRightsToTest, ULONG intRightsToTestCount)
+{
+  int retVal = 0;
+  LSA_HANDLE hLSA;
+  struct passwd* pw;
+  char  strUser    [UNLEN + 1];
+  char* domain = (char *) alloca (INTERNET_MAX_HOST_NAME_LENGTH + 1);
+
+  pw = getpwuid(uid);
+  if (!pw) return retVal;
+  cygwin_internal(CW_EXTRACT_DOMAIN_AND_USER, pw, domain, strUser);
+ 
+  hLSA=openPolicy(NULL,POLICY_ALL_ACCESS); /* always this machine */
+  if (hLSA)
+  {
+    retVal = testUserRights(hLSA, strUser, strRightsToTest, intRightsToTestCount);
+    LsaClose(hLSA);
+  }
+  return retVal;
+}
+
+/* Set an LSA_UNICODE_STRING based on an ascii string. *
+ * strLSA->Buffer must point to allocated storage of   *
+ * size (len+1) * sizeof(WCHAR).                       */
+PLSA_UNICODE_STRING makeLSAStringA(PLSA_UNICODE_STRING strLSA, const char* strIn, size_t len)
+{
+  if (!strIn) /* Fed NULL pointer */
+    {
+      strLSA->Buffer[0]=0; /* unicode NULL char */
+      strLSA->Length=0;
+      strLSA->MaximumLength=0;
+    }
+  else
+    {
+      mbstate_t mbst = {0}; /* multibyte char initial state. */
+      mbsrtowcs(strLSA->Buffer, &strIn, len, &mbst);
+      strLSA->Buffer[len]=0; /* NULL terminate. */
+      strLSA->Length=wcslen(strLSA->Buffer) * sizeof(WCHAR);
+      strLSA->MaximumLength=strLSA->Length + sizeof(WCHAR);
+    }
+  return strLSA;
+}
+
+/* Open a connection to the Local Security Authority *
+ * Policy object on the specified system.            */
+LSA_HANDLE openPolicy(const char* strMachine, ACCESS_MASK access)
+{
+  LSA_OBJECT_ATTRIBUTES objattr;
+  LSA_HANDLE hLSA;
+  NTSTATUS status;
+  LSA_UNICODE_STRING machine;
+  int c;
+  int NTCHECKERROR_FAILED = 0;
+
+  c = strMachine ? strlen(strMachine) : 0;
+  machine.Buffer=(PWSTR)alloca((c+1)*2); /* Keep within this function so we *
+					  * can use alloca(...)             */
+  makeLSAStringA(&machine,strMachine,c);
+
+  objattr.Length=sizeof(objattr); /* Reserved structure */
+  objattr.RootDirectory=NULL;
+  objattr.ObjectName=NULL;
+  objattr.Attributes=0;
+  objattr.SecurityDescriptor=NULL;
+  objattr.SecurityQualityOfService=NULL;
+
+  status=LsaOpenPolicy(
+		       machine.Length ? &machine : NULL, /* If no machine, use localhost. */
+		       &objattr,access,&hLSA);
+  NTCHECKERROR(STATUS_SUCCESS,LOG_WARNING,"openPolicy","LsaOpenPolicy",status)
+  return hLSA;
+}
+
+int testUserRights(LSA_HANDLE hLSA, const char* strUser, const char** strRightsToTest, ULONG intRightsToTestCount)
+{
+  /* Test if the user has the specified rights. Returns zero if they don't all match, non-zero if they do all match. */
+  PLSA_UNICODE_STRING UserRights;
+  ULONG CountOfRights;
+  NTSTATUS status;
+  int c,d;
+  int intMatched=0;
+  int NTCHECKERROR_FAILED = 0;
+
+  PSID Sid=getSID(hLSA,strUser);
+  if (!Sid)
+    {
+      return 0;
+    }
+
+  status=LsaEnumerateAccountRights(
+				   hLSA,
+				   Sid,
+				   &UserRights,
+				   &CountOfRights
+				   );
+	
+  LocalFree(Sid);
+
+  NTCHECKERROR(STATUS_SUCCESS && status!=STATUS_OBJECT_NAME_NOT_FOUND,LOG_WARNING,\
+	       "testUserRights","LsaEnumerateAccountRights",status);
+  if (NTCHECKERROR_FAILED)
+    {
+       return 0;
+    }
+
+  for (c=0;c<CountOfRights;c++)
+    {
+      const wchar_t* src=UserRights[c].Buffer;
+      size_t len=wcslen(src);
+      mbstate_t mbst = {0}; /* multibyte char initial state. */
+      char* rightname=alloca(len+1); /* allocate 1 byte per character + 1 byte for null terminator. */
+      rightname[wcsrtombs(rightname, &src, len, &mbst)]='\0';
+      for (d=0;d<intRightsToTestCount;d++)
+	{
+	  if (!stricmp(rightname,strRightsToTest[d])) intMatched++;
+	}
+    }
+  return(intMatched==intRightsToTestCount);
+}
+
+/* Warning: The caller must call LocalFree() on the returned PSID */
+PSID getSID(LSA_HANDLE hLSA, const char* strUser)
+{
+  LSA_UNICODE_STRING lsastrUser;
+  PLSA_REFERENCED_DOMAIN_LIST referencedDomains;
+  PLSA_TRANSLATED_SID sids;
+  int c;
+  ULONG cSubAuth, NewSidLength;
+  PSID NewSid;
+  NTSTATUS status;
+  LPTSTR strSid;
+  BOOL bStatus;
+  int NTCHECKERROR_FAILED = 0;
+
+  c = strUser ? strlen(strUser) : 0;
+  lsastrUser.Buffer=(PWSTR)alloca((c+1)*2); /* Keep within this function so we *
+					     * can use alloca()                */
+  makeLSAStringA(&lsastrUser,strUser,c);
+
+  status=LsaLookupNames(hLSA,1,&lsastrUser,&referencedDomains,&sids);
+  NTCHECKERROR(STATUS_SUCCESS,LOG_WARNING,"getSID","LsaLookupNames",status);
+  if (NTCHECKERROR_FAILED)
+    {
+      if (referencedDomains) { LsaFreeMemory(referencedDomains); }    
+      if (sids)              { LsaFreeMemory(sids); }
+      return NewSid; /* which is NULL */
+    }
+
+  cSubAuth = *GetSidSubAuthorityCount(referencedDomains->Domains[sids->DomainIndex].Sid);
+  NewSidLength = GetSidLengthRequired((UCHAR) (cSubAuth + 1));
+  NewSid = (PSID) LocalAlloc(0, NewSidLength);
+  CopySid(NewSidLength,NewSid,referencedDomains->Domains[sids->DomainIndex].Sid);
+  *GetSidSubAuthorityCount(NewSid) = (UCHAR) cSubAuth + 1;
+  *GetSidSubAuthority(NewSid, cSubAuth) = sids->RelativeId;
+
+  LsaFreeMemory(referencedDomains);
+  LsaFreeMemory(sids);
+
+  return NewSid;
+}
+


[-- Attachment #3: Type: text/plain, Size: 218 bytes --]

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: PATCH: login under privileged user != SYSTEM
  2008-04-17 12:57 PATCH: login under privileged user != SYSTEM Charles Wilson
@ 2008-04-17 13:06 ` Corinna Vinschen
  2008-04-18  9:31   ` Charles Wilson
  0 siblings, 1 reply; 6+ messages in thread
From: Corinna Vinschen @ 2008-04-17 13:06 UTC (permalink / raw)
  To: cygwin

On Apr 17 01:48, Charles Wilson wrote:
> I've been trying to get all the bugs in inetutils-1.5 squashed, and I ran 
> into an issue with rlogin when rlogind was running under a privileged user 
> (that is, not SYSTEM), as is required for Windows Server 2003, 2008, and 
> Vista.
>
> The problem was, although rsh would honor my .rhosts and allow passwordless 
> operation, rlogin would not. It always asked for my password.
>
> Internally, rlogind *knew* that the incoming connection was "authenticated" 
> via .rhosts, so it invoked login thus:
>
> login -p -h <incoming hostname> -f -- <username>
>
> where the '-f' SHOULD mean "this is already authenticated, don't ask for 
> the password again".  But it wasn't working, because login was hardcoded to 
> compare the current uid to 18 (that is, SYSTEM), before allowing 
> passwordless auth.  But rlogind/login were not running under SYSTEM.
>
>
> I don't think you can simply replace the code in login, the way we did in 
> many of the servers, tho:
>
>  #ifdef __CYGWIN__
> -#define  ROOT_UID    18
> +#define  ROOT_UID    getuid()
>  #else
>  #define  ROOT_UID     0
>  #endif
>
> because then you'd allow passwordless auth no matter what account login was 
> running under. Now, it might fail later, assuming we added code to check 
> whether some future setuid() succeeded or not, but I think that's too late 
> in the process.
>
> So, for *login*, I changed the code from
>    if (uid == ROOT_UID)
> to
>    if (is_a_ROOT_UID(uid))
>
> and implemented a function that, depending on the underlying windows 
> version, either
>   (1) compares to 18
>   (2) checks that the account with the specified uid has the following 
> privileges:
> +        SeAssignPrimaryTokenPrivilege
> +        SeCreateTokenPrivilege
> +        SeTcbPrivilege
> +        SeIncreaseQuotaPrivilege
> +        SeServiceLogonRight
> (On NT/2k/XP, uid = 18 is an automatic "yes", but if uid != 18, then we 
> fall back to the Vista check-privileges procedure)
>
> With these changes, I can now get passwordless rlogin when inetd is running 
> under a privileged user, and not SYSTEM.
>
> Most of the code was adapted from editrights/main.c...

Cool, thanks!  Would you mind to take over login maintainance, too?  It
was always just the wagging tail of inetutils anyway...

Other than that, I'd like to suggest a few minor changes to the patch:

- The SeServiceLogonRight doesn't have to be tested, IMHO.  It doesn't
  have anything to do with user account switching.

- I don't understand why NT4 is handled specially by only checking for the
  uid while 2K and XP get the additional account check if necessary.  None
  of the functions are new with 2K, they all exists since NT 3.51.

- I wouldn't do the automatic yes for uid 18 anymore.  Even for NT/2K/XP
  it would be more correct to check if the current account running the
  process is the one with SID S-1-5-18.  Given that there's already
  so much code for Windows specific privilege checking, I don't think
  it hurts a lot to add something along the lines of

    AllocateAndInitializeSid (SECURITY_NT_AUTHORITY, 1, 18, ..., &system_sid);
    token = OpenProcessToken (GetCurrentProcess ());
    user_sid = GetTokenInformation(token, TOKEN_USER);
    if (EqualSid (user_sid, system_sid))
      yes
    else
      check_privileges


Thanks again,
Corinna

-- 
Corinna Vinschen                  Please, send mails regarding Cygwin to
Cygwin Project Co-Leader          cygwin AT cygwin DOT com
Red Hat

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: PATCH: login under privileged user != SYSTEM
  2008-04-17 13:06 ` Corinna Vinschen
@ 2008-04-18  9:31   ` Charles Wilson
  2008-04-18 13:52     ` Corinna Vinschen
  0 siblings, 1 reply; 6+ messages in thread
From: Charles Wilson @ 2008-04-18  9:31 UTC (permalink / raw)
  To: cygwin

Corinna Vinschen wrote:
> On Apr 17 01:48, Charles Wilson wrote:

>> With these changes, I can now get passwordless rlogin when inetd is running 
>> under a privileged user, and not SYSTEM.
>>
>> Most of the code was adapted from editrights/main.c...
> 
> Cool, thanks!  Would you mind to take over login maintainance, too?  It
> was always just the wagging tail of inetutils anyway...

Sure.

> Other than that, I'd like to suggest a few minor changes to the patch:
> 
> - The SeServiceLogonRight doesn't have to be tested, IMHO.  It doesn't
>   have anything to do with user account switching.

Ack.

> - I don't understand why NT4 is handled specially by only checking for the
>   uid while 2K and XP get the additional account check if necessary.  None
>   of the functions are new with 2K, they all exists since NT 3.51.

I initially thought the program flow would be rather awkward, to make NT 
act like 2k/xp -- and I just didn't care much for NT.  But after I 
finished coding I should have noticed that it would really be quite 
simple to do that.  But I didn't.

Fixed now.

> - I wouldn't do the automatic yes for uid 18 anymore.  Even for NT/2K/XP
>   it would be more correct to check if the current account running the
                                              ^^^^^^^^^^^^^^^^^^^^^^^
>   process is the one with SID S-1-5-18.  

But that's not exactly what you want, here. Sometimes, login.c does
   isROOTUID(getuid())
which could be replaced as you suggest. But *most* of the time, login.c does
   isROOTUID(pw->pw_uid)
before it has actually switched to that user.

And saying that isROOTUID(uid) ==
   {
     setuid(pw->pw_uid);
     isCurrentProcessRunningAsROOT();
     setuid(saved_uid);
   }
is overkill -- especially as I want "isROOTUID(uid)" to work even if the 
current user does NOT have the privileges needed for setuid() to work.

>   Given that there's already
>   so much code for Windows specific privilege checking, I don't think
>   it hurts a lot to add something along the lines of
> 
>     AllocateAndInitializeSid (SECURITY_NT_AUTHORITY, 1, 18, ..., &system_sid);
>     token = OpenProcessToken (GetCurrentProcess ());
>     user_sid = GetTokenInformation(token, TOKEN_USER);
>     if (EqualSid (user_sid, system_sid))
>       yes
>     else
>       check_privileges

I implemented something along these lines. After creating several 
building block functions, I wrapped it all up into:

extern int currentUserIsLocalSystem();
extern int currentUserIsMemberOfLocalAdministrators();

These two use OpenProcessToken/GetTokenInformation as you suggested. 
However, the one I really use is:

extern int uidIsLocalSystem(uid_t uid);

==== aside:
And I wish I could have figured out how to make 
uidIsMemberOfLocalAdmin(uid_t uid), but if uid != current user it's 
really hard to get the either (a) the list of groups a particular user 
is a member of, or (b) the list of users that are members of a 
particular group.  Since I already have a make-SID-from-uid method, if I 
had (a) I could iterate that list trying to match the local 
Administrators SID, or if I had (b) I could iterate through the list and 
compare to my SID-from-uid.

I know there is NetUserGetLocalGroups, but what if the user is a member 
of a global group, and the local security policy makes that global group 
a member of the (local) Administrators group? With the multi-level 
inclusion of groups, it's almost easier to go the other way: get the 
local administrator group, and use (recursively) NetLocalGroupGetMembers 
and NetGroupGetUsers to build a list of all users that are (directly or 
by inclusion) members of the (local) Administrators group -- and THEN 
iterate that to see if any of them match SID-from-uid.

But neither is easy.
==== end aside

So, I'm still not checking that the uid specified is a member of the 
local Administrators group.

I did discover one awkward thing: in my make-SID-from-uid function, I do 
the following

1. get struct passwd* for uid
2. cygwin_internal(CW_EXTRACT_DOMAIN_AND_USER, pw, domain, name);
3. get the servername for the domain by using either
    DsGetDcName or NetGetDCName
4. use NetUserGetInfo to get a PUSER_INFO_3 structure
    (if domain user, and call fails, try again locally...)
5. use LookupAccountName to get the SID
    (if basic call fails and returned account type is SidTypeDomain,
    try again after adding domain spec to username)

However, if uid = 18 it turns out that NetUserGetInfo(...., 
toUnicode("LocalSystem"),...) always fails. I even tested that 
proposition in a quick test app. It just doesn't work.

Which means that my uidIsLocalSystem(uid_t uid) function is broken in 
this case. It's algorithm is:

1) create SID for LocalSystem manually. call this "desiredSID"
2) get SID corresponding to supplied uid. call this "userSID"
3) compare desiredSID == userSID

And for LocalSystem, step #2 fails.  So, I had to modify 
make-SID-from-uid, by adding step 1a:

1a. if pw->pw_name == "LocalSystem" (after canonicalizing from "SYSTEM")
     manually create SID for SECURITY_LOCAL_SYSTEM_RID and return

But when you boil it all down, that just means that I'm now comparing...

      18 == 18

to see if uid is LocalSystem. I'm doing a lot more work to get there, 
though. <g>

I'll upload a test package tomorrow, with all these changes. It'll need 
lots of testing, though, in various environments: I have no domain to 
test it on.

--
Chuck

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: PATCH: login under privileged user != SYSTEM
  2008-04-18  9:31   ` Charles Wilson
@ 2008-04-18 13:52     ` Corinna Vinschen
  2008-04-19 14:54       ` Charles Wilson
  0 siblings, 1 reply; 6+ messages in thread
From: Corinna Vinschen @ 2008-04-18 13:52 UTC (permalink / raw)
  To: cygwin

On Apr 18 04:32, Charles Wilson wrote:
> Corinna Vinschen wrote:
>> Cool, thanks!  Would you mind to take over login maintainance, too?  It
>> was always just the wagging tail of inetutils anyway...
>
> Sure.

Thank you!  Igor?  Can we get another gold star for Charles?

>> - I wouldn't do the automatic yes for uid 18 anymore.  Even for NT/2K/XP
>>   it would be more correct to check if the current account running the
>                                              ^^^^^^^^^^^^^^^^^^^^^^^
>>   process is the one with SID S-1-5-18.  
>
> But that's not exactly what you want, here. Sometimes, login.c does
>   isROOTUID(getuid())
> which could be replaced as you suggest. But *most* of the time, login.c 
> does
>   isROOTUID(pw->pw_uid)
> before it has actually switched to that user.
>
> And saying that isROOTUID(uid) ==
>   {
>     setuid(pw->pw_uid);
>     isCurrentProcessRunningAsROOT();
>     setuid(saved_uid);
>   }
> is overkill -- especially as I want "isROOTUID(uid)" to work even if the 
> current user does NOT have the privileges needed for setuid() to work.

That makes sense.

> ==== aside:
> And I wish I could have figured out how to make 
> uidIsMemberOfLocalAdmin(uid_t uid), but if uid != current user it's really 
> hard to get the either (a) the list of groups a particular user is a member 
> of, or (b) the list of users that are members of a particular group.  Since 
> I already have a make-SID-from-uid method, if I had (a) I could iterate 
> that list trying to match the local Administrators SID, or if I had (b) I 
> could iterate through the list and compare to my SID-from-uid.
>
> I know there is NetUserGetLocalGroups, but what if the user is a member of 
> a global group, and the local security policy makes that global group a 
> member of the (local) Administrators group? With the multi-level inclusion 
> of groups, it's almost easier to go the other way: get the local 
> administrator group, and use (recursively) NetLocalGroupGetMembers and 
> NetGroupGetUsers to build a list of all users that are (directly or by 
> inclusion) members of the (local) Administrators group -- and THEN iterate 
> that to see if any of them match SID-from-uid.
>
> But neither is easy.
> ==== end aside

Yes, I agree wholeheartedly.  The handling of users and groups is
really complicated and you're coding your brain out of your head just
to *get* the information and tyhen you still have to test.  It's
really not funny how much code you need to fetch certain types of
information.

> So, I'm still not checking that the uid specified is a member of the local 
> Administrators group.
>
> I did discover one awkward thing: in my make-SID-from-uid function, I do 
> the following
>
> 1. get struct passwd* for uid
> 2. cygwin_internal(CW_EXTRACT_DOMAIN_AND_USER, pw, domain, name);
> 3. get the servername for the domain by using either
>    DsGetDcName or NetGetDCName
> 4. use NetUserGetInfo to get a PUSER_INFO_3 structure
>    (if domain user, and call fails, try again locally...)
> 5. use LookupAccountName to get the SID
>    (if basic call fails and returned account type is SidTypeDomain,
>    try again after adding domain spec to username)
>
> However, if uid = 18 it turns out that NetUserGetInfo(...., 
> toUnicode("LocalSystem"),...) always fails. I even tested that proposition 
> in a quick test app. It just doesn't work.

As for an account being Administrator, and apart from special accounts
like SYSTEM or LOCAL_SERVICE...

What about just checking the value of PUSER_INFO_3->usri3_priv?  It may
contain the value USER_PRIV_ADMIN.  That should be sufficient, afaics.


Corinna

-- 
Corinna Vinschen                  Please, send mails regarding Cygwin to
Cygwin Project Co-Leader          cygwin AT cygwin DOT com
Red Hat

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: PATCH: login under privileged user != SYSTEM
  2008-04-18 13:52     ` Corinna Vinschen
@ 2008-04-19 14:54       ` Charles Wilson
  2008-04-21  9:09         ` Corinna Vinschen
  0 siblings, 1 reply; 6+ messages in thread
From: Charles Wilson @ 2008-04-19 14:54 UTC (permalink / raw)
  To: cygwin

Corinna Vinschen wrote:
> On Apr 18 04:32, Charles Wilson wrote: 
>> So, I'm still not checking that the uid specified is a member of the local 
>> Administrators group.

Now I am. See below.

> As for an account being Administrator, and apart from special accounts
> like SYSTEM or LOCAL_SERVICE...
> 
> What about just checking the value of PUSER_INFO_3->usri3_priv?  It may
> contain the value USER_PRIV_ADMIN.  That should be sufficient, afaics.

That works, even on Vista. I've uploaded a new login-1.9-8 as a test 
release. Please take a look (I'll make a more formal 'avail for test' 
announcement later).

--
Chuck

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: PATCH: login under privileged user != SYSTEM
  2008-04-19 14:54       ` Charles Wilson
@ 2008-04-21  9:09         ` Corinna Vinschen
  0 siblings, 0 replies; 6+ messages in thread
From: Corinna Vinschen @ 2008-04-21  9:09 UTC (permalink / raw)
  To: cygwin

On Apr 19 01:52, Charles Wilson wrote:
> Corinna Vinschen wrote:
>> On Apr 18 04:32, Charles Wilson wrote: 
>>> So, I'm still not checking that the uid specified is a member of the 
>>> local Administrators group.
>
> Now I am. See below.
>
>> As for an account being Administrator, and apart from special accounts
>> like SYSTEM or LOCAL_SERVICE...
>> What about just checking the value of PUSER_INFO_3->usri3_priv?  It may
>> contain the value USER_PRIV_ADMIN.  That should be sufficient, afaics.
>
> That works, even on Vista. I've uploaded a new login-1.9-8 as a test 
> release. Please take a look (I'll make a more formal 'avail for test' 
> announcement later).

I saw you did already.


Thanks,
Corinna

-- 
Corinna Vinschen                  Please, send mails regarding Cygwin to
Cygwin Project Co-Leader          cygwin AT cygwin DOT com
Red Hat

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2008-04-21  8:50 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-04-17 12:57 PATCH: login under privileged user != SYSTEM Charles Wilson
2008-04-17 13:06 ` Corinna Vinschen
2008-04-18  9:31   ` Charles Wilson
2008-04-18 13:52     ` Corinna Vinschen
2008-04-19 14:54       ` Charles Wilson
2008-04-21  9:09         ` Corinna Vinschen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).