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