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