Add socketOptions flags to QLocalServer

QLocalServer had no way to set socket options
that more complicated servers require. The
first set of options allow setting of access
control on the sockets.

Change-Id: If4268c66462fc2e6cf1e70b1d5f56c76d2c69228
Reviewed-by: Harald Fernengel <harald.fernengel@nokia.com>
This commit is contained in:
Andrew Stanley-Jones 2012-02-06 18:36:06 +01:00 committed by Qt by Nokia
parent 8522cb5c97
commit 49b5306155
5 changed files with 200 additions and 3 deletions

View File

@ -81,6 +81,27 @@ QT_BEGIN_NAMESPACE
\sa QLocalSocket, QTcpServer
*/
/*!
\enum QLocalServer::SocketOption
This enum describes the possible options that can be used to create the
socket. This changes the access permissions on platforms (Linux, Windows)
that support access permissions on the socket. Both GroupAccess and OtherAccess
may vary slightly in meanings depending on the platform.
\value UserAccess
Access is restricted to the same user as the process that created the socket.
\value GroupAccess
Access is restricted to the same group but not the user that created the socket on Linux.
\value OtherAccess
Access is available to everyone but the user and group that created the socket on Linux.
\value WorldAccess
No access restrictions.
\sa SocketOptions
*/
/*!
Create a new local socket server with the given \a parent.
@ -108,6 +129,48 @@ QLocalServer::~QLocalServer()
close();
}
/*!
\property QLocalServer::socketOptions
\since 5.0
The setSocketOptions method controls how the socket operates.
For example the socket may restrict access to what user ids can
connect to the socket.
These options must be set before listen() is called.
In some cases, such as with Unix domain sockets on Linux, the
access to the socket will be determined by file system permissions,
and are created based on the umask. Setting the access flags will
overide this and will restrict or permit access as specified.
Other Unix-based operating systems, such as Mac OS X, do not
honor file permissions for Unix domain sockets and by default
have WorldAccess and these permission flags will have no effect.
By default none of the flags are set, access permissions
are the platform default.
\sa listen()
*/
void QLocalServer::setSocketOptions(SocketOptions options)
{
Q_D(QLocalServer);
d->socketOptions = options;
}
/*!
Returns the socket options set on the socket.
\sa setSocketOptions()
*/
QLocalServer::SocketOptions QLocalServer::socketOptions() const
{
Q_D(const QLocalServer);
return d->socketOptions;
}
/*!
Stop listening for incoming connections. Existing connections are not
effected, but any new connections will be refused.

View File

@ -58,11 +58,22 @@ class Q_NETWORK_EXPORT QLocalServer : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QLocalServer)
Q_PROPERTY(SocketOptions socketOptions READ socketOptions WRITE setSocketOptions)
Q_FLAGS(SocketOption SocketOptions)
Q_SIGNALS:
void newConnection();
public:
enum SocketOption {
NoOptions = 0x0,
UserAccessOption = 0x01,
GroupAccessOption = 0x2,
OtherAccessOption = 0x4,
WorldAccessOption = 0x7
};
Q_DECLARE_FLAGS(SocketOptions, SocketOption)
QLocalServer(QObject *parent = 0);
~QLocalServer();
@ -80,6 +91,9 @@ public:
void setMaxPendingConnections(int numConnections);
bool waitForNewConnection(int msec = 0, bool *timedOut = 0);
void setSocketOptions(SocketOptions options);
SocketOptions socketOptions() const;
protected:
virtual void incomingConnection(quintptr socketDescriptor);
@ -88,6 +102,8 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_onNewConnection())
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QLocalServer::SocketOptions)
#endif // QT_NO_LOCALSERVER
QT_END_NAMESPACE

View File

@ -80,7 +80,8 @@ public:
#if !defined(QT_LOCALSOCKET_TCP) && !defined(Q_OS_WIN)
listenSocket(-1), socketNotifier(0),
#endif
maxPendingConnections(30), error(QAbstractSocket::UnknownSocketError)
maxPendingConnections(30), error(QAbstractSocket::UnknownSocketError),
socketOptions(QLocalServer::NoOptions)
{
}
@ -121,6 +122,7 @@ public:
QQueue<QLocalSocket*> pendingConnections;
QString errorString;
QAbstractSocket::SocketError error;
QLocalServer::SocketOptions socketOptions;
};
QT_END_NAMESPACE

View File

@ -44,6 +44,7 @@
#include "qlocalsocket.h"
#include "qlocalsocket_p.h"
#include "qnet_unix_p.h"
#include "qtemporarydir.h"
#ifndef QT_NO_LOCALSERVER
@ -92,6 +93,20 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
}
serverName = requestedServerName;
QString tempPath;
QScopedPointer<QTemporaryDir> tempDir;
// Check any of the flags
if (socketOptions & QLocalServer::WorldAccessOption) {
tempDir.reset(new QTemporaryDir(fullServerName));
if (!tempDir->isValid()) {
setError(QLatin1String("QLocalServer::listen"));
return false;
}
tempPath = tempDir->path();
tempPath += QLatin1Char('/') + requestedServerName;
}
// create the unix socket
listenSocket = qt_safe_socket(PF_UNIX, SOCK_STREAM, 0);
if (-1 == listenSocket) {
@ -108,8 +123,26 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
closeServer();
return false;
}
::memcpy(addr.sun_path, fullServerName.toLatin1().data(),
fullServerName.toLatin1().size() + 1);
if (socketOptions & QLocalServer::WorldAccessOption) {
if (sizeof(addr.sun_path) < (uint)tempPath.toLatin1().size() + 1) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
}
::memcpy(addr.sun_path, tempPath.toLatin1().data(),
tempPath.toLatin1().size() + 1);
if (-1 == ::fchmod(listenSocket, 0)) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
}
} else {
::memcpy(addr.sun_path, fullServerName.toLatin1().data(),
fullServerName.toLatin1().size() + 1);
}
// bind
if(-1 == QT_SOCKET_BIND(listenSocket, (sockaddr *)&addr, sizeof(sockaddr_un))) {
@ -133,6 +166,34 @@ bool QLocalServerPrivate::listen(const QString &requestedServerName)
QFile::remove(fullServerName);
return false;
}
if (socketOptions & QLocalServer::WorldAccessOption) {
mode_t mode = 000;
if (socketOptions & QLocalServer::UserAccessOption) {
mode |= S_IRWXU;
}
if (socketOptions & QLocalServer::GroupAccessOption) {
mode |= S_IRWXG;
}
if (socketOptions & QLocalServer::OtherAccessOption) {
mode |= S_IRWXO;
}
if (mode) {
if (-1 == ::chmod(tempPath.toLatin1(), mode)) {
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
}
}
if (-1 == ::rename(tempPath.toLatin1(), fullServerName.toLatin1())){
setError(QLatin1String("QLocalServer::listen"));
closeServer();
return false;
}
}
Q_ASSERT(!socketNotifier);
socketNotifier = new QSocketNotifier(listenSocket,
QSocketNotifier::Read, q);

View File

@ -48,6 +48,8 @@
Q_DECLARE_METATYPE(QLocalSocket::LocalSocketError)
Q_DECLARE_METATYPE(QLocalSocket::LocalSocketState)
Q_DECLARE_METATYPE(QLocalServer::SocketOption)
Q_DECLARE_METATYPE(QFile::Permissions)
class tst_QLocalSocket : public QObject
{
@ -104,12 +106,17 @@ private slots:
void bytesWrittenSignal();
void syncDisconnectNotify();
void asyncDisconnectNotify();
void verifySocketOptions();
void verifySocketOptions_data();
};
void tst_QLocalSocket::init()
{
qRegisterMetaType<QLocalSocket::LocalSocketState>("QLocalSocket::LocalSocketState");
qRegisterMetaType<QLocalSocket::LocalSocketError>("QLocalSocket::LocalSocketError");
qRegisterMetaType<QLocalServer::SocketOption>("QLocalServer::SocketOption");
qRegisterMetaType<QFile::Permissions>("QFile::Permissions");
}
void tst_QLocalSocket::cleanup()
@ -1010,6 +1017,54 @@ void tst_QLocalSocket::asyncDisconnectNotify()
QTRY_VERIFY(!disconnectedSpy.isEmpty());
}
void tst_QLocalSocket::verifySocketOptions_data()
{
#ifdef Q_OS_LINUX
QTest::addColumn<QString>("service");
QTest::addColumn<QLocalServer::SocketOption>("opts");
QTest::addColumn<QFile::Permissions>("perms");
QFile::Permissions p = QFile::ExeOwner|QFile::WriteOwner|QFile::ReadOwner |
QFile::ExeUser|QFile::WriteUser|QFile::ReadUser;
QTest::newRow("user") << "userPerms" << QLocalServer::UserAccess << p;
p = QFile::ExeGroup|QFile::WriteGroup|QFile::ReadGroup;
QTest::newRow("group") << "groupPerms" << QLocalServer::GroupAccess << p;
p = QFile::ExeOther|QFile::WriteOther|QFile::ReadOther;
QTest::newRow("other") << "otherPerms" << QLocalServer::OtherAccess << p;
p = QFile::ExeOwner|QFile::WriteOwner|QFile::ReadOwner|
QFile::ExeUser|QFile::WriteUser|QFile::ReadUser |
QFile::ExeGroup|QFile::WriteGroup|QFile::ReadGroup|
QFile::ExeOther|QFile::WriteOther|QFile::ReadOther;
QTest::newRow("all") << "worldPerms" << QLocalServer::WorldAccess << p;
#endif
}
void tst_QLocalSocket::verifySocketOptions()
{
// These are only guaranteed to be useful on linux at this time
#ifdef Q_OS_LINUX
QFETCH(QString, service);
QFETCH(QLocalServer::SocketOption, opts);
QFETCH(QFile::Permissions, perms);
QLocalServer::removeServer(service);
QLocalServer server;
server.setSocketOptions(opts);
QVERIFY2(server.listen(service), "service failed to start listening");
// find the socket
QString fullServerPath = QDir::cleanPath(QDir::tempPath());
fullServerPath += QLatin1Char('/') + service;
QFile socketFile(fullServerPath);
QVERIFY2(perms == socketFile.permissions(), "permissions on the socket don't match");
#endif
}
QTEST_MAIN(tst_QLocalSocket)
#include "tst_qlocalsocket.moc"