1 /*
2 Copyright 2020 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <server.h>
26
27 #include <item_lib.h>
28 #include <crypto.h>
29 #include <hash.h>
30 #include <eval_context.h>
31 #include <lastseen.h>
32 #include <conversion.h>
33 #include <string_lib.h>
34 #include <signals.h>
35 #include <mutex.h>
36 #include <global_mutex.h>
37 #include <net.h> /* SendTransaction,ReceiveTransaction */
38 #include <tls_generic.h> /* TLSVerifyPeer */
39 #include <rlist.h>
40 #include <misc_lib.h>
41 #include <cf-serverd-enterprise-stubs.h>
42 #include <audit.h>
43 #include <cfnet.h>
44 #include <protocol.h> /* ProtocolIsTLS() */
45 #include <server_tls.h> /* ServerTLS* */
46 #include <server_common.h>
47 #include <connection_info.h>
48 #include <cf-windows-functions.h>
49 #include <logging_priv.h> /* LoggingPrivSetContext */
50 #include <printsize.h>
51
52 #include "server_classic.h" /* BusyWithClassicConnection */
53
54
55 /*
56 The only exported function in this file is the following, used only in
57 cf-serverd-functions.c.
58
59 void ServerEntryPoint(EvalContext *ctx, const char *ipaddr, ConnectionInfo *info);
60
61 TODO move this file to cf-serverd-functions.c or most probably server_common.c.
62 */
63
64
65 //******************************************************************
66 // GLOBAL STATE
67 //******************************************************************
68
69 int ACTIVE_THREADS = 0; /* GLOBAL_X */
70
71 int CFD_MAXPROCESSES = 0; /* GLOBAL_P */
72 bool DENYBADCLOCKS = true; /* GLOBAL_P */
73 int MAXTRIES = 5; /* GLOBAL_P */
74 bool LOGENCRYPT = false; /* GLOBAL_P */
75 int COLLECT_INTERVAL = 0; /* GLOBAL_P */
76 int COLLECT_WINDOW = 30; /* GLOBAL_P */
77 bool SERVER_LISTEN = true; /* GLOBAL_P */
78
79 ServerAccess SERVER_ACCESS = { 0 }; /* GLOBAL_P */
80
81 char CFRUNCOMMAND[CF_MAXVARSIZE] = { 0 }; /* GLOBAL_P */
82
83 /******************************************************************/
84
85 static void SpawnConnection(EvalContext *ctx, const char *ipaddr, ConnectionInfo *info);
86 static void PurgeOldConnections(Item **list, time_t now);
87 static void *HandleConnection(void *conn);
88 static ServerConnectionState *NewConn(EvalContext *ctx, ConnectionInfo *info);
89 static void DeleteConn(ServerConnectionState *conn);
90
91 /****************************************************************************/
92
ServerEntryPoint(EvalContext * ctx,const char * ipaddr,ConnectionInfo * info)93 void ServerEntryPoint(EvalContext *ctx, const char *ipaddr, ConnectionInfo *info)
94 {
95 Log(LOG_LEVEL_VERBOSE,
96 "Obtained IP address of '%s' on socket %d from accept",
97 ipaddr, ConnectionInfoSocket(info));
98
99 /* TODO change nonattackerlist, attackerlist and especially connectionlist
100 * to binary searched lists, or remove them from the main thread! */
101 if (SERVER_ACCESS.nonattackerlist
102 && !IsMatchItemIn(SERVER_ACCESS.nonattackerlist, ipaddr))
103 {
104 Log(LOG_LEVEL_ERR,
105 "Remote host '%s' not in allowconnects, denying connection",
106 ipaddr);
107 }
108 else if (IsMatchItemIn(SERVER_ACCESS.attackerlist, ipaddr))
109 {
110 Log(LOG_LEVEL_ERR,
111 "Remote host '%s' is in denyconnects, denying connection",
112 ipaddr);
113 }
114 else
115 {
116 time_t now = time(NULL);
117 if (now == -1)
118 {
119 now = 0;
120 }
121
122 PurgeOldConnections(&SERVER_ACCESS.connectionlist, now);
123
124 bool allow = IsMatchItemIn(SERVER_ACCESS.multiconnlist, ipaddr);
125 if (!allow)
126 {
127 ThreadLock(cft_count);
128 /* At most one connection allowed for this host: */
129 allow = !IsItemIn(SERVER_ACCESS.connectionlist, ipaddr);
130 ThreadUnlock(cft_count);
131
132 if (!allow) /* Duplicate. */
133 {
134 Log(LOG_LEVEL_ERR,
135 "Remote host '%s' is not in allowallconnects, denying second simultaneous connection",
136 ipaddr);
137 }
138 }
139
140 if (allow)
141 {
142 char intime[PRINTSIZE(now)];
143 xsnprintf(intime, sizeof(intime), "%jd", (intmax_t) now);
144
145 ThreadLock(cft_count);
146 PrependItem(&SERVER_ACCESS.connectionlist, ipaddr, intime);
147 ThreadUnlock(cft_count);
148
149 SpawnConnection(ctx, ipaddr, info);
150 return; /* Success */
151 }
152 }
153 /* Tidy up on failure: */
154
155 if (info->is_call_collect)
156 {
157 CollectCallMarkProcessed();
158 }
159 cf_closesocket(ConnectionInfoSocket(info));
160 ConnectionInfoDestroy(&info);
161 }
162
163 /**********************************************************************/
164
PurgeOldConnections(Item ** list,time_t now)165 static void PurgeOldConnections(Item **list, time_t now)
166 /* Some connections might not terminate properly. These should be cleaned
167 every couple of hours. That should be enough to prevent spamming. */
168 {
169 assert(list != NULL);
170
171 Log(LOG_LEVEL_DEBUG, "Purging Old Connections...");
172
173 ThreadLock(cft_count);
174 Item *next;
175 for (Item *ip = *list; ip != NULL; ip = next)
176 {
177 int then = 0;
178 sscanf(ip->classes, "%d", &then);
179
180 next = ip->next;
181
182 if (now > then + 2 * SECONDS_PER_HOUR)
183 {
184 Log(LOG_LEVEL_VERBOSE,
185 "IP address '%s' has been more than two hours in connection list, purging",
186 ip->name);
187 DeleteItem(list, ip);
188 }
189 }
190 ThreadUnlock(cft_count);
191
192 Log(LOG_LEVEL_DEBUG, "Done purging old connections");
193 }
194
195 /*********************************************************************/
196
SpawnConnection(EvalContext * ctx,const char * ipaddr,ConnectionInfo * info)197 static void SpawnConnection(EvalContext *ctx, const char *ipaddr, ConnectionInfo *info)
198 {
199 ServerConnectionState *conn = NULL;
200 int ret;
201 pthread_t tid;
202 pthread_attr_t threadattrs;
203
204 conn = NewConn(ctx, info); /* freed in HandleConnection */
205 int sd_accepted = ConnectionInfoSocket(info);
206 strlcpy(conn->ipaddr, ipaddr, CF_MAX_IP_LEN );
207
208 Log(LOG_LEVEL_VERBOSE,
209 "New connection (from %s, sd %d), spawning new thread...",
210 conn->ipaddr, sd_accepted);
211
212 ret = pthread_attr_init(&threadattrs);
213 if (ret != 0)
214 {
215 Log(LOG_LEVEL_ERR,
216 "SpawnConnection: Unable to initialize thread attributes (%s)",
217 GetErrorStr());
218 goto err;
219 }
220 ret = pthread_attr_setdetachstate(&threadattrs, PTHREAD_CREATE_DETACHED);
221 if (ret != 0)
222 {
223 Log(LOG_LEVEL_ERR,
224 "SpawnConnection: Unable to set thread to detached state (%s).",
225 GetErrorStr());
226 goto cleanup;
227 }
228 ret = pthread_attr_setstacksize(&threadattrs, 1024 * 1024);
229 if (ret != 0)
230 {
231 Log(LOG_LEVEL_WARNING,
232 "SpawnConnection: Unable to set thread stack size (%s).",
233 GetErrorStr());
234 /* Continue with default thread stack size. */
235 }
236
237 ret = pthread_create(&tid, &threadattrs, HandleConnection, conn);
238 if (ret != 0)
239 {
240 errno = ret;
241 Log(LOG_LEVEL_ERR,
242 "Unable to spawn worker thread. (pthread_create: %s)",
243 GetErrorStr());
244 goto cleanup;
245 }
246
247 cleanup:
248 pthread_attr_destroy(&threadattrs);
249 err:
250 if (ret != 0)
251 {
252 Log(LOG_LEVEL_WARNING, "Thread is being handled from main loop!");
253 HandleConnection(conn);
254 }
255 }
256
257 /*********************************************************************/
258
DisableSendDelays(int sockfd)259 static void DisableSendDelays(int sockfd)
260 {
261 int yes = 1;
262
263 if (setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (void *) &yes, sizeof(yes)) == -1)
264 {
265 Log(LOG_LEVEL_INFO, "Unable to disable Nagle algorithm, expect performance problems. (setsockopt(TCP_NODELAY): %s)", GetErrorStr());
266 }
267 }
268
269 /*********************************************************************/
270
271 /* TODO performance fix this to avoid the StringConcatenate() reallocations,
272 * if only we could set a static logging prefix. */
LogHook(LoggingPrivContext * log_ctx,ARG_UNUSED LogLevel level,const char * message)273 static char *LogHook(LoggingPrivContext *log_ctx,
274 ARG_UNUSED LogLevel level, const char *message)
275 {
276 const char *aligned_ipaddr = log_ctx->param;
277 return StringConcatenate(2, aligned_ipaddr, message);
278 }
279
280 /* TRIES: counts the number of consecutive connections dropped. */
281 static int TRIES = 0;
282
HandleConnection(void * c)283 static void *HandleConnection(void *c)
284 {
285 ServerConnectionState *conn = c;
286 int ret;
287
288 /* Set logging prefix to be the IP address for all of thread's lifetime. */
289 /* These stack-allocated variables should be valid for all the lifetime of
290 * the thread. */
291 char aligned_ipaddr[CF_MAX_IP_LEN + 2];
292 LoggingPrivContext log_ctx = {
293 .log_hook = LogHook,
294 .param = aligned_ipaddr
295 };
296
297 strlcpy(aligned_ipaddr, conn->ipaddr, sizeof(aligned_ipaddr));
298 strlcat(aligned_ipaddr, "> ", sizeof(aligned_ipaddr));
299 /* Pad with enough spaces for IPv4 addresses to be aligned. Max chars are
300 * 15 for the address plus two for "> " == 17. */
301 size_t len;
302 for (len = strlen(aligned_ipaddr); len < 17; len++)
303 {
304 aligned_ipaddr[len] = ' ';
305 }
306 aligned_ipaddr[len] = '\0';
307
308 LoggingPrivSetContext(&log_ctx);
309
310 Log(LOG_LEVEL_INFO, "Accepting connection");
311
312 /* We test if number of active threads is greater than max, if so we deny
313 connection, if it happened too many times within a short timeframe then we
314 kill ourself.TODO this test should be done *before* spawning the thread. */
315 ThreadLock(cft_server_children);
316 if (ACTIVE_THREADS > CFD_MAXPROCESSES)
317 {
318 if (TRIES > MAXTRIES)
319 {
320 /* This happens when no thread was freed while we had to drop 5
321 * (or maxconnections/3) consecutive connections, because none of
322 * the existing threads finished. */
323 Log(LOG_LEVEL_CRIT,
324 "Server seems to be paralyzed. DOS attack? "
325 "Committing apoptosis...");
326 ThreadUnlock(cft_server_children);
327 FatalError(conn->ctx, "Terminating");
328 }
329
330 TRIES++;
331 Log(LOG_LEVEL_ERR,
332 "Too many threads (%d > %d), dropping connection! "
333 "Increase server maxconnections?",
334 ACTIVE_THREADS, CFD_MAXPROCESSES);
335
336 ThreadUnlock(cft_server_children);
337 goto conndone;
338 }
339
340 ACTIVE_THREADS++;
341 TRIES = 0;
342 ThreadUnlock(cft_server_children);
343
344 DisableSendDelays(ConnectionInfoSocket(conn->conn_info));
345
346 /* 20 times the connect() timeout should be enough to avoid MD5
347 * computation timeouts on big files on old slow Solaris 8 machines. */
348 SetReceiveTimeout(ConnectionInfoSocket(conn->conn_info),
349 CONNTIMEOUT * 20 * 1000);
350
351 if (conn->conn_info->status != CONNECTIONINFO_STATUS_ESTABLISHED)
352 {
353 /* Decide the protocol used. */
354 bool success = ServerTLSPeek(conn->conn_info);
355 if (!success)
356 {
357 goto dethread;
358 }
359 }
360
361 ProtocolVersion protocol_version = ConnectionInfoProtocolVersion(conn->conn_info);
362 if (ProtocolIsTLS(protocol_version))
363 {
364 bool established = ServerTLSSessionEstablish(conn, NULL);
365 if (!established)
366 {
367 goto dethread;
368 }
369 }
370 else if (ProtocolIsClassic(protocol_version))
371 {
372 /* This connection is legacy protocol.
373 * We are not allowing it by default. */
374 if (!IsMatchItemIn(SERVER_ACCESS.allowlegacyconnects, conn->ipaddr))
375 {
376 Log(LOG_LEVEL_INFO,
377 "Connection is not using latest protocol, denying");
378 goto dethread;
379 }
380 }
381 else
382 {
383 UnexpectedError("HandleConnection: ProtocolVersion %d!",
384 ConnectionInfoProtocolVersion(conn->conn_info));
385 goto dethread;
386 }
387
388
389 /* ========================= MAIN LOOPS ========================= */
390 if (ProtocolIsTLS(protocol_version))
391 {
392 /* New protocol does DNS reverse look up of the connected
393 * IP address, to check hostname access_rules. */
394 if (NEED_REVERSE_LOOKUP)
395 {
396 ret = getnameinfo((const struct sockaddr *) &conn->conn_info->ss,
397 conn->conn_info->ss_len,
398 conn->revdns, sizeof(conn->revdns),
399 NULL, 0, NI_NAMEREQD);
400 if (ret != 0)
401 {
402 Log(LOG_LEVEL_INFO,
403 "Reverse lookup failed (getnameinfo: %s)!",
404 gai_strerror(ret));
405 }
406 else
407 {
408 Log(LOG_LEVEL_INFO,
409 "Hostname (reverse looked up): %s",
410 conn->revdns);
411 }
412 }
413
414 while (BusyWithNewProtocol(conn->ctx, conn))
415 {
416 }
417 }
418 else if (ProtocolIsClassic(protocol_version))
419 {
420 while (BusyWithClassicConnection(conn->ctx, conn))
421 {
422 }
423 }
424 else
425 {
426 assert(!"Bogus protocol version - but we checked that already !");
427 }
428 /* ============================================================ */
429
430 Log(LOG_LEVEL_INFO, "Closing connection, terminating thread");
431
432 dethread:
433 ThreadLock(cft_server_children);
434 ACTIVE_THREADS--;
435 ThreadUnlock(cft_server_children);
436
437 conndone:
438 if (conn->conn_info->is_call_collect)
439 {
440 CollectCallMarkProcessed();
441 }
442 DeleteConn(conn);
443 return NULL;
444 }
445
446
447 /***************************************************************/
448 /* Toolkit/Class: conn */
449 /***************************************************************/
450
NewConn(EvalContext * ctx,ConnectionInfo * info)451 static ServerConnectionState *NewConn(EvalContext *ctx, ConnectionInfo *info)
452 {
453 #if 1
454 /* TODO: why do we do this ? We fail if getsockname() fails, but
455 * don't use the information it returned. Was the intent to use
456 * it to fill in conn->ipaddr ? */
457 struct sockaddr_storage addr;
458 socklen_t size = sizeof(addr);
459
460 if (getsockname(ConnectionInfoSocket(info), (struct sockaddr *)&addr, &size) == -1)
461 {
462 Log(LOG_LEVEL_ERR,
463 "Could not obtain socket address. (getsockname: '%s')",
464 GetErrorStr());
465 return NULL;
466 }
467 #endif
468
469 ServerConnectionState *conn = xcalloc(1, sizeof(*conn));
470 conn->ctx = ctx;
471 conn->conn_info = info;
472 conn->encryption_type = 'c';
473 conn->dump_reports = EvalContextGetDumpReports(ctx);
474 /* Only public files (chmod o+r) accessible to non-root */
475 conn->uid = CF_UNKNOWN_OWNER; /* Careful, 0 is root! */
476 /* conn->maproot is false: only public files (chmod o+r) are accessible */
477
478 Log(LOG_LEVEL_DEBUG,
479 "New connection (socket %d).",
480 ConnectionInfoSocket(info));
481 return conn;
482 }
483
484 /**
485 * @note This function is thread-safe. Do NOT wrap it with mutex!
486 */
DeleteConn(ServerConnectionState * conn)487 static void DeleteConn(ServerConnectionState *conn)
488 {
489 int sd = ConnectionInfoSocket(conn->conn_info);
490 if (sd != SOCKET_INVALID)
491 {
492 cf_closesocket(sd);
493 }
494 ConnectionInfoDestroy(&conn->conn_info);
495
496 if (conn->ipaddr[0] != '\0')
497 {
498 ThreadLock(cft_count);
499 DeleteItemMatching(&SERVER_ACCESS.connectionlist, conn->ipaddr);
500 ThreadUnlock(cft_count);
501 }
502
503 *conn = (ServerConnectionState) {0};
504 free(conn->session_key);
505 free(conn);
506 }
507