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 :
33 : namespace
34 : {
35 : constexpr uid_t kNoOwnerChange = -1; // -1 means chown() won't change the owner
36 : constexpr gid_t kNoGroupChange = -1; // -1 means chown() won't change the group
37 : } // namespace
38 :
39 : namespace firebolt::rialto::ipc
40 : {
41 6 : INamedSocketFactory &INamedSocketFactory::getFactory()
42 : {
43 6 : static NamedSocketFactory factory;
44 6 : return factory;
45 : }
46 :
47 3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket() const
48 : try
49 : {
50 3 : return std::make_unique<NamedSocket>();
51 : }
52 0 : catch (const std::runtime_error &error)
53 : {
54 0 : RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
55 0 : return nullptr;
56 : }
57 :
58 3 : std::unique_ptr<INamedSocket> NamedSocketFactory::createNamedSocket(const std::string &socketPath) const
59 : try
60 : {
61 3 : return std::make_unique<NamedSocket>(socketPath);
62 : }
63 0 : catch (const std::runtime_error &error)
64 : {
65 0 : RIALTO_IPC_LOG_ERROR("Failed to create named socket: %s", error.what());
66 0 : return nullptr;
67 : }
68 :
69 3 : NamedSocket::NamedSocket()
70 : {
71 3 : RIALTO_IPC_LOG_MIL("Creating new socket without binding");
72 :
73 : // Create the socket
74 3 : m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
75 3 : if (m_sockFd == -1)
76 : {
77 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
78 0 : throw std::runtime_error("socket error");
79 : }
80 :
81 3 : RIALTO_IPC_LOG_MIL("Socket created, fd: %d", m_sockFd);
82 : }
83 :
84 3 : NamedSocket::NamedSocket(const std::string &socketPath)
85 : {
86 3 : RIALTO_IPC_LOG_MIL("Creating named socket with name: %s", socketPath.c_str());
87 3 : m_sockPath = socketPath;
88 :
89 : // Create the socket
90 3 : m_sockFd = ::socket(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
91 3 : if (m_sockFd == -1)
92 : {
93 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "socket error");
94 0 : throw std::runtime_error("socket error");
95 : }
96 :
97 : // get the socket lock
98 3 : if (!getSocketLock())
99 : {
100 0 : closeListeningSocket();
101 0 : throw std::runtime_error("lock error");
102 : }
103 :
104 : // bind to the given path
105 3 : struct sockaddr_un addr = {0};
106 3 : memset(&addr, 0x00, sizeof(addr));
107 3 : addr.sun_family = AF_UNIX;
108 3 : strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
109 :
110 3 : if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
111 : {
112 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
113 :
114 0 : closeListeningSocket();
115 0 : throw std::runtime_error("bind error");
116 : }
117 :
118 3 : RIALTO_IPC_LOG_MIL("Named socket with name: %s created, fd: %d", m_sockPath.c_str(), m_sockFd);
119 : }
120 :
121 12 : NamedSocket::~NamedSocket()
122 : {
123 6 : RIALTO_IPC_LOG_MIL("Close named socket with name: %s, fd: %d", m_sockPath.c_str(), m_sockFd);
124 6 : closeListeningSocket();
125 12 : }
126 :
127 4 : int NamedSocket::getFd() const
128 : {
129 4 : return m_sockFd;
130 : }
131 :
132 1 : bool NamedSocket::setSocketPermissions(unsigned int socketPermissions) const
133 : {
134 1 : errno = 0;
135 1 : if (chmod(m_sockPath.c_str(), socketPermissions) != 0)
136 : {
137 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the permissions on the IPC socket");
138 0 : return false;
139 : }
140 1 : return true;
141 : }
142 :
143 1 : bool NamedSocket::setSocketOwnership(const std::string &socketOwner, const std::string &socketGroup) const
144 : {
145 1 : uid_t ownerId = getSocketOwnerId(socketOwner);
146 1 : gid_t groupId = getSocketGroupId(socketGroup);
147 :
148 1 : if (ownerId != kNoOwnerChange || groupId != kNoGroupChange)
149 : {
150 1 : errno = 0;
151 1 : if (chown(m_sockPath.c_str(), ownerId, groupId) != 0)
152 : {
153 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to change the owner/group for the IPC socket");
154 : }
155 : }
156 1 : return true;
157 : }
158 :
159 2 : bool NamedSocket::blockNewConnections() const
160 : {
161 2 : if (m_sockPath.empty())
162 : {
163 1 : RIALTO_IPC_LOG_DEBUG("No need to block new connections - socket not configured");
164 1 : return true;
165 : }
166 1 : RIALTO_IPC_LOG_INFO("Block new connections for: %s", m_sockPath.c_str());
167 1 : if (listen(m_sockFd, 0) == -1)
168 : {
169 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "blockNewConnections: listen error");
170 0 : return false;
171 : }
172 1 : return true;
173 : }
174 :
175 3 : bool NamedSocket::bind(const std::string &socketPath)
176 : {
177 3 : if (!m_sockPath.empty())
178 : {
179 1 : RIALTO_IPC_LOG_DEBUG("no need to bind again");
180 1 : return true;
181 : }
182 2 : RIALTO_IPC_LOG_MIL("Binding socket with fd: %d with name: %s", m_sockFd, socketPath.c_str());
183 2 : m_sockPath = socketPath;
184 :
185 : // get the socket lock
186 2 : if (!getSocketLock())
187 : {
188 0 : closeListeningSocket();
189 0 : return false;
190 : }
191 :
192 : // bind to the given path
193 2 : struct sockaddr_un addr = {0};
194 2 : memset(&addr, 0x00, sizeof(addr));
195 2 : addr.sun_family = AF_UNIX;
196 2 : strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
197 :
198 2 : if (::bind(m_sockFd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) == -1)
199 : {
200 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "bind error");
201 :
202 0 : closeListeningSocket();
203 0 : return false;
204 : }
205 :
206 2 : RIALTO_IPC_LOG_MIL("Named socket with fd: %d bound with path: %s", m_sockFd, m_sockPath.c_str());
207 :
208 2 : return true;
209 : }
210 :
211 6 : void NamedSocket::closeListeningSocket()
212 : {
213 6 : if (!m_sockPath.empty() && (unlink(m_sockPath.c_str()) != 0) && (errno != ENOENT))
214 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket @ '%s'", m_sockPath.c_str());
215 6 : if ((m_sockFd >= 0) && (close(m_sockFd) != 0))
216 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close listening socket");
217 :
218 6 : if (!m_lockPath.empty() && (unlink(m_lockPath.c_str()) != 0) && (errno != ENOENT))
219 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to remove socket lock file @ '%s'", m_lockPath.c_str());
220 6 : if ((m_lockFd >= 0) && (close(m_lockFd) != 0))
221 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to close socket lock file");
222 :
223 6 : m_sockFd = -1;
224 6 : m_sockPath.clear();
225 :
226 6 : m_lockFd = -1;
227 6 : m_lockPath.clear();
228 : }
229 :
230 5 : bool NamedSocket::getSocketLock()
231 : {
232 5 : std::string lockPath = m_sockPath + ".lock";
233 5 : int fd = open(lockPath.c_str(), O_CREAT | O_CLOEXEC, (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
234 5 : if (fd < 0)
235 : {
236 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to create / open lockfile @ '%s' (check permissions)", lockPath.c_str());
237 0 : return false;
238 : }
239 :
240 5 : if (flock(fd, LOCK_EX | LOCK_NB) < 0)
241 : {
242 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "failed to lock lockfile @ '%s', maybe another server is running",
243 : lockPath.c_str());
244 0 : close(fd);
245 0 : return false;
246 : }
247 :
248 5 : struct stat sbuf = {0};
249 5 : if (stat(m_sockPath.c_str(), &sbuf) < 0)
250 : {
251 5 : if (errno != ENOENT)
252 : {
253 0 : RIALTO_IPC_LOG_SYS_ERROR(errno, "did not manage to stat existing socket @ '%s'", m_sockPath.c_str());
254 0 : close(fd);
255 0 : return false;
256 : }
257 : }
258 0 : else if ((sbuf.st_mode & S_IWUSR) || (sbuf.st_mode & S_IWGRP))
259 : {
260 0 : unlink(m_sockPath.c_str());
261 : }
262 :
263 5 : m_lockFd = fd;
264 5 : m_lockPath = std::move(lockPath);
265 :
266 5 : return true;
267 : }
268 :
269 1 : uid_t NamedSocket::getSocketOwnerId(const std::string &socketOwner) const
270 : {
271 1 : uid_t ownerId = kNoOwnerChange;
272 : // sysconf returns long; -1 on error. Store as int64_t to avoid unsigned conversion issues.
273 1 : const int64_t bufferSizeLong = sysconf(_SC_GETPW_R_SIZE_MAX);
274 1 : if (!socketOwner.empty() && bufferSizeLong > 0)
275 : {
276 1 : const size_t kBufferSize = static_cast<size_t>(bufferSizeLong);
277 1 : errno = 0;
278 1 : passwd passwordStruct{};
279 1 : passwd *passwordResult = nullptr;
280 1 : char buffer[kBufferSize];
281 1 : int result = getpwnam_r(socketOwner.c_str(), &passwordStruct, buffer, kBufferSize, &passwordResult);
282 1 : if (result == 0 && passwordResult)
283 : {
284 1 : ownerId = passwordResult->pw_uid;
285 : }
286 : else
287 : {
288 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine ownerId for '%s'", socketOwner.c_str());
289 : }
290 1 : }
291 1 : return ownerId;
292 : }
293 :
294 1 : gid_t NamedSocket::getSocketGroupId(const std::string &socketGroup) const
295 : {
296 1 : gid_t groupId = kNoGroupChange;
297 : // sysconf returns long; -1 on error. Store as int64_t to avoid unsigned conversion issues.
298 1 : const int64_t bufferSizeLong = sysconf(_SC_GETGR_R_SIZE_MAX);
299 1 : if (!socketGroup.empty() && bufferSizeLong > 0)
300 : {
301 1 : const size_t kBufferSize = static_cast<size_t>(bufferSizeLong);
302 1 : errno = 0;
303 1 : group groupStruct{};
304 1 : group *groupResult = nullptr;
305 1 : char buffer[kBufferSize];
306 1 : int result = getgrnam_r(socketGroup.c_str(), &groupStruct, buffer, kBufferSize, &groupResult);
307 1 : if (result == 0 && groupResult)
308 : {
309 1 : groupId = groupResult->gr_gid;
310 : }
311 : else
312 : {
313 0 : RIALTO_IPC_LOG_SYS_WARN(errno, "Failed to determine groupId for '%s'", socketGroup.c_str());
314 : }
315 1 : }
316 1 : return groupId;
317 : }
318 : } // namespace firebolt::rialto::ipc
|