This code is part of my Google Summer of Code project. It will be used in a file manager to provide file management in a root-owned directory.
As the title suggests this will be used to share file descriptor using
unix local domain socket. There are two classes: FdReceiver and FdSender. FdReceiver acts as the server and will be used in the under-privileged process. FdSender is the client and will be used in the privileged/root-owned process.
Header File:
#ifndef SHAREFD_H
#define SHAREFD_H
#include <QSocketNotifier>
class FdReceiver : public QObject
{
Q_OBJECT
public:
FdReceiver(QObject *parent = nullptr);
bool startListening(const QString &path);
void stopListening();
int fileDescriptor() const;
private:
Q_SLOT void receiveFileDescriptor();
QSocketNotifier *m_readNotifier;
int m_socketDes;
int m_fileDes;
};
class FdSender
{
public:
FdSender();
void connectToPath(const std::string &path);
bool sendFileDescriptor(int fd);
bool isConnected() const;
private:
int m_socketDes;
bool m_connected;
};
#endif
Source file:
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "sharefd.h"
class KSockaddrUn
{
sockaddr_un addr;
int addrlen;
public:
KSockaddrUn(const std::string &path)
{
addrlen = sizeof(sockaddr_un);
memset(&addr, 0, addrlen);
addr.sun_family = AF_UNIX;
std::string finalPath = "/tmp/" + path;
#ifdef __linux__
strcpy(&addr.sun_path[1], finalPath.c_str());
#else
strcpy(&addr.sun_path, finalPath.c_str());
unlink(finalPath.c_str());
#endif
}
int length() const
{
return addrlen;
}
const sockaddr *address()
{
return reinterpret_cast<sockaddr*>(&addr);
}
};
class KMsgHdr
{
char io_buf[2];
char cmsg_buf[CMSG_SPACE(sizeof(int))];
msghdr msg;
iovec io;
public:
KMsgHdr()
{
io.iov_base = io_buf;
io.iov_len = 2;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
}
msghdr *message()
{
return &msg;
}
cmsghdr *cmsgHeader() const
{
return CMSG_FIRSTHDR(&msg);
}
};
// File descriptor reciever
FdReceiver::FdReceiver(QObject *parent)
: QObject(parent)
, m_socketDes(-1)
, m_fileDes(-1)
{
}
bool FdReceiver::startListening(const QString &path)
{
if (path.isEmpty())
return false;
m_socketDes = ::socket(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (m_socketDes == -1)
return false;
KSockaddrUn addr(path.toStdString());
bool bound = bind(m_socketDes, addr.address(), addr.length()) != -1;
bool listening = listen(m_socketDes, 5) != -1;
if (!bound || !listening) {
::close(m_socketDes);
return false;
}
m_readNotifier = new QSocketNotifier(m_socketDes, QSocketNotifier::Read, this);
connect(m_readNotifier, &QSocketNotifier::activated, this, &FdReceiver::receiveFileDescriptor);
return true;
}
void FdReceiver::receiveFileDescriptor()
{
int client = ::accept(m_socketDes, NULL, NULL);
if (client > 0) {
KMsgHdr msg;
if (recvmsg(client, msg.message(), 0) == 2) {
cmsghdr *cmsg = msg.cmsgHeader();
memcpy(&m_fileDes, (int*)CMSG_DATA(cmsg), sizeof(int));
m_readNotifier->setEnabled(false);
}
::close(client);
}
::close(m_socketDes);
}
void FdReceiver::stopListening()
{
if (m_readNotifier->isEnabled()) {
m_readNotifier->setEnabled(false);
::close(m_socketDes);
}
}
int FdReceiver::fileDescriptor() const
{
return m_fileDes;
}
// File descriptor sender
FdSender::FdSender()
: m_socketDes(-1)
, m_connected(false)
{
}
void FdSender::connectToPath(const std::string &path)
{
if (path.empty())
return;
m_socketDes = ::socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
if (m_socketDes == -1)
return;
KSockaddrUn addr(path);
bool connected = ::connect(m_socketDes, addr.address(), addr.length()) == 0;
if (!connected)
close(m_socketDes);
m_connected = true;
}
bool FdSender::sendFileDescriptor(int fd)
{
KMsgHdr msg;
cmsghdr *cmsg = msg.cmsgHeader();
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_level = SOL_SOCKET;
memcpy((int*)CMSG_DATA(cmsg), &fd, sizeof(int));
bool success = sendmsg(m_socketDes, msg.message(), 0) == 2;
close(m_socketDes);
return success;
}
bool FdSender::isConnected() const
{
return m_connected;
}
Using FdReceiver:
if (!file.isOpen()) {
int src_fd = -1;
FdReceiver fdRecv;
bool _continue = ( fdRecv.startListening(QStringLiteral("org_kde_kio_file_helper_socket"))
&& /*execute the root owned helper and open the file*/
&& (src_fd = fdRecv.fileDescriptor()) != -1
&& /*open file using src_fd*/);
if (!_continue) {
error();
return;
}
}
Using FdSender (in helper):
bool sendFileDescriptor(int fd, const char *socketPath)
{
FdSender fdSender;
fdSender.connectToPath(socketPath);
if (fdSender.isConnected()) {
fdSender.sendFileDescriptor(fd);
return true;
}
return false;
}
I would really appreciate some feedback regarding the code (both the classes and their example usage).
The coding style, class names KMsgHdr and KSockaddrUn are completely fine, in case it strikes someone as odd.