254 lines
6.9 KiB
C++
254 lines
6.9 KiB
C++
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) Robin E.R. Davies
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
* the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
* of the Software, and to permit persons to whom the Software is furnished to do
|
|
* so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
#include "UnixSocket.hpp"
|
|
#include <atomic>
|
|
#include <filesystem>
|
|
#include <grp.h>
|
|
#include <stdexcept>
|
|
#include "gsl/util"
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <sys/un.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include "ss.hpp"
|
|
|
|
using namespace std;
|
|
namespace pipedal
|
|
{
|
|
namespace detail
|
|
{
|
|
class UnixSocketData
|
|
{
|
|
public:
|
|
UnixSocketData()
|
|
{
|
|
memset(&localAddress, 0, sizeof(localAddress));
|
|
memset(&remoteAddress, 0, sizeof(remoteAddress));
|
|
localAddress.sun_family = AF_UNIX;
|
|
remoteAddress.sun_family = AF_UNIX;
|
|
}
|
|
int socket = -1;
|
|
struct sockaddr_un localAddress;
|
|
struct sockaddr_un remoteAddress;
|
|
};
|
|
}
|
|
}
|
|
using namespace pipedal;
|
|
using namespace pipedal::detail;
|
|
using namespace gsl;
|
|
|
|
|
|
bool UnixSocket::IsOpen() const { return data->socket != -1; }
|
|
|
|
UnixSocketException::UnixSocketException(int errno_)
|
|
: what_(strerror(errno_)), errno_(errno_)
|
|
{
|
|
}
|
|
|
|
UnixSocketException::UnixSocketException(const std::string what_, int errno_)
|
|
: what_(what_ + " " + strerror(errno_) + ")"), errno_(errno_)
|
|
{
|
|
}
|
|
UnixSocketException::UnixSocketException(const std::string what_)
|
|
: what_(what_), errno_(0)
|
|
{
|
|
}
|
|
|
|
void UnixSocket::Bind(const std::string &socketName, const std::string &permissionGroup)
|
|
{
|
|
if (socketName.length() >= sizeof(data->localAddress.sun_path) - 1)
|
|
{
|
|
throw UnixSocketException("Socket name too long.", E2BIG);
|
|
}
|
|
int s = socket(PF_UNIX, SOCK_DGRAM, 0);
|
|
auto socket_cleanup = finally([&]() {
|
|
if (s != -1)
|
|
{
|
|
close(s);
|
|
s = -1;
|
|
}
|
|
});
|
|
strcpy(data->localAddress.sun_path, socketName.c_str());
|
|
|
|
for (int retry = 0; /**/; ++retry)
|
|
{
|
|
int err = bind(s, (struct sockaddr *)&data->localAddress, sizeof(data->localAddress));
|
|
|
|
if (err < 0)
|
|
{
|
|
if (retry != 2)
|
|
{
|
|
unlink(data->localAddress.sun_path);
|
|
continue;
|
|
}
|
|
throw UnixSocketException("Can't bind socket " + socketName, errno);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (permissionGroup != "")
|
|
{
|
|
gid_t gid;
|
|
struct group *group_;
|
|
group_ = getgrnam(permissionGroup.c_str());
|
|
if (group_ == nullptr)
|
|
{
|
|
throw UnixSocketException("Group not found.");
|
|
}
|
|
gid = group_->gr_gid;
|
|
|
|
std::ignore = chown(data->localAddress.sun_path, -1, gid);
|
|
std::ignore = chmod(data->localAddress.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
|
|
}
|
|
|
|
data->socket = s;
|
|
s = -1;
|
|
}
|
|
|
|
UnixSocket::UnixSocket()
|
|
{
|
|
data = new UnixSocketData();
|
|
}
|
|
|
|
void UnixSocket::Close()
|
|
{
|
|
if (data->socket != -1)
|
|
{
|
|
close(data->socket);
|
|
unlink(data->localAddress.sun_path);
|
|
}
|
|
}
|
|
UnixSocket::~UnixSocket()
|
|
{
|
|
Close();
|
|
delete data;
|
|
}
|
|
|
|
static std::atomic<uint64_t> instanceId;
|
|
|
|
void UnixSocket::Connect(const std::string &remoteSocketName, const std::string &permissionGroup)
|
|
{
|
|
|
|
const std::string localSocketName =
|
|
SS("/tmp/ppadmin-" << getpid() << "-" << (instanceId++));
|
|
|
|
if (localSocketName.length() >= sizeof(data->localAddress.sun_path) - 1)
|
|
{
|
|
throw UnixSocketException("Local socket path is too long. ", E2BIG);
|
|
}
|
|
strcpy(data->localAddress.sun_path, localSocketName.c_str());
|
|
|
|
int s = socket(PF_UNIX, SOCK_DGRAM, 0);
|
|
auto socket_cleanup = finally([&]() {
|
|
if (s != -1)
|
|
{
|
|
close(s);
|
|
s = -1;
|
|
unlink(data->localAddress.sun_path);
|
|
}
|
|
});
|
|
|
|
int e = bind(s, (sockaddr *)&data->localAddress, sizeof(data->localAddress));
|
|
|
|
if (permissionGroup.length() != 0)
|
|
{
|
|
struct group *g = getgrnam(permissionGroup.c_str());
|
|
if (g == nullptr)
|
|
{
|
|
throw UnixSocketException("Group not found");
|
|
}
|
|
gid_t gid = g->gr_gid;
|
|
|
|
std::ignore = chown(data->localAddress.sun_path, -1, gid);
|
|
std::ignore = chmod(data->localAddress.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
|
|
}
|
|
|
|
strcpy(data->remoteAddress.sun_path, remoteSocketName.c_str());
|
|
e = connect(s, (sockaddr *)&data->remoteAddress, sizeof(data->remoteAddress));
|
|
if (e < 0)
|
|
{
|
|
throw UnixSocketException("Can't connect to socket " + remoteSocketName, errno);
|
|
}
|
|
data->socket = s;
|
|
s = -1;
|
|
}
|
|
|
|
size_t UnixSocket::Send(const void *buffer, size_t length)
|
|
{
|
|
ssize_t size = send(data->socket, buffer, length, 0);
|
|
if (size < 0)
|
|
{
|
|
throw UnixSocketException(errno);
|
|
}
|
|
return (size_t)size;
|
|
}
|
|
|
|
size_t UnixSocket::Receive(void *buffer, size_t length)
|
|
{
|
|
ssize_t received = recv(data->socket, buffer, length, 0);
|
|
if (received < 0)
|
|
{
|
|
throw UnixSocketException(errno);
|
|
}
|
|
return (size_t)received;
|
|
}
|
|
|
|
size_t UnixSocket::ReceiveFrom(void *buffer, size_t length, std::string *pFrom)
|
|
{
|
|
sockaddr_un sockAddr;
|
|
socklen_t sockAddrLen = sizeof(sockAddr);
|
|
|
|
ssize_t received = recvfrom(data->socket, buffer, length, 0, (sockaddr *)&sockAddr, &sockAddrLen);
|
|
if (received < 0)
|
|
{
|
|
throw UnixSocketException(errno);
|
|
}
|
|
*pFrom = std::string(sockAddr.sun_path);
|
|
return (size_t)received;
|
|
}
|
|
|
|
size_t UnixSocket::SendTo(const void *buffer, size_t length, const std::string &toAddress)
|
|
{
|
|
sockaddr_un sockAddr;
|
|
if (toAddress.length() >= sizeof(sockAddr.sun_path) - 1)
|
|
{
|
|
throw UnixSocketException("Address too long.", E2BIG);
|
|
}
|
|
strcpy(sockAddr.sun_path, toAddress.c_str());
|
|
sockAddr.sun_family = AF_UNIX;
|
|
|
|
ssize_t size = sendto(data->socket, buffer, length, 0, (sockaddr *)&sockAddr, sizeof(sockaddr_un));
|
|
if (size < 0)
|
|
{
|
|
throw UnixSocketException(errno);
|
|
}
|
|
return (size_t)size;
|
|
}
|