1
2 #include <platform.h>
3 #include <server_code.h>
4
5 #include <cf3.extern.h> // BINDINTERFACE
6 #include <printsize.h> // PRINTSIZE
7 #include <systype.h> // CLASSTEXT
8 #include <signals.h> // GetSignalPipe
9 #include <cleanup.h> // DoCleanupAndExit
10 #include <ctype.h> // isdigit
11
12 #if HAVE_SYSTEMD_SD_DAEMON_H
13 #include <systemd/sd-daemon.h> // sd_listen_fds
14 #endif
15
16 /* Wait up to a minute for an in-coming connection.
17 *
18 * @param sd The listening socket or -1.
19 * @param tm_sec timeout in seconds
20 * @retval > 0 In-coming connection.
21 * @retval 0 No in-coming connection.
22 * @retval -1 Error (other than interrupt).
23 * @retval < -1 Interrupted while waiting.
24 */
WaitForIncoming(int sd,time_t tm_sec)25 int WaitForIncoming(int sd, time_t tm_sec)
26 {
27 Log(LOG_LEVEL_DEBUG, "Waiting at incoming select...");
28 struct timeval timeout = { .tv_sec = tm_sec };
29 int signal_pipe = GetSignalPipe();
30 fd_set rset;
31 FD_ZERO(&rset);
32 FD_SET(signal_pipe, &rset);
33
34 /* sd might be -1 if "listen" attribute in body server control is set
35 * to off (enterprise feature for call-collected clients). */
36 if (sd != -1)
37 {
38 FD_SET(sd, &rset);
39 }
40
41 int result = select(MAX(sd, signal_pipe) + 1,
42 &rset, NULL, NULL, &timeout);
43 if (result == -1)
44 {
45 return (errno == EINTR) ? -2 : -1;
46 }
47 assert(result >= 0);
48
49 /* Empty the signal pipe, it is there to only detect missed
50 * signals in-between checking IsPendingTermination() and calling
51 * select(). */
52 unsigned char buf;
53 while (recv(signal_pipe, &buf, 1, 0) > 0)
54 {
55 /* skip */
56 }
57
58 /* We have an incoming connection if select() marked sd as ready: */
59 if (sd != -1 && result > 0 && FD_ISSET(sd, &rset))
60 {
61 return 1;
62 }
63 return 0;
64 }
65
66 /**
67 * Orders 'struct addrinfo *' linked list in a descending order based on the
68 * ai_family, prefering IPV6.
69 */
OrderAddrInfoResponsesByAIfamily(struct addrinfo ** first_response)70 static void OrderAddrInfoResponsesByAIfamily(struct addrinfo **first_response)
71 {
72 /* Below is just a trivial implementation of the Bubble-sort algorithm to
73 * sort the linked list. */
74 struct addrinfo *first = *first_response;
75 bool change = true;
76 while (change)
77 {
78 change = false;
79 struct addrinfo *item = first;
80 struct addrinfo *prev = NULL;
81 while (item->ai_next != NULL)
82 {
83 /* AF_INET6 should be greater than AF_INET, but let's not rely on
84 * that and use a direct comparison. */
85 if ((item->ai_family == AF_INET) && (item->ai_next->ai_family == AF_INET6))
86 {
87 struct addrinfo *orig_next = item->ai_next;
88 item->ai_next = orig_next->ai_next;
89 orig_next->ai_next = item;
90 if (prev != NULL)
91 {
92 prev->ai_next = orig_next;
93 }
94 else
95 {
96 first = orig_next;
97 }
98 prev = orig_next;
99 change = true;
100 }
101 else
102 {
103 item = item->ai_next;
104 }
105 }
106 }
107 *first_response = first;
108 }
109
110 /**
111 * @param bind_address address to bind to or %NULL to use the default BINDINTERFACE
112 */
OpenReceiverChannel(char * bind_address)113 static int OpenReceiverChannel(char *bind_address)
114 {
115 struct addrinfo *response = NULL, *ap;
116 struct addrinfo query = {
117 .ai_flags = AI_PASSIVE,
118 .ai_family = AF_UNSPEC,
119 .ai_socktype = SOCK_STREAM
120 };
121
122 if (bind_address == NULL)
123 {
124 bind_address = BINDINTERFACE;
125 }
126
127 /* Listen to INADDR(6)_ANY if BINDINTERFACE unset. */
128 char *ptr = NULL;
129 if (bind_address[0] != '\0')
130 {
131 ptr = bind_address;
132
133 /* Just a quick check if the string looks like an IPv4 address to see if
134 * we should use AI_NUMERICHOST or not. IPv6 addresses could use it too,
135 * but they are not so easy to recognize and the proper check would be
136 * more expensive than the optimization. */
137 bool is_numeric_host = true;
138 for (char *c = ptr; is_numeric_host && (*c != '\0'); c++)
139 {
140 is_numeric_host = ((*c == '.') || isdigit(*c));
141 }
142 if (is_numeric_host)
143 {
144 query.ai_flags |= AI_NUMERICHOST;
145 }
146 }
147
148 /* Resolve listening interface. */
149 int gres = getaddrinfo(ptr, CFENGINE_PORT_STR, &query, &response);
150 if (gres != 0)
151 {
152 Log(LOG_LEVEL_ERR, "DNS/service lookup failure. (getaddrinfo: %s)",
153 gai_strerror(gres));
154 if (response)
155 {
156 freeaddrinfo(response);
157 }
158 return -1;
159 }
160
161 /* Make sure IPV6 addresses/interfaces are preferred over IPV4 ones (binding
162 * to the IPV6 address makes connections to the IPV4 equivalent work too, in
163 * particular for INADDR6_ANY (::) and INADDR_ANY (0.0.0.0/0). */
164 OrderAddrInfoResponsesByAIfamily(&response);
165
166 int sd = -1;
167 for (ap = response; ap != NULL; ap = ap->ai_next)
168 {
169 sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol);
170 if (sd == -1)
171 {
172 if (ap->ai_family == AF_INET)
173 {
174 Log(LOG_LEVEL_VERBOSE, "Failed to create socket for binding to an IPV4 interface");
175 }
176 else if (ap->ai_family == AF_INET6)
177 {
178 Log(LOG_LEVEL_VERBOSE, "Failed to create socket for binding to an IPV6 interface");
179 }
180 else
181 {
182 Log(LOG_LEVEL_VERBOSE,
183 "Failed to create socket for binding to an interface of ai_family %d",
184 ap->ai_family);
185 }
186 continue;
187 }
188
189 #ifdef IPV6_V6ONLY
190 /* Properly implemented getaddrinfo(AI_PASSIVE) should return the IPV6
191 loopback address first. Some platforms (notably Windows) don't
192 listen to both address families when binding to it and need this
193 flag. Some other platforms won't even honour this flag
194 (openbsd). */
195 if (bind_address[0] == '\0' && ap->ai_family == AF_INET6)
196 {
197 int no = 0;
198 if (setsockopt(sd, IPPROTO_IPV6, IPV6_V6ONLY,
199 &no, sizeof(no)) == -1)
200 {
201 Log(LOG_LEVEL_VERBOSE,
202 "Failed to clear IPv6-only flag on listening socket"
203 " (setsockopt: %s)",
204 GetErrorStr());
205 }
206 }
207 #endif
208
209 int yes = 1;
210 if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR,
211 &yes, sizeof(yes)) == -1)
212 {
213 Log(LOG_LEVEL_VERBOSE,
214 "Socket option SO_REUSEADDR was not accepted. (setsockopt: %s)",
215 GetErrorStr());
216 }
217
218 struct linger cflinger = {
219 .l_onoff = 1,
220 .l_linger = 60
221 };
222 if (setsockopt(sd, SOL_SOCKET, SO_LINGER,
223 &cflinger, sizeof(cflinger)) == -1)
224 {
225 Log(LOG_LEVEL_INFO,
226 "Socket option SO_LINGER was not accepted. (setsockopt: %s)",
227 GetErrorStr());
228 }
229
230 if (bind(sd, ap->ai_addr, ap->ai_addrlen) != -1)
231 {
232 if (WouldLog(LOG_LEVEL_DEBUG))
233 {
234 /* Convert IP address to string, no DNS lookup performed. */
235 char txtaddr[CF_MAX_IP_LEN] = "";
236 getnameinfo(ap->ai_addr, ap->ai_addrlen,
237 txtaddr, sizeof(txtaddr),
238 NULL, 0, NI_NUMERICHOST);
239 Log(LOG_LEVEL_DEBUG, "Bound to address '%s' on '%s' = %d", txtaddr,
240 CLASSTEXT[VSYSTEMHARDCLASS], VSYSTEMHARDCLASS);
241 }
242 break;
243 }
244 Log(LOG_LEVEL_ERR, "Could not bind server address. (bind: %s)", GetErrorStr());
245 cf_closesocket(sd);
246 sd = -1;
247 }
248
249 if (sd == -1)
250 {
251 Log(LOG_LEVEL_ERR,
252 "Failed to bind to all attempted addresses (bind specification: '%s'",
253 bind_address);
254 }
255
256 assert(response != NULL); /* getaddrinfo() was successful */
257 freeaddrinfo(response);
258 return sd;
259 }
260
261 /**
262 * @param queue_size length of the queue for pending connections
263 * @param bind_address address to bind to or %NULL to use the default BINDINTERFACE
264 */
InitServer(size_t queue_size,char * bind_address)265 int InitServer(size_t queue_size, char *bind_address)
266 {
267 #if HAVE_SYSTEMD_SD_DAEMON_H
268 int n = sd_listen_fds(0);
269 if (n > 1)
270 {
271 Log(LOG_LEVEL_ERR, "Too many file descriptors received from systemd");
272 }
273 else if (n == 1)
274 {
275 // we can check here that we have a socket with sd_is_socket_inet(3)
276 // but why should we limit ourselves
277 return SD_LISTEN_FDS_START;
278 }
279 else
280 #endif // HAVE_SYSTEMD_SD_DAEMON_H
281 {
282 int sd = OpenReceiverChannel(bind_address);
283
284 if (sd == -1)
285 {
286 /* More detailed info is logged in case of error in
287 * OpenReceiverChannel() */
288 Log(LOG_LEVEL_ERR, "Unable to start server");
289 }
290 else if (listen(sd, queue_size) == -1)
291 {
292 Log(LOG_LEVEL_ERR, "Failed to listen on the '%s' address (listen: %s)",
293 bind_address, GetErrorStr());
294 cf_closesocket(sd);
295 }
296 else
297 {
298 return sd;
299 }
300 }
301
302 DoCleanupAndExit(EXIT_FAILURE);
303 }
304