1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtNetwork module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qlocalserver.h"
41 #include "qlocalserver_p.h"
42 #include "qlocalsocket.h"
43 #include <QtCore/private/qsystemerror_p.h>
44 
45 #include <qdebug.h>
46 
47 #include <aclapi.h>
48 #include <accctrl.h>
49 #include <sddl.h>
50 
51 // The buffer size need to be 0 otherwise data could be
52 // lost if the socket that has written data closes the connection
53 // before it is read.  Pipewriter is used for write buffering.
54 #define BUFSIZE 0
55 
56 // ###: This should be a property. Should replace the insane 50 on unix as well.
57 #define SYSTEM_MAX_PENDING_SOCKETS 8
58 
59 QT_BEGIN_NAMESPACE
60 
addListener()61 bool QLocalServerPrivate::addListener()
62 {
63     // The object must not change its address once the
64     // contained OVERLAPPED struct is passed to Windows.
65     listeners << Listener();
66     Listener &listener = listeners.last();
67 
68     SECURITY_ATTRIBUTES sa;
69     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
70     sa.bInheritHandle = FALSE;      //non inheritable handle, same as default
71     sa.lpSecurityDescriptor = 0;    //default security descriptor
72 
73     QScopedPointer<SECURITY_DESCRIPTOR> pSD;
74     PSID worldSID = 0;
75     QByteArray aclBuffer;
76     QByteArray tokenUserBuffer;
77     QByteArray tokenGroupBuffer;
78 
79     // create security descriptor if access options were specified
80     if ((socketOptions & QLocalServer::WorldAccessOption)) {
81         pSD.reset(new SECURITY_DESCRIPTOR);
82         if (!InitializeSecurityDescriptor(pSD.data(), SECURITY_DESCRIPTOR_REVISION)) {
83             setError(QLatin1String("QLocalServerPrivate::addListener"));
84             return false;
85         }
86         HANDLE hToken = NULL;
87         if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
88             return false;
89         DWORD dwBufferSize = 0;
90         GetTokenInformation(hToken, TokenUser, 0, 0, &dwBufferSize);
91         tokenUserBuffer.fill(0, dwBufferSize);
92         auto pTokenUser = reinterpret_cast<PTOKEN_USER>(tokenUserBuffer.data());
93         if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)) {
94             setError(QLatin1String("QLocalServerPrivate::addListener"));
95             CloseHandle(hToken);
96             return false;
97         }
98 
99         dwBufferSize = 0;
100         GetTokenInformation(hToken, TokenPrimaryGroup, 0, 0, &dwBufferSize);
101         tokenGroupBuffer.fill(0, dwBufferSize);
102         auto pTokenGroup = reinterpret_cast<PTOKEN_PRIMARY_GROUP>(tokenGroupBuffer.data());
103         if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dwBufferSize, &dwBufferSize)) {
104             setError(QLatin1String("QLocalServerPrivate::addListener"));
105             CloseHandle(hToken);
106             return false;
107         }
108         CloseHandle(hToken);
109 
110 #ifdef QLOCALSERVER_DEBUG
111         DWORD groupNameSize;
112         DWORD domainNameSize;
113         SID_NAME_USE groupNameUse;
114         LPWSTR groupNameSid;
115         LookupAccountSid(0, pTokenGroup->PrimaryGroup, 0, &groupNameSize, 0, &domainNameSize, &groupNameUse);
116         QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> groupName(new wchar_t[groupNameSize]);
117         QScopedPointer<wchar_t, QScopedPointerArrayDeleter<wchar_t>> domainName(new wchar_t[domainNameSize]);
118         if (LookupAccountSid(0, pTokenGroup->PrimaryGroup, groupName.data(), &groupNameSize, domainName.data(), &domainNameSize, &groupNameUse)) {
119             qDebug() << "primary group" << QString::fromWCharArray(domainName.data()) << "\\" << QString::fromWCharArray(groupName.data()) << "type=" << groupNameUse;
120         }
121         if (ConvertSidToStringSid(pTokenGroup->PrimaryGroup, &groupNameSid)) {
122             qDebug() << "primary group SID" << QString::fromWCharArray(groupNameSid) << "valid" << IsValidSid(pTokenGroup->PrimaryGroup);
123             LocalFree(groupNameSid);
124         }
125 #endif
126 
127         SID_IDENTIFIER_AUTHORITY WorldAuth = { SECURITY_WORLD_SID_AUTHORITY };
128         if (!AllocateAndInitializeSid(&WorldAuth, 1, SECURITY_WORLD_RID,
129             0, 0, 0, 0, 0, 0, 0,
130             &worldSID)) {
131             setError(QLatin1String("QLocalServerPrivate::addListener"));
132             return false;
133         }
134 
135         //calculate size of ACL buffer
136         DWORD aclSize = sizeof(ACL) + ((sizeof(ACCESS_ALLOWED_ACE)) * 3);
137         aclSize += GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);
138         aclSize += GetLengthSid(pTokenGroup->PrimaryGroup) - sizeof(DWORD);
139         aclSize += GetLengthSid(worldSID) - sizeof(DWORD);
140         aclSize = (aclSize + (sizeof(DWORD) - 1)) & 0xfffffffc;
141 
142         aclBuffer.fill(0, aclSize);
143         auto acl = reinterpret_cast<PACL>(aclBuffer.data());
144         InitializeAcl(acl, aclSize, ACL_REVISION_DS);
145 
146         if (socketOptions & QLocalServer::UserAccessOption) {
147             if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenUser->User.Sid)) {
148                 setError(QLatin1String("QLocalServerPrivate::addListener"));
149                 FreeSid(worldSID);
150                 return false;
151             }
152         }
153         if (socketOptions & QLocalServer::GroupAccessOption) {
154             if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, pTokenGroup->PrimaryGroup)) {
155                 setError(QLatin1String("QLocalServerPrivate::addListener"));
156                 FreeSid(worldSID);
157                 return false;
158             }
159         }
160         if (socketOptions & QLocalServer::OtherAccessOption) {
161             if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, worldSID)) {
162                 setError(QLatin1String("QLocalServerPrivate::addListener"));
163                 FreeSid(worldSID);
164                 return false;
165             }
166         }
167         SetSecurityDescriptorOwner(pSD.data(), pTokenUser->User.Sid, FALSE);
168         SetSecurityDescriptorGroup(pSD.data(), pTokenGroup->PrimaryGroup, FALSE);
169         if (!SetSecurityDescriptorDacl(pSD.data(), TRUE, acl, FALSE)) {
170             setError(QLatin1String("QLocalServerPrivate::addListener"));
171             FreeSid(worldSID);
172             return false;
173         }
174 
175         sa.lpSecurityDescriptor = pSD.data();
176     }
177 
178     listener.handle = CreateNamedPipe(
179                  reinterpret_cast<const wchar_t *>(fullServerName.utf16()), // pipe name
180                  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,       // read/write access
181                  PIPE_TYPE_BYTE |          // byte type pipe
182                  PIPE_READMODE_BYTE |      // byte-read mode
183                  PIPE_WAIT,                // blocking mode
184                  PIPE_UNLIMITED_INSTANCES, // max. instances
185                  BUFSIZE,                  // output buffer size
186                  BUFSIZE,                  // input buffer size
187                  3000,                     // client time-out
188                  &sa);
189 
190     if (listener.handle == INVALID_HANDLE_VALUE) {
191         setError(QLatin1String("QLocalServerPrivate::addListener"));
192         listeners.removeLast();
193         return false;
194     }
195 
196     if (worldSID)
197         FreeSid(worldSID);
198 
199     memset(&listener.overlapped, 0, sizeof(listener.overlapped));
200     listener.overlapped.hEvent = eventHandle;
201 
202     // Beware! ConnectNamedPipe will reset the eventHandle to non-signaled.
203     // Callers of addListener must check all listeners for connections.
204     if (!ConnectNamedPipe(listener.handle, &listener.overlapped)) {
205         switch (GetLastError()) {
206         case ERROR_IO_PENDING:
207             listener.connected = false;
208             break;
209         case ERROR_PIPE_CONNECTED:
210             listener.connected = true;
211             break;
212         default:
213             CloseHandle(listener.handle);
214             setError(QLatin1String("QLocalServerPrivate::addListener"));
215             listeners.removeLast();
216             return false;
217         }
218     } else {
219         Q_ASSERT_X(false, "QLocalServerPrivate::addListener", "The impossible happened");
220         SetEvent(eventHandle);
221     }
222     return true;
223 }
224 
setError(const QString & function)225 void QLocalServerPrivate::setError(const QString &function)
226 {
227     int windowsError = GetLastError();
228     errorString = QString::fromLatin1("%1: %2").arg(function, qt_error_string(windowsError));
229     error = QAbstractSocket::UnknownSocketError;
230 }
231 
init()232 void QLocalServerPrivate::init()
233 {
234 }
235 
removeServer(const QString & name)236 bool QLocalServerPrivate::removeServer(const QString &name)
237 {
238     Q_UNUSED(name);
239     return true;
240 }
241 
listen(const QString & name)242 bool QLocalServerPrivate::listen(const QString &name)
243 {
244     Q_Q(QLocalServer);
245 
246     const QLatin1String pipePath("\\\\.\\pipe\\");
247     if (name.startsWith(pipePath))
248         fullServerName = name;
249     else
250         fullServerName = pipePath + name;
251 
252     // Use only one event for all listeners of one socket.
253     // The idea is that listener events are rare, so polling all listeners once in a while is
254     // cheap compared to waiting for N additional events in each iteration of the main loop.
255     eventHandle = CreateEvent(NULL, TRUE, FALSE, NULL);
256     connectionEventNotifier = new QWinEventNotifier(eventHandle , q);
257     q->connect(connectionEventNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_onNewConnection()));
258 
259     for (int i = 0; i < SYSTEM_MAX_PENDING_SOCKETS; ++i)
260         if (!addListener())
261             return false;
262 
263     _q_onNewConnection();
264     return true;
265 }
266 
listen(qintptr)267 bool QLocalServerPrivate::listen(qintptr)
268 {
269     qWarning("QLocalServer::listen(qintptr) is not supported on Windows QTBUG-24230");
270     return false;
271 }
272 
_q_onNewConnection()273 void QLocalServerPrivate::_q_onNewConnection()
274 {
275     Q_Q(QLocalServer);
276     DWORD dummy;
277     bool tryAgain;
278     do {
279         tryAgain = false;
280 
281         // Reset first, otherwise we could reset an event which was asserted
282         // immediately after we checked the conn status.
283         ResetEvent(eventHandle);
284 
285         // Testing shows that there is indeed absolutely no guarantee which listener gets
286         // a client connection first, so there is no way around polling all of them.
287         for (int i = 0; i < listeners.size(); ) {
288             HANDLE handle = listeners[i].handle;
289             if (listeners[i].connected
290                 || GetOverlappedResult(handle, &listeners[i].overlapped, &dummy, FALSE))
291             {
292                 listeners.removeAt(i);
293 
294                 addListener();
295 
296                 if (pendingConnections.size() > maxPendingConnections)
297                     connectionEventNotifier->setEnabled(false);
298                 else
299                     tryAgain = true;
300 
301                 // Make this the last thing so connected slots can wreak the least havoc
302                 q->incomingConnection(reinterpret_cast<quintptr>(handle));
303             } else {
304                 if (GetLastError() != ERROR_IO_INCOMPLETE) {
305                     q->close();
306                     setError(QLatin1String("QLocalServerPrivate::_q_onNewConnection"));
307                     return;
308                 }
309 
310                 ++i;
311             }
312         }
313     } while (tryAgain);
314 }
315 
closeServer()316 void QLocalServerPrivate::closeServer()
317 {
318     connectionEventNotifier->setEnabled(false); // Otherwise, closed handle is checked before deleter runs
319     connectionEventNotifier->deleteLater();
320     connectionEventNotifier = 0;
321     CloseHandle(eventHandle);
322     for (int i = 0; i < listeners.size(); ++i)
323         CloseHandle(listeners[i].handle);
324     listeners.clear();
325 }
326 
waitForNewConnection(int msecs,bool * timedOut)327 void QLocalServerPrivate::waitForNewConnection(int msecs, bool *timedOut)
328 {
329     Q_Q(QLocalServer);
330     if (!pendingConnections.isEmpty() || !q->isListening())
331         return;
332 
333     DWORD result = WaitForSingleObject(eventHandle, (msecs == -1) ? INFINITE : msecs);
334     if (result == WAIT_TIMEOUT) {
335         if (timedOut)
336             *timedOut = true;
337     } else {
338         _q_onNewConnection();
339     }
340 }
341 
342 QT_END_NAMESPACE
343