Implement QLocalServer::socketOptions on windows
authorShane Kearns <ext-shane.2.kearns@nokia.com>
Wed, 2 May 2012 14:32:09 +0000 (15:32 +0100)
committerQt by Nokia <qt-info@nokia.com>
Thu, 3 May 2012 14:11:58 +0000 (16:11 +0200)
Tested manually using the localfortuneserver example and multiple
login sessions. The linux autotest isn't suitable for windows due
to pipe permissions not appearing in the filesystem.

Task-number: QTBUG-25147
Change-Id: I5ea4db81d1870dc45bd483fa8d0b06afede3b722
Reviewed-by: Joerg Bornemann <joerg.bornemann@nokia.com>
src/network/socket/qlocalserver.cpp
src/network/socket/qlocalserver_win.cpp
src/network/socket/socket.pri

index b379e99..8284ae4 100644 (file)
@@ -95,8 +95,10 @@ QT_BEGIN_NAMESPACE
     Access is restricted to the same user as the process that created the socket.
     \value GroupAccessOption
     Access is restricted to the same group but not the user that created the socket on Linux.
+    Access is restricted to the primary group of the process on Windows
     \value OtherAccessOption
     Access is available to everyone but the user and group that created the socket on Linux.
+    Access is available to everyone on Windows.
     \value WorldAccessOption
     No access restrictions.
 
@@ -150,6 +152,13 @@ QLocalServer::~QLocalServer()
     honor file permissions for Unix domain sockets and by default
     have WorldAccess and these permission flags will have no effect.
 
+    On Windows, UserAccessOption is sufficient to allow a non
+    elevated process to connect to a local server created by an
+    elevated process run by the same user. GroupAccessOption
+    refers to the primary group of the process (see TokenPrimaryGroup
+    in the Windows documentation). OtherAccessOption refers to
+    the well known "Everyone" group.
+
     By default none of the flags are set, access permissions
     are the platform default.
 
index 07357e5..00eae32 100644 (file)
 #include "qlocalserver.h"
 #include "qlocalserver_p.h"
 #include "qlocalsocket.h"
+#include <QtCore/private/qsystemerror_p.h>
 
 #include <qdebug.h>
 
+#include <Aclapi.h>
+#include <AccCtrl.h>
+#include <Sddl.h>
+
 // The buffer size need to be 0 otherwise data could be
 // lost if the socket that has written data closes the connection
 // before it is read.  Pipewriter is used for write buffering.
@@ -62,6 +67,116 @@ bool QLocalServerPrivate::addListener()
     listeners << Listener();
     Listener &listener = listeners.last();
 
