1 /*
2   Copyright 2021 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 <platform.h>
26 
27 #include <client_code.h>        // cfnet_init
28 #include <crypto.h>             // CryptoInitialize
29 
30 #include <bootstrap.h>          // GetAmPolicyHub
31 #include <cf-serverd-enterprise-stubs.h>
32 #include <conversion.h>         // MapAddress
33 #include <exec_tools.h>         // ActAsDaemon
34 #include <item_lib.h>           // DeleteItemList
35 #include <known_dirs.h>         // GetInputDir
36 #include <loading.h>            // LoadPolicy
37 #include <locks.h>              // AcquireLock
38 #include <man.h>                // ManPageWrite
39 #include <mutex.h>              // ThreadLock
40 #include <net.h>
41 #include <openssl/err.h>        // ERR_get_error
42 #include <policy_server.h>      // PolicyServerReadFile
43 #include <printsize.h>          // PRINTSIZE
44 #include <server_access.h>      // acl_Free
45 #include <server_code.h>        // InitServer
46 #include <server_common.h>
47 #include <server_tls.h>         // ServerTLSInitialize
48 #include <server_transform.h>   // Summarize
49 #include <signals.h>            // ReloadConfigRequested
50 #include <string_lib.h>         // StringFormat, etc.
51 #include <sysinfo.h>            // DetectEnvironment
52 #include <systype.h>            // CLASSTEXT
53 #include <time_classes.h>       // UpdateTimeClasses
54 #include <timeout.h>            // SetReferenceTime
55 #include <tls_generic.h>        // TLSLogError
56 #include <logging.h>            // thread-specific log prefix
57 #include <cleanup.h>
58 
59 #ifndef __MINGW32__
60 #include <sys/socket.h>
61 #include <netinet/in.h>
62 #include <arpa/inet.h>
63 #else
64 #include <winsock2.h>
65 #include <inaddr.h>
66 #endif
67 
68 #define CFTESTD_QUEUE_SIZE 10
69 #define WAIT_CHECK_TIMEOUT 5
70 
71 /* Strictly speaking/programming, this should use a lock, but it's not needed
72  * for this testing tool. The worst that can happen is that some threads would
73  * do one more WaitForIncoming iteration (WAIT_CHECK_TIMEOUT seconds). */
74 static bool TERMINATE = false;
75 
76 // ============================= CFTestD_Config ==============================
77 typedef struct
78 {
79     char *report_file;
80     char *report_data;
81     Seq *report;
82     int report_len;
83     char *key_file;
84     RSA *priv_key;
85     RSA *pub_key;
86     SSL_CTX *ssl_ctx;
87     char *address;
88     pthread_t t_id;
89     int ret;
90 } CFTestD_Config;
91 
92 /*******************************************************************/
93 /* Command line option parsing                                     */
94 /*******************************************************************/
95 
96 static const struct option OPTIONS[] = {
97     {"address", required_argument, 0, 'a'},
98     {"debug", no_argument, 0, 'd'},
99     {"log-level", required_argument, 0, 'g'},
100     {"help", no_argument, 0, 'h'},
101     {"inform", no_argument, 0, 'I'},
102     {"jobs", required_argument, 0, 'j'},
103     {"key-file", required_argument, 0, 'k'},
104     {"timestamp", no_argument, 0, 'l'},
105     {"port", required_argument, 0, 'p'},
106     {"report", required_argument, 0, 'r'},
107     {"verbose", no_argument, 0, 'v'},
108     {"version", no_argument, 0, 'V'},
109     {NULL, 0, 0, '\0'}};
110 
111 static const char *const HINTS[] = {
112     "Bind to a specific address",
113     "Enable debugging output",
114     "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'",
115     "Print the help message",
116     "Print basic information about what cf-testd does",
117     "Number of jobs (threads) to run in parallel. Use '%d' in the report path"
118     " and key file path (will be replaced by the thread number, starting with"
119     " 0). Threads will bind to different addresses incrementally.",
120     "Specify a path to the key (private) to use for communication",
121     "Log timestamps on each line of log output",
122     "Set the port cf-testd will listen on",
123     "Read report from file",
124     "Output verbose information about the behaviour of the agent",
125     "Output the version of the software",
126     NULL};
127 
CFTestD_ConfigInit()128 CFTestD_Config *CFTestD_ConfigInit()
129 {
130     CFTestD_Config *r =
131         (CFTestD_Config *)(xcalloc(1, sizeof(CFTestD_Config)));
132     return r;
133 }
134 
CFTestD_ConfigDestroy(CFTestD_Config * config)135 void CFTestD_ConfigDestroy(CFTestD_Config *config)
136 {
137     free(config->report_file);
138     SeqDestroy(config->report);
139     free(config->key_file);
140     free(config->address);
141     free(config);
142 }
143 
CFTestD_Help()144 void CFTestD_Help()
145 {
146     Writer *w = FileWriter(stdout);
147     WriterWriteHelp(w, "cf-testd", OPTIONS, HINTS, NULL, false, true);
148     FileWriterDetach(w);
149 }
150 
CFTestD_CheckOpts(int argc,char ** argv,long * n_threads)151 CFTestD_Config *CFTestD_CheckOpts(int argc, char **argv, long *n_threads)
152 {
153     extern char *optarg;
154     int c;
155     CFTestD_Config *config = CFTestD_ConfigInit();
156     assert(config != NULL);
157 
158     while ((c = getopt_long(argc, argv, "a:df:g:hIj:k:lp:vV", OPTIONS, NULL)) != -1)
159     {
160         switch (c)
161         {
162         case 'a':
163             config->address = xstrdup(optarg);
164             break;
165         case 'd':
166             LogSetGlobalLevel(LOG_LEVEL_DEBUG);
167             break;
168         case 'h':
169             CFTestD_Help();
170             DoCleanupAndExit(EXIT_SUCCESS);
171         case 'I':
172             LogSetGlobalLevel(LOG_LEVEL_INFO);
173             break;
174         case 'g':
175             LogSetGlobalLevelArgOrExit(optarg);
176             break;
177         case 'j':
178         {
179             int ret = StringToLong(optarg, n_threads);
180             if (ret != 0)
181             {
182                 Log(LOG_LEVEL_ERR, "Failed to parse number of threads/jobs from '%s'\n", optarg);
183                 *n_threads = 1;
184             }
185             break;
186         }
187         case 'k':
188             config->key_file = xstrdup(optarg);
189             break;
190         case 'l':
191             LoggingEnableTimestamps(true);
192             break;
193         case 'p':
194         {
195             bool ret = SetCfenginePort(optarg);
196             if (!ret)
197             {
198                 /* the function call above logs an error for us (if any) */
199                 DoCleanupAndExit(EXIT_FAILURE);
200             }
201             break;
202         }
203         case 'r':
204             config->report_file = xstrdup(optarg);
205             break;
206         case 'v':
207             LogSetGlobalLevel(LOG_LEVEL_VERBOSE);
208             break;
209         case 'V':
210         {
211             Writer *w = FileWriter(stdout);
212             GenericAgentWriteVersion(w);
213             FileWriterDetach(w);
214         }
215             DoCleanupAndExit(EXIT_SUCCESS);
216         default:
217             CFTestD_Help();
218             DoCleanupAndExit(EXIT_FAILURE);
219         }
220     }
221 
222     if (optind < argc) // More unparsed arguments left after getopt_long
223     {
224         // Enumerate and print all invalid arguments:
225         Log(LOG_LEVEL_ERR, "Invalid command line arguments:");
226 
227         int start = optind;
228         int stop  = argc;
229         int total = stop - start;
230         for (int i = 0; i < total; ++i)
231         {
232             Log(LOG_LEVEL_ERR,
233                 "[%d/%d]: %s\n",
234                 i + 1,
235                 total,
236                 argv[start + i]);
237         }
238     }
239 
240     return config;
241 }
242 
CFTestD_TLSSessionEstablish(ServerConnectionState * conn,CFTestD_Config * config)243 bool CFTestD_TLSSessionEstablish(ServerConnectionState *conn, CFTestD_Config *config)
244 {
245     if (conn->conn_info->status == CONNECTIONINFO_STATUS_ESTABLISHED)
246     {
247         return true;
248     }
249 
250     bool established = BasicServerTLSSessionEstablish(conn, config->ssl_ctx);
251     if (!established)
252     {
253         return false;
254     }
255 
256     Log(LOG_LEVEL_VERBOSE, "TLS session established, checking trust...");
257 
258     /* Send/Receive "CFE_v%d" version string, agree on version, receive
259        identity (username) of peer. */
260     char username[sizeof(conn->username)] = "";
261     bool id_success = ServerIdentificationDialog(
262         conn->conn_info,
263         username,
264         sizeof(username)
265     );
266     if (!id_success)
267     {
268         return false;
269     }
270 
271     /* No CAUTH, SAUTH in non-classic protocol. */
272     conn->user_data_set = true;
273     conn->rsa_auth      = true;
274 
275     ServerSendWelcome(conn);
276     return true;
277 }
278 
CFTestD_GetServerQuery(ServerConnectionState * conn,char * recvbuffer,CFTestD_Config * config)279 bool CFTestD_GetServerQuery(
280     ServerConnectionState *conn, char *recvbuffer, CFTestD_Config *config)
281 {
282     char query[CF_BUFSIZE];
283     Seq *report          = config->report;
284     const int report_len = config->report_len;
285     ConnectionInfo *const conn_info = conn->conn_info;
286 
287     query[0] = '\0';
288     sscanf(recvbuffer, "QUERY %255[^\n]", query);
289 
290     if (strlen(query) == 0)
291     {
292         return false;
293     }
294 
295     if (report_len == 0)
296     {
297         Log(LOG_LEVEL_INFO,
298             "No report file argument so returning canned data from enterprise plugin server\n");
299         return CFTestD_ReturnQueryData(conn, query);
300     }
301 
302     Log(LOG_LEVEL_INFO,
303         "Report file argument specified. Returning report of length %d",
304         report_len);
305 
306     const size_t n_report_lines = SeqLength(report);
307 
308     assert(n_report_lines > 1);
309     char *ts = SeqAt(report, 0);
310     char *header = StringFormat("CFR: 0 %s %d\n", ts, report_len);
311     SendTransaction(conn_info, header, SafeStringLength(header), CF_MORE);
312     free(header);
313 
314     for (size_t i = 1; i < n_report_lines; i++)
315     {
316         const char *report_line = SeqAt(report, i);
317         SendTransaction(conn_info, report_line, SafeStringLength(report_line), CF_MORE);
318     }
319 
320     const char end_reply[] = "QUERY complete";
321     SendTransaction(conn_info, end_reply, SafeStringLength(end_reply), CF_DONE);
322 
323     return true;
324 }
325 
CFTestD_ProtocolError(ServerConnectionState * conn,const char * recvbuffer,char * sendbuffer)326 static bool CFTestD_ProtocolError(
327     ServerConnectionState *conn, const char *recvbuffer, char *sendbuffer)
328 {
329     strcpy(sendbuffer, "BAD: Request denied");
330     SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE);
331     Log(LOG_LEVEL_INFO,
332         "Closing connection due to illegal request: %s",
333         recvbuffer);
334     return false;
335 }
336 
CFTestD_BusyLoop(ServerConnectionState * conn,CFTestD_Config * config)337 static bool CFTestD_BusyLoop(
338     ServerConnectionState *conn, CFTestD_Config *config)
339 {
340     char recvbuffer[CF_BUFSIZE + CF_BUFEXT]        = "";
341     char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = "";
342 
343     const int received =
344         ReceiveTransaction(conn->conn_info, recvbuffer, NULL);
345 
346     if (received == -1)
347     {
348         /* Already Log()ged in case of error. */
349         return false;
350     }
351     if (received > CF_BUFSIZE - 1)
352     {
353         UnexpectedError("Received transaction of size %d", received);
354         return false;
355     }
356 
357     if (strlen(recvbuffer) == 0)
358     {
359         Log(LOG_LEVEL_WARNING,
360             "Got NULL transmission (of size %d)",
361             received);
362         return true;
363     }
364     /* Don't process request if we're signalled to exit. */
365     if (IsPendingTermination())
366     {
367         Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection");
368         return false;
369     }
370 
371     // See BusyWithNewProtocol() in server_tls.c for how to add other commands
372     switch (GetCommandNew(recvbuffer))
373     {
374     case PROTOCOL_COMMAND_COOKIE:
375     {
376         const char response[] = "COOKIES 0 0";
377         const size_t size = sizeof(response) - 1;
378         SendTransaction(conn->conn_info, response, size, CF_DONE);
379 
380         return true;
381         break;
382     }
383     case PROTOCOL_COMMAND_QUERY:
384     {
385         char query[256], name[128];
386         int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query);
387         int ret2 = sscanf(recvbuffer, "QUERY %127s", name);
388         if (ret1 != 1 || ret2 != 1)
389         {
390             return CFTestD_ProtocolError(conn, recvbuffer, sendbuffer);
391         }
392 
393         if (CFTestD_GetServerQuery(conn, recvbuffer, config))
394         {
395             return true;
396         }
397 
398         break;
399     }
400     case PROTOCOL_COMMAND_BAD:
401     default:
402         Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer);
403     }
404 
405     return CFTestD_ProtocolError(conn, recvbuffer, sendbuffer);
406 }
407 
CFTestD_NewConn(ConnectionInfo * info)408 static ServerConnectionState *CFTestD_NewConn(ConnectionInfo *info)
409 {
410 #if 1
411     /* TODO: why do we do this ?  We fail if getsockname() fails, but
412      * don't use the information it returned.  Was the intent to use
413      * it to fill in conn->ipaddr ? */
414     struct sockaddr_storage addr;
415     socklen_t size = sizeof(addr);
416     int sockfd     = ConnectionInfoSocket(info);
417     int sockname   = getsockname(sockfd, (struct sockaddr *)&addr, &size);
418 
419     if (sockname == -1)
420     {
421         Log(LOG_LEVEL_ERR,
422             "Could not obtain socket address. (getsockname: '%s')",
423             GetErrorStr());
424         return NULL;
425     }
426 #endif
427 
428     ServerConnectionState *conn = xcalloc(1, sizeof(*conn));
429     conn->ctx                   = NULL;
430     conn->conn_info             = info;
431     conn->encryption_type       = 'c';
432     /* Only public files (chmod o+r) accessible to non-root */
433     conn->uid = CF_UNKNOWN_OWNER; /* Careful, 0 is root! */
434     /* conn->maproot is false: only public files (chmod o+r) are accessible */
435 
436     Log(LOG_LEVEL_DEBUG,
437         "New connection (socket %d).",
438         ConnectionInfoSocket(info));
439     return conn;
440 }
441 
CFTestD_DeleteConn(ServerConnectionState * conn)442 static void CFTestD_DeleteConn(ServerConnectionState *conn)
443 {
444     int sd = ConnectionInfoSocket(conn->conn_info);
445     if (sd != SOCKET_INVALID)
446     {
447         cf_closesocket(sd);
448     }
449     ConnectionInfoDestroy(&conn->conn_info);
450 
451     free(conn->session_key);
452     free(conn);
453 }
454 
CFTestD_HandleConnection(ServerConnectionState * conn,CFTestD_Config * config)455 static void *CFTestD_HandleConnection(ServerConnectionState *conn, CFTestD_Config *config)
456 {
457     Log(LOG_LEVEL_INFO, "Accepting connection");
458 
459     bool established = CFTestD_TLSSessionEstablish(conn, config);
460     if (!established)
461     {
462         Log(LOG_LEVEL_ERR, "Could not establish TLS Session");
463         return NULL;
464     }
465     int ret = getnameinfo(
466         (const struct sockaddr *)&conn->conn_info->ss,
467         conn->conn_info->ss_len,
468         conn->revdns,
469         sizeof(conn->revdns),
470         NULL,
471         0,
472         NI_NAMEREQD);
473     if (ret != 0)
474     {
475         Log(LOG_LEVEL_INFO,
476             "Reverse lookup failed (getnameinfo: %s)!",
477             gai_strerror(ret));
478     }
479     else
480     {
481         Log(LOG_LEVEL_INFO, "Hostname (reverse looked up): %s", conn->revdns);
482     }
483 
484     while (CFTestD_BusyLoop(conn, config))
485     {
486     }
487 
488     CFTestD_DeleteConn(conn);
489     return NULL;
490 }
491 
CFTestD_SpawnConnection(const char * ipaddr,ConnectionInfo * info,CFTestD_Config * config)492 static void CFTestD_SpawnConnection(
493     const char *ipaddr, ConnectionInfo *info, CFTestD_Config *config)
494 {
495     ServerConnectionState *conn = CFTestD_NewConn(info);
496     ConnectionInfoSocket(info);
497     strlcpy(conn->ipaddr, ipaddr, CF_MAX_IP_LEN);
498 
499     Log(LOG_LEVEL_WARNING, "Connection is being handled from main loop!");
500     CFTestD_HandleConnection(conn, config);
501 }
502 
503 /* Try to accept a connection; handle if we get one. */
CFTestD_AcceptAndHandle(int sd,CFTestD_Config * config)504 static void CFTestD_AcceptAndHandle(int sd, CFTestD_Config *config)
505 {
506     /* TODO embed ConnectionInfo into ServerConnectionState. */
507     ConnectionInfo *info = ConnectionInfoNew(); /* Uses xcalloc() */
508 
509     info->ss_len = sizeof(info->ss);
510     info->sd     = accept(sd, (struct sockaddr *)&info->ss, &info->ss_len);
511     if (info->sd == -1)
512     {
513         Log(LOG_LEVEL_INFO, "Error accepting connection (%s)", GetErrorStr());
514         ConnectionInfoDestroy(&info);
515         return;
516     }
517 
518     Log(LOG_LEVEL_DEBUG,
519         "Socket descriptor returned from accept(): %d",
520         info->sd);
521 
522     /* Just convert IP address to string, no DNS lookup. */
523     char ipaddr[CF_MAX_IP_LEN] = "";
524     getnameinfo(
525         (const struct sockaddr *)&info->ss,
526         info->ss_len,
527         ipaddr,
528         sizeof(ipaddr),
529         NULL,
530         0,
531         NI_NUMERICHOST);
532 
533     /* IPv4 mapped addresses (e.g. "::ffff:192.168.1.2") are
534      * hereby represented with their IPv4 counterpart. */
535     CFTestD_SpawnConnection(MapAddress(ipaddr), info, config);
536 }
537 
CFTestD_StartServer(CFTestD_Config * config)538 int CFTestD_StartServer(CFTestD_Config *config)
539 {
540     int ret = -1;
541 
542     bool tls_init_ok = ServerTLSInitialize(config->priv_key, config->pub_key, &(config->ssl_ctx));
543     if (!tls_init_ok)
544     {
545         return -1;
546     }
547 
548     int sd = InitServer(CFTESTD_QUEUE_SIZE, config->address);
549 
550     int selected = 0;
551     while (!TERMINATE && (selected != -1))
552     {
553         selected = WaitForIncoming(sd, WAIT_CHECK_TIMEOUT);
554 
555         if (selected > 0)
556         {
557             Log(LOG_LEVEL_DEBUG, "select(): %d", selected);
558             CFTestD_AcceptAndHandle(sd, config);
559         }
560     }
561     if (!TERMINATE)
562     {
563         Log(LOG_LEVEL_ERR,
564             "Error while waiting for connections. (select: %s)",
565             GetErrorStr());
566     }
567     else
568     {
569         ret = 0;
570     }
571 
572     Log(LOG_LEVEL_NOTICE, "Cleaning up and exiting...");
573     if (sd != -1)
574     {
575         Log(LOG_LEVEL_VERBOSE, "Closing listening socket");
576         cf_closesocket(sd);
577     }
578 
579     return ret;
580 }
581 
LogAddPrefix(LoggingPrivContext * log_ctx,ARG_UNUSED LogLevel level,const char * raw)582 static char *LogAddPrefix(LoggingPrivContext *log_ctx,
583                           ARG_UNUSED LogLevel level,
584                           const char *raw)
585 {
586     const char *ip_addr = log_ctx->param;
587     return ip_addr ? StringConcatenate(4, "[", ip_addr, "] ", raw) : xstrdup(raw);
588 }
589 
CFTestD_GetReportLine(char * data,char ** report_line_start,size_t * report_line_length)590 static bool CFTestD_GetReportLine(char *data, char **report_line_start, size_t *report_line_length)
591 {
592     int ret = sscanf(data, "%10zd", report_line_length);
593     if (ret != 1)
594     {
595         /* incorrect number of items scanned (could be EOF) */
596         return false;
597     }
598 
599     *report_line_start = data + 10;
600     return true;
601 }
602 
CFTestD_ServeReport(void * config_arg)603 static void *CFTestD_ServeReport(void *config_arg)
604 {
605     CFTestD_Config *config = (CFTestD_Config *) config_arg;
606 
607     /* Set prefix for all Log()ging: */
608     LoggingPrivContext *prior = LoggingPrivGetContext();
609     LoggingPrivContext log_ctx = {
610         .log_hook = LogAddPrefix,
611         .param = config->address
612     };
613     LoggingPrivSetContext(&log_ctx);
614 
615     char *priv_key_path = NULL;
616     char *pub_key_path = NULL;
617     if (config->key_file != NULL)
618     {
619         priv_key_path = config->key_file;
620         pub_key_path = xstrdup(priv_key_path);
621         StringReplace(pub_key_path, strlen(pub_key_path) + 1,
622                       "priv", "pub");
623     }
624 
625     LoadSecretKeys(priv_key_path, pub_key_path, &(config->priv_key), &(config->pub_key));
626     free(pub_key_path);
627 
628     char *report_file = config->report_file;
629 
630     if (report_file != NULL)
631     {
632         Log(LOG_LEVEL_NOTICE, "Got file argument: '%s'", report_file);
633         if (!FileCanOpen(report_file, "r"))
634         {
635             Log(LOG_LEVEL_ERR,
636                 "Can't open file '%s' for reading",
637                 report_file);
638             DoCleanupAndExit(EXIT_FAILURE);
639         }
640 
641         Writer *contents = FileRead(report_file, SIZE_MAX, NULL);
642         if (!contents)
643         {
644             Log(LOG_LEVEL_ERR, "Error reading report file '%s'", report_file);
645             DoCleanupAndExit(EXIT_FAILURE);
646         }
647 
648         size_t report_data_len = StringWriterLength(contents);
649         config->report_data = StringWriterClose(contents);
650 
651         Seq *report = SeqNew(64, NULL);
652         size_t report_len = 0;
653 
654         StringRef ts_ref = StringGetToken(config->report_data, report_data_len, 0, "\n");
655         char *ts = (char *) ts_ref.data;
656         *(ts + ts_ref.len) = '\0';
657         SeqAppend(report, ts);
658 
659         /* start right after the newline after the timestamp header */
660         char *position = ts + ts_ref.len + 1;
661         char *report_line;
662         size_t report_line_len;
663         while (CFTestD_GetReportLine(position, &report_line, &report_line_len))
664         {
665             *(report_line + report_line_len) = '\0';
666             SeqAppend(report, report_line);
667             report_len += report_line_len;
668             position = report_line + report_line_len + 1; /* there's an extra newline after each report_line */
669         }
670 
671         config->report = report;
672         config->report_len = report_len;
673 
674         Log(LOG_LEVEL_NOTICE,
675             "Read %d bytes for report contents",
676             config->report_len);
677 
678         if (config->report_len <= 0)
679         {
680             Log(LOG_LEVEL_ERR, "Report file contained no bytes");
681             DoCleanupAndExit(EXIT_FAILURE);
682         }
683     }
684 
685     Log(LOG_LEVEL_INFO, "Starting server at %s...", config->address);
686     fflush(stdout); // for debugging startup
687 
688     config->ret = CFTestD_StartServer(config);
689 
690     free(config->report_data);
691 
692     /* we don't really need to do this here because the process is about the
693      * terminate, but it's a good way the cleanup actually works and doesn't
694      * cause a segfault or something */
695     ServerTLSDeInitialize(&(config->priv_key), &(config->pub_key), &(config->ssl_ctx));
696 
697     LoggingPrivSetContext(prior);
698 
699     return NULL;
700 }
701 
HandleSignal(int signum)702 static void HandleSignal(int signum)
703 {
704     switch (signum)
705     {
706     case SIGTERM:
707     case SIGINT:
708         // flush all logging before process ends.
709         fflush(stdout);
710         fprintf(stderr, "Terminating...\n");
711         TERMINATE = true;
712         break;
713     default:
714         break;
715     }
716 }
717 
718 /**
719  * @param ip_str string representation of an IPv4 address (the usual one, with
720  *               4 octets separated by dots)
721  * @return a new string representing the incremented IP address (HAS TO BE FREED)
722  */
IncrementIPaddress(const char * ip_str)723 static char *IncrementIPaddress(const char *ip_str)
724 {
725     uint32_t ip = (uint32_t) inet_addr(ip_str);
726     if (ip == INADDR_NONE)
727     {
728         Log(LOG_LEVEL_ERR, "Failed to parse address: '%s'", ip_str);
729         return NULL;
730     }
731 
732     int step = 1;
733     char *last_dot = strrchr(ip_str, '.');
734     assert(last_dot != NULL);   /* the doc comment says there must be dots! */
735     if (StringEqual(last_dot + 1, "255"))
736     {
737         /* avoid the network address (ending with 0) */
738         step = 2;
739     }
740     else if (StringEqual(last_dot + 1, "254"))
741     {
742         /* avoid the broadcast address and the network address */
743         step = 3;
744     }
745 
746     uint32_t ip_num = ntohl(ip);
747     ip_num += step;
748     ip = htonl(ip_num);
749 
750     struct in_addr ip_struct;
751     ip_struct.s_addr = ip;
752 
753     return xstrdup(inet_ntoa(ip_struct));
754 }
755 
main(int argc,char * argv[])756 int main(int argc, char *argv[])
757 {
758     signal(SIGINT, HandleSignal);
759     signal(SIGTERM, HandleSignal);
760 
761     Log(LOG_LEVEL_VERBOSE, "Starting cf-testd");
762     cfnet_init(NULL, NULL);
763     MakeSignalPipe();
764 
765     long n_threads = 1;
766     CFTestD_Config *config = CFTestD_CheckOpts(argc, argv, &n_threads);
767     if (config->address == NULL)
768     {
769         /* default to localhost */
770         config->address = xstrdup("127.0.0.1");
771     }
772 
773     CFTestD_Config **thread_configs = (CFTestD_Config**) xcalloc(n_threads, sizeof(CFTestD_Config*));
774     for (int i = 0; i < n_threads; i++)
775     {
776         thread_configs[i] = (CFTestD_Config*) xmalloc(sizeof(CFTestD_Config));
777 
778         if (config->report_file != NULL && strstr(config->report_file, "%d") != NULL)
779         {
780             /* replace the '%d' with the thread number */
781             asprintf(&(thread_configs[i]->report_file), config->report_file, i);
782         }
783         else
784         {
785             thread_configs[i]->report_file = SafeStringDuplicate(config->report_file);
786         }
787 
788         if (config->key_file != NULL && strstr(config->key_file, "%d") != NULL)
789         {
790             /* replace the '%d' with the thread number */
791             asprintf(&(thread_configs[i]->key_file), config->key_file, i);
792         }
793         else
794         {
795             thread_configs[i]->key_file = SafeStringDuplicate(config->key_file);
796         }
797 
798         if (i == 0)
799         {
800             thread_configs[i]->address = xstrdup(config->address);
801         }
802         else
803         {
804             thread_configs[i]->address = IncrementIPaddress(thread_configs[i-1]->address);
805         }
806     }
807 
808     CFTestD_ConfigDestroy(config);
809 
810     bool failure = false;
811     for (int i = 0; !failure && (i < n_threads); i++)
812     {
813         int ret = pthread_create(&(thread_configs[i]->t_id), NULL, CFTestD_ServeReport, thread_configs[i]);
814         if (ret != 0)
815         {
816             Log(LOG_LEVEL_ERR, "Failed to create a new thread nr. %d: %s\n", i, strerror(ret));
817             failure = true;
818         }
819     }
820 
821     if (failure)
822     {
823         CallCleanupFunctions();
824         return EXIT_FAILURE;
825     }
826 
827     for (int i = 0; i < n_threads; i++)
828     {
829         int ret = pthread_join(thread_configs[i]->t_id, NULL);
830         if (ret != 0)
831         {
832             Log(LOG_LEVEL_ERR, "Failed to join the thread nr. %d: %s\n", i, strerror(ret));
833         }
834         else
835         {
836             failure = failure && (thread_configs[i]->ret != 0);
837         }
838         CFTestD_ConfigDestroy(thread_configs[i]);
839     }
840 
841     CallCleanupFunctions();
842     return failure ? EXIT_FAILURE : EXIT_SUCCESS;
843 }
844