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