Line data Source code
1 : /*
2 : * If not stated otherwise in this file or this component's LICENSE file the
3 : * following copyright and licenses apply:
4 : *
5 : * Copyright 2025 Sky UK
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : #include "NamedSocket.h"
21 : #include "IpcLogging.h"
22 : #include <cstdint>
23 : #include <grp.h>
24 : #include <pwd.h>
25 : #include <stdexcept>
26 : #include <sys/file.h>
27 : #include <sys/socket.h>
28 : #include <sys/stat.h>
29 : #include <sys/un.h>
30 : #include <unistd.h>
31 : #include <utility>
32 : #include <vector>
33 :
34 : namespace
35 : {
36 : constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner
37 : constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group
38 : constexpr size_t kDefaultBufferSize = 4096; // Fallback buffer size
39 : } // namespace
40 :
41 : namespace firebolt::rialto::ipc
42 : {
43 6 : INamedSocketFactory &INamedSocketFactory::getFactory()
44 : {
45 6 : static NamedSocketFactory factory;
46 6 : return factory;
47 : }
48 :
49 3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket() const
50 : try
51 : {
52 3 : return std::make_unique<NamedSocket>();
53 : }
54 0 : catch (const std::runtime_error &error)
55 : {
56 0 : RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
57 0 : return nullptr;
58 : }
59 :
60 3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket(const std::string &socketPath) const
61 : try
62 : {
63 3 : return std::make_unique<NamedSocket>(socketPath);
64 : }
65 0 : catch (const std::runtime_error &error)
66 : {
67 0 : RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
68 0 : return nullptr;
69 : }
70 :
71 3 : NamedSocket::NamedSocket()
72 : {
73 3 : RIALTO_IPC_LOG_MIL("Creating new socket without binding");
74 :
75 : // Create the socket
76 3 : m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
77 3 : if (m_sockFd == -1)
78 : {
79 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
80 0 : throw std::runtime_error("socket error");
81 : }
82 :
83 3 : RIALTO_IPC_LOG_MIL("Socket created, fd: %d", m_sockFd);
84 : }
85 :
86 3 : NamedSocket::NamedSocket(const std::string &socketPath)
87 : {
88 3 : RIALTO_IPC_LOG_MIL("Creating named socket with name: %s", socketPath.c_str());
89 3 : m_sockPath = socketPath;
90 :
91 : // Create the socket
92 3 : m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
93 3 : if (m_sockFd == -1)
94 : {
95 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
96 0 : throw std::runtime_error("socket error");
97 : }
98 :
99 : // get the socket lock
100 3 : if (!getSocketLock())
101 : {
102 0 : closeListeningSocket();
103 0 : throw std::runtime_error("lock error");
104 : }
105 :
106 : // bind to the given path
107 3 : struct sockaddr_un addr = {0};
108 3 : memset(&addr, 0x00, sizeof(addr));
109 3 : addr.sun_family = AF_UNIX;
110 3 : strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
111 :
112 3 : if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
113 : {
114 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
115 :
116 0 : closeListeningSocket();
117 0 : throw std::runtime_error("bind error");
118 : }
119 :
120 3 : RIALTO_IPC_LOG_MIL("Named socket with name: %s created, fd: %d", m_sockPath.c_str(), m_sockFd);
121 : }
122 :
123 12 : NamedSocket::~NamedSocket()
124 : {
125 6 : RIALTO_IPC_LOG_MIL("Close named socket with name: %s, fd: %d", m_sockPath.c_str(), m_sockFd);
126 6 : closeListeningSocket();
127 12 : }
128 :
129 4 : int NamedSocket::getFd() const
130 : {
131 4 : return m_sockFd;
132 : }
133 :
134 1 : bool NamedSocket::setSocketPermissions(unsigned int socketPermissions) const
135 : {
136 1 : errno = 0;
137 1 : if (chmod(m_sockPath.c_str(), socketPermissions) != 0)
138 : {
139 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the permissions on the IPC socket");
140 0 : return false;
141 : }
142 1 : return true;
143 : }
144 :
145 1 : bool NamedSocket::setSocketOwnership(const std::string &socketOwner, const std::string &socketGroup) const
146 : {
147 1 : uid_t ownerId = getSocketOwnerId(socketOwner);
148 1 : gid_t groupId = getSocketGroupId(socketGroup);
149 :
150 1 : if (ownerId != kNoOwnerChange || groupId != kNoGroupChange)
151 : {
152 1 : errno = 0;
153 1 : if (chown(m_sockPath.c_str(), ownerId, groupId) != 0)
154 : {
155 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the owner/group for the IPC socket");
156 : }
157 : }
158 1 : return true;
159 : }
160 :
161 2 : bool NamedSocket::blockNewConnections() const
162 : {
163 2 : if (m_sockPath.empty())
164 : {
165 1 : RIALTO_IPC_LOG_DEBUG("No need to block new connections - socket not configured");
166 1 : return true;
167 : }
168 1 : RIALTO_IPC_LOG_INFO("Block new connections for: %s", m_sockPath.c_str());
169 1 : if (listen(m_sockFd, 0) == -1)
170 : {
171 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "blockNewConnections: listen error");
172 0 : return false;
173 : }
174 1 : return true;
175 : }
176 :
177 3 : bool NamedSocket::bind(const std::string &socketPath)
178 : {
179 3 : if (!m_sockPath.empty())
180 : {
181 1 : RIALTO_IPC_LOG_DEBUG("no need to bind again");
182 1 : return true;
183 : }
184 2 : RIALTO_IPC_LOG_MIL("Binding socket with fd: %d with name: %s", m_sockFd, socketPath.c_str());
185 2 : m_sockPath = socketPath;
186 :
187 : // get the socket lock
188 2 : if (!getSocketLock())
189 : {
190 0 : closeListeningSocket();
191 0 : return false;
192 : }
193 :
194 : // bind to the given path
195 2 : struct sockaddr_un addr = {0};
196 2 : memset(&addr, 0x00, sizeof(addr));
197 2 : addr.sun_family = AF_UNIX;
198 2 : strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
199 :
200 2 : if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
201 : {
202 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
203 :
204 0 : closeListeningSocket();
205 0 : return false;
206 : }
207 :
208 2 : RIALTO_IPC_LOG_MIL("Named socket with fd: %d bound with path: %s", m_sockFd, m_sockPath.c_str());
209 :
210 2 : return true;
211 : }
212 :
213 6 : void NamedSocket::closeListeningSocket()
214 : {
215 6 : if (!m_sockPath.empty() && (unlink(m_sockPath.c_str()) != 0) && (errno != ENOENT))
216 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket @ '%s'", m_sockPath.c_str());
217 6 : if ((m_sockFd >= 0) && (close(m_sockFd) != 0))
218 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close listening socket");
219 :
220 6 : if (!m_lockPath.empty() && (unlink(m_lockPath.c_str()) != 0) && (errno != ENOENT))
221 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket lock file @ '%s'", m_lockPath.c_str());
222 6 : if ((m_lockFd >= 0) && (close(m_lockFd) != 0))
223 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close socket lock file");
224 :
225 6 : m_sockFd = -1;
226 6 : m_sockPath.clear();
227 :
228 6 : m_lockFd = -1;
229 6 : m_lockPath.clear();
230 : }
231 :
232 5 : bool NamedSocket::getSocketLock()
233 : {
234 5 : std::string lockPath = m_sockPath + ".lock";
235 5 : int fd = open(lockPath.c_str(), O_CREAT | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
236 5 : if (fd < 0)
237 : {
238 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to create / open lockfile @ '%s' (check permissions)", lockPath.c_str());
239 0 : return false;
240 : }
241 :
242 5 : if (flock(fd, LOCK_EX | LOCK_NB) < 0)
243 : {
244 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to lock lockfile @ '%s', maybe another server is running",
245 : lockPath.c_str());
246 0 : close(fd);
247 0 : return false;
248 : }
249 :
250 5 : struct stat sbuf = {0};
251 5 : if (stat(m_sockPath.c_str(), &sbuf) < 0)
252 : {
253 5 : if (errno != ENOENT)
254 : {
255 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "did not manage to stat existing socket @ '%s'", m_sockPath.c_str());
256 0 : close(fd);
257 0 : return false;
258 : }
259 : }
260 0 : else if ((sbuf.st_mode & S_IWUSR) || (sbuf.st_mode & S_IWGRP))
261 : {
262 0 : unlink(m_sockPath.c_str());
263 : }
264 :
265 5 : m_lockFd = fd;
266 5 : m_lockPath = std::move(lockPath);
267 :
268 5 : return true;
269 : }
270 :
271 1 : uid_t NamedSocket::getSocketOwnerId(const std::string &socketOwner) const
272 : {
273 1 : uid_t ownerId = kNoOwnerChange;
274 1 : if (!socketOwner.empty())
275 : {
276 1 : int64_t bufferSizeLong = sysconf(_SC_GETPW_R_SIZE_MAX);
277 1 : const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast<size_t>(bufferSizeLong) : kDefaultBufferSize;
278 :
279 1 : errno = 0;
280 1 : passwd passwordStruct{};
281 1 : passwd *passwordResult = nullptr;
282 1 : std::vector<char> buffer(kBufferSize);
283 1 : int result = getpwnam_r(socketOwner.c_str(), &passwordStruct, buffer.data(), buffer.size(), &passwordResult);
284 1 : if (result == 0)
285 : {
286 1 : if (passwordResult)
287 : {
288 1 : ownerId = passwordResult->pw_uid;
289 : }
290 : else
291 : {
292 0 : RIALTO_IPC_LOG_WARN("Owner name '%s' not found", socketOwner.c_str());
293 : }
294 : }
295 : else
296 : {
297 0 : RIALTO_IPC_LOG_SYS_WARN(result, "Failed to lookup ownerId for '%s'", socketOwner.c_str());
298 : }
299 1 : }
300 1 : return ownerId;
301 : }
302 :
303 1 : gid_t NamedSocket::getSocketGroupId(const std::string &socketGroup) const
304 : {
305 1 : gid_t groupId = kNoGroupChange;
306 1 : if (!socketGroup.empty())
307 : {
308 1 : int64_t bufferSizeLong = sysconf(_SC_GETGR_R_SIZE_MAX);
309 1 : const size_t kBufferSize = (bufferSizeLong > 0) ? static_cast<size_t>(bufferSizeLong) : kDefaultBufferSize;
310 :
311 1 : errno = 0;
312 1 : group groupStruct{};
313 1 : group *groupResult = nullptr;
314 1 : std::vector<char> buffer(kBufferSize);
315 1 : int result = getgrnam_r(socketGroup.c_str(), &groupStruct, buffer.data(), buffer.size(), &groupResult);
316 1 : if (result == 0)
317 : {
318 1 : if (groupResult)
319 : {
320 1 : groupId = groupResult->gr_gid;
321 : }
322 : else
323 : {
324 0 : RIALTO_IPC_LOG_WARN("Group name '%s' not found", socketGroup.c_str());
325 : }
326 : }
327 : else
328 : {
329 0 : RIALTO_IPC_LOG_SYS_WARN(result, "Failed to lookup groupId for '%s'", socketGroup.c_str());
330 : }
331 1 : }
332 1 : return groupId;
333 : }
334 : } // namespace firebolt::rialto::ipc
|