1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <netinet/in.h>
5 #include <stdbool.h>
6 #include <stddef.h>
7 #include <string.h>
8 #include <sys/un.h>
9 #include <unistd.h>
10 
11 #include "alloc-util.h"
12 #include "fd-util.h"
13 #include "fs-util.h"
14 #include "log.h"
15 #include "macro.h"
16 #include "missing_socket.h"
17 #include "mkdir-label.h"
18 #include "selinux-util.h"
19 #include "socket-util.h"
20 #include "umask-util.h"
21 
socket_address_listen(const SocketAddress * a,int flags,int backlog,SocketAddressBindIPv6Only only,const char * bind_to_device,bool reuse_port,bool free_bind,bool transparent,mode_t directory_mode,mode_t socket_mode,const char * label)22 int socket_address_listen(
23                 const SocketAddress *a,
24                 int flags,
25                 int backlog,
26                 SocketAddressBindIPv6Only only,
27                 const char *bind_to_device,
28                 bool reuse_port,
29                 bool free_bind,
30                 bool transparent,
31                 mode_t directory_mode,
32                 mode_t socket_mode,
33                 const char *label) {
34 
35         _cleanup_close_ int fd = -1;
36         const char *p;
37         int r;
38 
39         assert(a);
40 
41         r = socket_address_verify(a, true);
42         if (r < 0)
43                 return r;
44 
45         if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported())
46                 return -EAFNOSUPPORT;
47 
48         if (label) {
49                 r = mac_selinux_create_socket_prepare(label);
50                 if (r < 0)
51                         return r;
52         }
53 
54         fd = RET_NERRNO(socket(socket_address_family(a), a->type | flags, a->protocol));
55 
56         if (label)
57                 mac_selinux_create_socket_clear();
58 
59         if (fd < 0)
60                 return fd;
61 
62         if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) {
63                 r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_V6ONLY, only == SOCKET_ADDRESS_IPV6_ONLY);
64                 if (r < 0)
65                         return r;
66         }
67 
68         if (IN_SET(socket_address_family(a), AF_INET, AF_INET6)) {
69                 if (bind_to_device) {
70                         r = socket_bind_to_ifname(fd, bind_to_device);
71                         if (r < 0)
72                                 return r;
73                 }
74 
75                 if (reuse_port) {
76                         r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEPORT, true);
77                         if (r < 0)
78                                 log_warning_errno(r, "SO_REUSEPORT failed: %m");
79                 }
80 
81                 if (free_bind) {
82                         r = socket_set_freebind(fd, socket_address_family(a), true);
83                         if (r < 0)
84                                 log_warning_errno(r, "IP_FREEBIND/IPV6_FREEBIND failed: %m");
85                 }
86 
87                 if (transparent) {
88                         r = socket_set_transparent(fd, socket_address_family(a), true);
89                         if (r < 0)
90                                 log_warning_errno(r, "IP_TRANSPARENT/IPV6_TRANSPARENT failed: %m");
91                 }
92         }
93 
94         r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true);
95         if (r < 0)
96                 return r;
97 
98         p = socket_address_get_path(a);
99         if (p) {
100                 /* Create parents */
101                 (void) mkdir_parents_label(p, directory_mode);
102 
103                 /* Enforce the right access mode for the socket */
104                 RUN_WITH_UMASK(~socket_mode) {
105                         r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size);
106                         if (r == -EADDRINUSE) {
107                                 /* Unlink and try again */
108 
109                                 if (unlink(p) < 0)
110                                         return r; /* didn't work, return original error */
111 
112                                 r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size);
113                         }
114                         if (r < 0)
115                                 return r;
116                 }
117         } else {
118                 if (bind(fd, &a->sockaddr.sa, a->size) < 0)
119                         return -errno;
120         }
121 
122         if (socket_address_can_accept(a))
123                 if (listen(fd, backlog) < 0)
124                         return -errno;
125 
126         /* Let's trigger an inotify event on the socket node, so that anyone waiting for this socket to be connectable
127          * gets notified */
128         if (p)
129                 (void) touch(p);
130 
131         return TAKE_FD(fd);
132 }
133