1 /*
2 * Systemd integration parts.
3 *
4 * Most of this file is directly copied from systemd sources.
5 * Changes:
6 * - all exported functions were renamed to have a netsnmp_ prefix
7 * - all nonexported functions were made static
8 * - includes were changed to match Net-SNMP style.
9 * - removed gcc export macros
10 * - removed POSIX message queues
11 * - removed log level macros
12 * - removed unused functions
13 * - made SD_LISTEN_FDS_START as it is only used internally
14 */
15
16 #include <net-snmp/net-snmp-config.h>
17 #include <net-snmp/net-snmp-features.h>
18 #include <net-snmp/types.h>
19 #include <net-snmp/library/tools.h>
20 #include <net-snmp/library/snmp_debug.h>
21
22 #ifndef NETSNMP_NO_SYSTEMD
23
24 /***
25 Copyright 2010 Lennart Poettering
26
27 Permission is hereby granted, free of charge, to any person
28 obtaining a copy of this software and associated documentation files
29 (the "Software"), to deal in the Software without restriction,
30 including without limitation the rights to use, copy, modify, merge,
31 publish, distribute, sublicense, and/or sell copies of the Software,
32 and to permit persons to whom the Software is furnished to do so,
33 subject to the following conditions:
34
35 The above copyright notice and this permission notice shall be
36 included in all copies or substantial portions of the Software.
37
38 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
39 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
40 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
41 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
42 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
43 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
44 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45 SOFTWARE.
46 ***/
47
48 #ifndef _GNU_SOURCE
49 #define _GNU_SOURCE
50 #endif
51
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <sys/socket.h>
55 #include <sys/un.h>
56 #include <sys/fcntl.h>
57 #include <netinet/in.h>
58 #include <stdlib.h>
59 #include <errno.h>
60 #include <unistd.h>
61 #include <string.h>
62 #include <stdarg.h>
63 #include <stdio.h>
64 #include <stddef.h>
65 #include <limits.h>
66
67 #include <net-snmp/library/sd-daemon.h>
68
69 /* The first passed file descriptor is fd 3 */
70 #define SD_LISTEN_FDS_START 3
71
netsnmp_sd_listen_fds(int unset_environment)72 int netsnmp_sd_listen_fds(int unset_environment) {
73
74 int r, fd;
75 const char *e;
76 char *p = NULL;
77 unsigned long l;
78
79 if (!(e = getenv("LISTEN_PID"))) {
80 r = 0;
81 goto finish;
82 }
83
84 errno = 0;
85 l = strtoul(e, &p, 10);
86
87 if (errno != 0) {
88 r = -errno;
89 goto finish;
90 }
91
92 if (!p || *p || l <= 0) {
93 r = -EINVAL;
94 goto finish;
95 }
96
97 /* Is this for us? */
98 if (getpid() != (pid_t) l) {
99 r = 0;
100 goto finish;
101 }
102
103 if (!(e = getenv("LISTEN_FDS"))) {
104 r = 0;
105 goto finish;
106 }
107
108 errno = 0;
109 l = strtoul(e, &p, 10);
110
111 if (errno != 0 || l != (int)l) {
112 r = errno ? -errno : -EINVAL;
113 goto finish;
114 }
115
116 if (!p || *p) {
117 r = -EINVAL;
118 goto finish;
119 }
120
121 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
122 int flags;
123
124 if ((flags = fcntl(fd, F_GETFD)) < 0) {
125 r = -errno;
126 goto finish;
127 }
128
129 if (flags & FD_CLOEXEC)
130 continue;
131
132 if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
133 r = -errno;
134 goto finish;
135 }
136 }
137
138 r = (int) l;
139
140 finish:
141 if (unset_environment) {
142 unsetenv("LISTEN_PID");
143 unsetenv("LISTEN_FDS");
144 }
145
146 return r;
147 }
148
sd_is_socket_internal(int fd,int type,int listening)149 static int sd_is_socket_internal(int fd, int type, int listening) {
150 struct stat st_fd;
151
152 if (fd < 0 || type < 0)
153 return -EINVAL;
154
155 if (fstat(fd, &st_fd) < 0)
156 return -errno;
157
158 if (!S_ISSOCK(st_fd.st_mode))
159 return 0;
160
161 if (type != 0) {
162 int other_type = 0;
163 socklen_t l = sizeof(other_type);
164
165 if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
166 return -errno;
167
168 if (l != sizeof(other_type))
169 return -EINVAL;
170
171 if (other_type != type)
172 return 0;
173 }
174
175 if (listening >= 0) {
176 int accepting = 0;
177 socklen_t l = sizeof(accepting);
178
179 if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
180 return -errno;
181
182 if (l != sizeof(accepting))
183 return -EINVAL;
184
185 if (!accepting != !listening)
186 return 0;
187 }
188
189 return 1;
190 }
191
192 union sockaddr_union {
193 struct sockaddr sa;
194 struct sockaddr_in in4;
195 struct sockaddr_in6 in6;
196 struct sockaddr_un un;
197 struct sockaddr_storage storage;
198 };
199
sd_is_socket_inet(int fd,int family,int type,int listening,uint16_t port)200 static int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
201 union sockaddr_union sockaddr;
202 socklen_t l;
203 int r;
204
205 if (family != 0 && family != AF_INET && family != AF_INET6)
206 return -EINVAL;
207
208 if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
209 return r;
210
211 memset(&sockaddr, 0, sizeof(sockaddr));
212 l = sizeof(sockaddr);
213
214 if (getsockname(fd, &sockaddr.sa, &l) < 0)
215 return -errno;
216
217 if (l < sizeof(sa_family_t))
218 return -EINVAL;
219
220 if (sockaddr.sa.sa_family != AF_INET &&
221 sockaddr.sa.sa_family != AF_INET6)
222 return 0;
223
224 if (family > 0)
225 if (sockaddr.sa.sa_family != family)
226 return 0;
227
228 if (port > 0) {
229 if (sockaddr.sa.sa_family == AF_INET) {
230 if (l < sizeof(struct sockaddr_in))
231 return -EINVAL;
232
233 return htons(port) == sockaddr.in4.sin_port;
234 } else {
235 if (l < sizeof(struct sockaddr_in6))
236 return -EINVAL;
237
238 return htons(port) == sockaddr.in6.sin6_port;
239 }
240 }
241
242 return 1;
243 }
244
sd_is_socket_unix(int fd,int type,int listening,const char * path,size_t length)245 static int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
246 union sockaddr_union sockaddr;
247 socklen_t l;
248 int r;
249
250 if ((r = sd_is_socket_internal(fd, type, listening)) <= 0)
251 return r;
252
253 memset(&sockaddr, 0, sizeof(sockaddr));
254 l = sizeof(sockaddr);
255
256 if (getsockname(fd, &sockaddr.sa, &l) < 0)
257 return -errno;
258
259 if (l < sizeof(sa_family_t))
260 return -EINVAL;
261
262 if (sockaddr.sa.sa_family != AF_UNIX)
263 return 0;
264
265 if (path) {
266 if (length <= 0)
267 length = strlen(path);
268
269 if (length <= 0)
270 /* Unnamed socket */
271 return l == offsetof(struct sockaddr_un, sun_path);
272
273 if (path[0])
274 /* Normal path socket */
275 return
276 (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
277 memcmp(path, sockaddr.un.sun_path, length+1) == 0;
278 else
279 /* Abstract namespace socket */
280 return
281 (l == offsetof(struct sockaddr_un, sun_path) + length) &&
282 memcmp(path, sockaddr.un.sun_path, length) == 0;
283 }
284
285 return 1;
286 }
287
netsnmp_sd_notify(int unset_environment,const char * state)288 int netsnmp_sd_notify(int unset_environment, const char *state) {
289 int fd = -1, r;
290 struct msghdr msghdr;
291 struct iovec iovec;
292 union sockaddr_union sockaddr;
293 const char *e;
294
295 if (!state) {
296 r = -EINVAL;
297 goto finish;
298 }
299
300 if (!(e = getenv("NOTIFY_SOCKET")))
301 return 0;
302
303 /* Must be an abstract socket, or an absolute path */
304 if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
305 r = -EINVAL;
306 goto finish;
307 }
308
309 if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
310 r = -errno;
311 goto finish;
312 }
313
314 memset(&sockaddr, 0, sizeof(sockaddr));
315 sockaddr.sa.sa_family = AF_UNIX;
316 strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
317
318 if (sockaddr.un.sun_path[0] == '@')
319 sockaddr.un.sun_path[0] = 0;
320
321 memset(&iovec, 0, sizeof(iovec));
322 iovec.iov_base = NETSNMP_REMOVE_CONST(char *, state);
323 iovec.iov_len = strlen(state);
324
325 memset(&msghdr, 0, sizeof(msghdr));
326 msghdr.msg_name = &sockaddr;
327 msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
328
329 if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
330 msghdr.msg_namelen = sizeof(struct sockaddr_un);
331
332 msghdr.msg_iov = &iovec;
333 msghdr.msg_iovlen = 1;
334
335 if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
336 r = -errno;
337 goto finish;
338 }
339
340 r = 1;
341
342 finish:
343 if (unset_environment)
344 unsetenv("NOTIFY_SOCKET");
345
346 if (fd >= 0)
347 close(fd);
348
349 return r;
350 }
351
352 /* End of original sd-daemon.c from systemd sources */
353
354 int
netsnmp_sd_find_inet_socket(int family,int type,int listening,int port)355 netsnmp_sd_find_inet_socket(int family, int type, int listening, int port)
356 {
357 int count, fd;
358
359 count = netsnmp_sd_listen_fds(0);
360 if (count <= 0) {
361 DEBUGMSGTL(("systemd:find_inet_socket", "No LISTEN_FDS found.\n"));
362 return -1;
363 }
364 DEBUGMSGTL(("systemd:find_inet_socket", "LISTEN_FDS reports %d sockets.\n",
365 count));
366
367 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
368 int rc = sd_is_socket_inet(fd, family, type, listening, port);
369 if (rc < 0)
370 DEBUGMSGTL(("systemd:find_inet_socket",
371 "sd_is_socket_inet error: %d\n", rc));
372 if (rc > 0) {
373 DEBUGMSGTL(("systemd:find_inet_socket",
374 "Found the socket in LISTEN_FDS\n"));
375 return fd;
376 }
377 }
378 DEBUGMSGTL(("systemd:find_inet_socket", "Socket not found in LISTEN_FDS\n"));
379 return -1;
380 }
381
382 int
netsnmp_sd_find_unix_socket(int type,int listening,const char * path)383 netsnmp_sd_find_unix_socket(int type, int listening, const char *path)
384 {
385 int count, fd;
386
387 count = netsnmp_sd_listen_fds(0);
388 if (count <= 0) {
389 DEBUGMSGTL(("systemd:find_unix_socket", "No LISTEN_FDS found.\n"));
390 return -1;
391 }
392 DEBUGMSGTL(("systemd:find_unix_socket", "LISTEN_FDS reports %d sockets.\n",
393 count));
394
395 for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) {
396 int rc = sd_is_socket_unix(fd, type, listening, path, 0);
397 if (rc < 0)
398 DEBUGMSGTL(("systemd:find_unix_socket",
399 "sd_is_socket_unix error: %d\n", rc));
400 if (rc > 0) {
401 DEBUGMSGTL(("systemd:find_unix_socket",
402 "Found the socket in LISTEN_FDS\n"));
403 return fd;
404 }
405 }
406 DEBUGMSGTL(("systemd:find_unix_socket", "Socket not found in LISTEN_FDS\n"));
407 return -1;
408 }
409
410 #endif /* ! NETSNMP_NO_SYSTEMD */
411