summaryrefslogtreecommitdiffstats
path: root/src/shared/socket-label.c
blob: b86a6adbb96ed1881ac250ea82c0c8209eddeef6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <errno.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/un.h>
#include <unistd.h>

#include "alloc-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "log.h"
#include "macro.h"
#include "missing_socket.h"
#include "mkdir-label.h"
#include "selinux-util.h"
#include "socket-util.h"
#include "umask-util.h"

int 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) {

        _cleanup_close_ int fd = -EBADF;
        const char *p;
        int r;

        assert(a);

        r = socket_address_verify(a, true);
        if (r < 0)
                return r;

        if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported())
                return -EAFNOSUPPORT;

        if (label) {
                r = mac_selinux_create_socket_prepare(label);
                if (r < 0)
                        return r;
        }

        fd = RET_NERRNO(socket(socket_address_family(a), a->type | flags, a->protocol));

        if (label)
                mac_selinux_create_socket_clear();

        if (fd < 0)
                return fd;

        if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) {
                r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_V6ONLY, only == SOCKET_ADDRESS_IPV6_ONLY);
                if (r < 0)
                        return r;
        }

        if (IN_SET(socket_address_family(a), AF_INET, AF_INET6)) {
                if (bind_to_device) {
                        r = socket_bind_to_ifname(fd, bind_to_device);
                        if (r < 0)
                                return r;
                }

                if (reuse_port) {
                        r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEPORT, true);
                        if (r < 0)
                                log_warning_errno(r, "SO_REUSEPORT failed: %m");
                }

                if (free_bind) {
                        r = socket_set_freebind(fd, socket_address_family(a), true);
                        if (r < 0)
                                log_warning_errno(r, "IP_FREEBIND/IPV6_FREEBIND failed: %m");
                }

                if (transparent) {
                        r = socket_set_transparent(fd, socket_address_family(a), true);
                        if (r < 0)
                                log_warning_errno(r, "IP_TRANSPARENT/IPV6_TRANSPARENT failed: %m");
                }
        }

        r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true);
        if (r < 0)
                return r;

        p = socket_address_get_path(a);
        if (p) {
                /* Create parents */
                (void) mkdir_parents_label(p, directory_mode);

                /* Enforce the right access mode for the socket */
                WITH_UMASK(~socket_mode) {
                        r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size);
                        if (r == -EADDRINUSE) {
                                /* Unlink and try again */

                                if (unlink(p) < 0)
                                        return r; /* didn't work, return original error */

                                r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size);
                        }
                        if (r < 0)
                                return r;
                }
        } else {
                if (bind(fd, &a->sockaddr.sa, a->size) < 0)
                        return -errno;
        }

        if (socket_address_can_accept(a))
                if (listen(fd, backlog) < 0)
                        return -errno;

        /* Let's trigger an inotify event on the socket node, so that anyone waiting for this socket to be connectable
         * gets notified */
        if (p)
                (void) touch(p);

        return TAKE_FD(fd);
}