+    SECURITY_ATTRIBUTES sa;
+    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+    sa.bInheritHandle = FALSE;      //non inheritable handle, same as default
+    sa.lpSecurityDescriptor = 0;    //default security descriptor
+
+    QScopedPointer<SECURITY_DESCRIPTOR> pSD;
+    PSID worldSID = 0;
+    QByteArray aclBuffer;
+    QByteArray tokenUserBuffer;
+    QByteArray tokenGroupBuffer;
+
+    // create security descriptor if access options were specified
+    if ((socketOptions & QLocalServer::WorldAccessOption)) {
+        pSD.reset(new SECURITY_DESCRIPTOR);
+        if (!InitializeSecurityDescriptor(pSD.data(), SECURITY_DESCRIPTOR_REVISION)) {
+            setError(QLatin1String("QLocalServerPrivate::addListener"));
+            return false;
+        }
+        HANDLE hToken = NULL;
+        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+            return false;
+        DWORD dwBufferSize = 0;
+        GetTokenInformation(hToken, TokenUser, 0, 0, &dwBufferSize);
+        tokenUserBuffer.fill(0, dwBufferSize);
+        PTOKEN_USER pTokenUser = (PTOKEN_USER)tokenUserBuffer.data();
+        if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)) {
+            setError(QLatin1String("QLocalServerPrivate::addListener"));
+            CloseHandle(hToken);
+            return false;
+        }
+
+        dwBufferSize = 0;
+        GetTokenInformation(hToken, TokenPrimaryGroup, 0, 0, &dwBufferSize);
+        tokenGroupBuffer.fill(0, dwBufferSize);
+        PTOKEN_PRIMARY_GROUP pTokenGroup = (PTOKEN_PRIMARY_GROUP)tokenGroupBuffer.data();
+        if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dwBufferSize, &dwBufferSize)) {
+            setError(QLatin1String("QLocalServerPrivate::addListener"));
+            CloseHandle(hToken);
+            return false;
+        }
+        CloseHandle(hToken);
+
+#ifdef QLOCALSERVER_DEBUG
+        DWORD groupNameSize;
+        DWORD domainNameSize;
+        SID_NAME_USE groupNameUse;
+        LPWSTR groupNameSid;
+        LookupAccountSid(0, pTokenGroup->PrimaryGroup, 0, &groupNameSize, 0, &domainNameSize, &groupNameUse);
+        QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> groupName(new wchar_t[groupNameSize]);
+        QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> domainName(new wchar_t[domainNameSize]);
+        if (LookupAccountSid(0, pTokenGroup->PrimaryGroup, groupName.data(), &groupNameSize, domainName.data(), &domainNameSize, &groupNameUse)) {
+            qDebug() << "primary group" << QString::fromWCharArray(domainName.data()) << "\\" << QString::fromWCharArray(groupName.data()) << "type=" << groupNameUse;
+        }
+        if (ConvertSidToStringSid(pTokenGroup->PrimaryGroup, &groupNameSid)) {
+            qDebug() << "primary group SID" << QString::fromWCharArray(groupNameSid) << "valid" << IsValidSid(pTokenGroup->PrimaryGroup);
+            LocalFree(groupNameSid);
+        }
+#endif
+
+        SID_IDENTIFIER_AUTHORITY WorldAuth = SECURITY_WORLD_SID_AUTHORITY;
+        if (!AllocateAndInitializeSid(&WorldAuth, 1, SECURITY_WORLD_RID,
+            0, 0, 0, 0, 0, 0, 0,
+            &worldSID)) {
+            setError(QLatin1String("QLocalServerPrivate::addListener"));
+            return false;
+        }
+
+        //calculate size of ACL buffer
+        DWORD aclSize = sizeof(ACL) + ((sizeof(ACCESS_ALLOWED_ACE)) * 3);
+        aclSize += GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
+        aclSize += GetLengthSid(pTokenGroup->PrimaryGroup) - sizeof(DWORD);
+        aclSize += GetLengthSid(worldSID) - sizeof(DWORD);
+        aclSize = (aclSize + (sizeof(DWORD) - 1)) & 0xfffffffc;
+
+        aclBuffer.fill(0, aclSize);
+        PACL acl = (PACL)aclBuffer.data();
+        InitializeAcl(acl, aclSize, ACL_REVISION_DS);
+
+        if (socketOptions & QLocalServer::UserAccessOption) {
+            if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenUser->User.Sid)) {
+                setError(QLatin1String("QLocalServerPrivate::addListener"));
+                FreeSid(worldSID);
+                return false;
+            }
+        }
+        if (socketOptions & QLocalServer::GroupAccessOption) {
+            if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenGroup->PrimaryGroup)) {
+                setError(QLatin1String("QLocalServerPrivate::addListener"));
+                FreeSid(worldSID);
+                return false;
+            }
+        }
+        if (socketOptions & QLocalServer::OtherAccessOption) {
+            if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, worldSID)) {
+                setError(QLatin1String("QLocalServerPrivate::addListener"));
+                FreeSid(worldSID);
+                return false;
+            }
+        }
+        SetSecurityDescriptorOwner(pSD.data(), pTokenUser->User.Sid, FALSE);
+        SetSecurityDescriptorGroup(pSD.data(), pTokenGroup->PrimaryGroup, FALSE);
+        if (!SetSecurityDescriptorDacl(pSD.data(), TRUE, acl, FALSE)) {
+            setError(QLatin1String("QLocalServerPrivate::addListener"));
+            FreeSid(worldSID);
+            return false;
+        }
+
+        sa.lpSecurityDescriptor = pSD.data();
+    }
+
     listener.handle = CreateNamedPipe(
                  (const wchar_t *)fullServerName.utf16(), // pipe name
                  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,       // read/write access
@@ -72,7 +187,7 @@ bool QLocalServerPrivate::addListener()
                  BUFSIZE,                  // output buffer size
                  BUFSIZE,                  // input buffer size
                  3000,                     // client time-out
-                 NULL);
+                 &sa);
 
     if (listener.handle == INVALID_HANDLE_VALUE) {
         setError(QLatin1String("QLocalServerPrivate::addListener"));
@@ -80,6 +195,9 @@ bool QLocalServerPrivate::addListener()
         return false;
     }
 
+    if (worldSID)
+        FreeSid(worldSID);
+
     memset(&listener.overlapped, 0, sizeof(listener.overlapped));
     listener.overlapped.hEvent = eventHandle;
     if (!ConnectNamedPipe(listener.handle, &listener.overlapped)) {
index 8f93f0d..39b214f 100644 (file)
@@ -40,6 +40,8 @@ win32:SOURCES += socket/qnativesocketengine_win.cpp \
                 socket/qlocalsocket_win.cpp \
                 socket/qlocalserver_win.cpp
 
+win32:!wince*:LIBS += -lAdvapi32
+
 wince*: {
     SOURCES -= socket/qlocalsocket_win.cpp \
                socket/qlocalserver_win.cpp