1 /*------------------------------------------------------------------------------
2  *
3  * Copyright (c) 2011-2021, EURid vzw. All rights reserved.
4  * The YADIFA TM software product is provided under the BSD 3-clause license:
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  *        * Redistributions of source code must retain the above copyright
11  *          notice, this list of conditions and the following disclaimer.
12  *        * Redistributions in binary form must reproduce the above copyright
13  *          notice, this list of conditions and the following disclaimer in the
14  *          documentation and/or other materials provided with the distribution.
15  *        * Neither the name of EURid nor the names of its contributors may be
16  *          used to endorse or promote products derived from this software
17  *          without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  *
31  *------------------------------------------------------------------------------
32  *
33  */
34 
35 /** @defgroup test
36  *  @ingroup test
37  *  @brief skeleton file
38  *
39  *
40  * Query for all DNSKEYs
41  *
42  * Generate KSK (Publish: none, Active: now, Inactive: never, Delete: never) + ZSK (Publish none: Active +60s, Inactive +180s, Delete: never)
43  * Remove all DNSKEYs and add KSK and ZSK
44  *
45  * loop:
46  *   Generate ZSK (Publish none: Active +60s, Inactive +180s, Delete: never)
47  *   Wait for update time + 60 seconds.
48  *   Remove previous ZSK and add new ZSK
49  *
50  */
51 
52 #include <dnscore/dnscore.h>
53 #include <dnscore/dnskey.h>
54 #include <dnscore/format.h>
55 #include <dnscore/config-cmdline.h>
56 #include <dnscore/packet_writer.h>
57 #include <dnscore/signals.h>
58 
59 #include <dnscore/timems.h>
60 #include <dnscore/thread_pool.h>
61 
62 #include <dnscore/dnskey-signature.h>
63 #include <dnscore/packet_reader.h>
64 #include <dnscore/dnskey-keyring.h>
65 #include <dnscore/parsing.h>
66 #include <dnscore/zone_reader_text.h>
67 #include <dnscore/server-setup.h>
68 #include <dnscore/logger_channel_stream.h>
69 #include <dnscore/buffer_output_stream.h>
70 #include <dnscore/logger.h>
71 #include <dnscore/file_output_stream.h>
72 #include <dnscore/pid.h>
73 #include <dnscore/dnscore-release-date.h>
74 
75 #include <sys/stat.h>
76 
77 #include "keyroll.h"
78 #include "keyroll-config.h"
79 
80 #include "config-dnssec-policy.h"
81 #include "dnssec-policy.h"
82 #include "buildinfo.h"
83 
84 #define PURGE_QUESTION "YES"
85 
86 extern logger_handle *g_dnssec_logger;
87 extern logger_handle *g_keyroll_logger;
88 
89 #define MODULE_MSG_HANDLE g_keyroll_logger
90 
91 #define FIRST_JANUARY_2019_00_00_00 1546300800
92 #define FIRST_JANUARY_2021_00_00_00 1609459200
93 
94 #define SERVER_FAILURE_RETRY_DELAY 30
95 #define CONSECUTIVE_ERRORS_BEFORE_RESTART 60
96 
97 #define WAIT_MARGIN (ONE_SECOND_US * 10)
98 
99 #define PROGRAM_NAME "yakeyrolld"
100 #define KEYROLL_CONFIG_SECTION "yakeyrolld"
101 #define RELEASEDATE YADIFA_DNSCORE_RELEASE_DATE
102 
103 // mount -t tmpfs -o size=16384 tmpfs /registry/yadifa/var/log/yakeyrolld
104 
105 static random_ctx rnd;
106 
107 enum PROGRAM_MODE
108 {
109     NONE = 0,
110     GENERATE,
111     PLAY,
112     PLAYLOOP,
113     PRINT,
114     PRINT_JSON,
115     TEST
116 };
117 
118 static value_name_table program_mode_enum_table[]=
119 {
120     {NONE, "none"},
121     {PLAY, "play"},
122     {PLAYLOOP, "playloop"},
123     {GENERATE, "generate"},
124     {PRINT, "print"},
125     {PRINT_JSON, "print-json"},
126     {TEST, "test"},
127     {0, NULL}
128 };
129 
130 struct main_args
131 {
132     ptr_vector domains;
133     ptr_vector fqdns;
134     char *configuration_file_path;
135     char *log_path;
136     char *keys_path;
137     char *plan_path;
138     char *pid_path;
139     char *pid_file;
140     char *generate_from;
141     char *generate_until;
142     char *policy_name;
143     host_address *server;
144 
145     uid_t uid;
146     gid_t gid;
147     u32 timeout;
148     u32 ttl;
149 
150     u32 update_apply_verify_retries;        // if an update wasn't applied successfully, retry CHECKING this amount of times
151     u32 update_apply_verify_retries_delay;  // time between the above retries
152 
153     u32 match_verify_retries;        // if there is not match, retry checking this amount of times
154     u32 match_verify_retries_delay;  // time between the above retries
155 
156     int program_mode;
157     bool reset;
158     bool purge;
159     bool dryrun;
160     bool wait_for_yadifad;
161     bool daemonise;
162     bool print_plan;
163     bool user_confirmation;
164 #if DEBUG
165     bool with_secret_keys;
166 #endif
167 };
168 
169 typedef struct main_args main_args;
170 
171 struct testing_args
172 {
173     s64 timeus_offset;
174 };
175 
176 typedef struct testing_args testing_args;
177 
178 static ya_result
directory_writable(const char * path)179 directory_writable(const char *path)
180 {
181     if(path == NULL)
182     {
183         return UNEXPECTED_NULL_ARGUMENT_ERROR;
184     }
185 
186     struct stat ds;
187 
188     if(stat(path, &ds) < 0)
189     {
190         int err = errno;
191         log_err("error: '%s': %s", path, strerror(err));
192         formatln("error: '%s': %s", path, strerror(err));
193 
194         return MAKE_ERRNO_ERROR(err);
195     }
196 
197     if((ds.st_mode & S_IFMT) != S_IFDIR)
198     {
199         log_err("error: '%s' is not a directory", path);
200         formatln("error: '%s' is not a directory", path);
201 
202         return INVALID_PATH;
203     }
204 
205     char tempfile[PATH_MAX];
206 
207     ya_result ret = snformat(tempfile, sizeof(tempfile), "%s/ydf.XXXXXX", path);
208 
209     if(FAIL(ret))
210     {
211         log_err("error: '%s' temp file name creation failed: %r", path, ret);
212         formatln("error: '%s' temp file name creation failed: %r", path, ret);
213         return ret;
214     }
215 
216     if(ret >= PATH_MAX)
217     {
218         log_err("error: '%s' path is too big", path);
219         formatln("error: '%s' path is too big", path);
220         return INVALID_PATH;
221     }
222 
223     int tempfd;
224     if((tempfd = mkstemp_ex(tempfile)) < 0)
225     {
226         int ret = ERRNO_ERROR;
227 #ifndef WIN32
228         formatln("error: '%s' is not writable as (%d:%d): %r", path, getuid(), getgid(), ret);
229 #else
230         log_err("error: '%s' is not writable: %r", fullpath, ret);
231         formatln("error: '%s' is not writable: %r", fullpath, ret);
232 #endif
233         return ret;
234     }
235 
236     unlink(tempfile);
237     close_ex(tempfd);
238 
239     return SUCCESS;
240 }
241 
242 
243 #ifndef PREFIX
244 #define PREFIX "/usr/local"
245 #endif
246 
247 #ifndef LOCALSTATEDIR
248 #define LOCALSTATEDIR PREFIX "/var"
249 #endif
250 
251 #ifndef SYSCONFDIR
252 #define SYSCONFDIR PREFIX "/etc"
253 #endif
254 
255 #define CONFIGURATION_FILE_PATH_DEFAULT SYSCONFDIR "/yakeyrolld.conf"
256 
257 #define CONFIG_TYPE main_args
258 
259 CONFIG_BEGIN(main_args_desc)
260         CONFIG_STRING_ARRAY(domains,NULL, 200)      // I'm using a thread-pool for this, it cannot go beyond THREAD_POOL_SIZE_LIMIT_MAX threads.
261         CONFIG_PATH(log_path, LOCALSTATEDIR "/log/yakeyrolld")
262         CONFIG_FILE(configuration_file_path, CONFIGURATION_FILE_PATH_DEFAULT)
263         CONFIG_PATH(keys_path, LOCALSTATEDIR "/zones/keys")
264         CONFIG_PATH(plan_path, LOCALSTATEDIR "/plans")
265         CONFIG_PATH(pid_path, LOCALSTATEDIR "/run")
266         CONFIG_STRING(pid_file, "yakeyrolld.pid")
267         CONFIG_HOST_LIST(server, "127.0.0.1")
268         CONFIG_U32(timeout, "3")
269         CONFIG_U32(ttl, "600")
270 
271         CONFIG_U32_RANGE(update_apply_verify_retries, "60", 0, 3600)        // if an update wasn't applied successfully, retry CHECKING this amount of times
272         CONFIG_U32_RANGE(update_apply_verify_retries_delay, "1", 1, 60)     // time between the above retries
273 
274         CONFIG_U32_RANGE(match_verify_retries, "60", 0, 3600)        // if there is not match, retry checking this amount of times
275         CONFIG_U32_RANGE(match_verify_retries_delay, "1", 1, 60)  // time between the above retries
276 
277         CONFIG_STRING(generate_from, "now")
278         CONFIG_STRING(generate_until, "+1y")
279         CONFIG_STRING(policy_name, "")
280         CONFIG_UID(uid, "0")
281         CONFIG_GID(gid, "0")
282         CONFIG_BOOL(reset, "0")
283         CONFIG_BOOL(dryrun, "0")
284         CONFIG_BOOL(wait_for_yadifad, "1")
285         CONFIG_BOOL(daemonise, "0")
286         CONFIG_BOOL(print_plan, "0")
287         CONFIG_BOOL(user_confirmation, "1")
288 #if DEBUG
289         CONFIG_BOOL(with_secret_keys, "0")
290 #endif
291         CONFIG_ENUM(program_mode, "none", program_mode_enum_table)
292         CONFIG_ALIAS(policy, policy_name)
293         CONFIG_ALIAS(domain, domains)
294         CONFIG_ALIAS(daemon, daemonise)
295         CONFIG_ALIAS(plans_path, plan_path)
296 CONFIG_END(main_args_desc)
297 #undef CONFIG_TYPE
298 
299 #define CONFIG_TYPE testing_args
300 
301 CONFIG_BEGIN(testing_args_desc)
302         CONFIG_U64(timeus_offset, "0")
303 CONFIG_END(testing_args_desc)
304 
305 CMDLINE_BEGIN(keyroll_cmdline)
306         CMDLINE_VERSION_HELP(keyroll_cmdline)
307         CMDLINE_SECTION(KEYROLL_CONFIG_SECTION)
308         CMDLINE_OPT("config", 'c', "configuration_file_path")
309         CMDLINE_HELP("", "sets the configuration file to use (default: " CONFIGURATION_FILE_PATH_DEFAULT ")")
310         CMDLINE_OPT("mode", 'm', "program_mode")
311         CMDLINE_HELP("", "sets the program mode (generate,play,playloop,print,json)")
312         CMDLINE_OPT("domain",0,"domain")
313         CMDLINE_HELP("fqdn", "the domain name, overrides the domains from the configuration file")
314         CMDLINE_OPT("path",'p',"keys_path")
315         CMDLINE_HELP("directory", "the directory where to store the keys")
316         CMDLINE_OPT("server",'s',"server")
317         CMDLINE_HELP("address", "the address of the server")
318         CMDLINE_OPT("ttl",'t',"ttl")
319         CMDLINE_HELP("seconds", "the TTL to use for both DNSKEY and RRSIG records")
320         CMDLINE_BOOL("reset",0,"reset")
321         CMDLINE_HELP("", "start by removing all the keys, create a new KSK and a new ZSK")
322         CMDLINE_OPT("policy",0,"policy_name")
323         CMDLINE_HELP("", "name of the policy to use")
324         CMDLINE_OPT("from",0,"generate_from")
325         CMDLINE_HELP("time", "at what time the plan starts (e.g. : now, -1y, YYYYMMDDHHSS in UTC).")
326         CMDLINE_OPT("until",0,"generate_until")
327         CMDLINE_HELP("time", "the upper time limit covered by the plan (+1y, YYYYMMDDHHSS in UTC).")
328         CMDLINE_BLANK()
329         CMDLINE_INDENT(4)
330         CMDLINE_IMSG("", "time values can be:")
331         CMDLINE_BLANK()
332         CMDLINE_INDENT(4)
333         CMDLINE_IMSG("", "now")
334         CMDLINE_IMSG("", "tomorrow")
335         CMDLINE_IMSG("", "yesterday")
336         CMDLINE_IMSG("", "[+-]#{years|months|weeks|days|seconds} where # is an integer")
337         CMDLINE_IMSG("", "YYYY-MM-DD")
338         CMDLINE_IMSG("", "YYYYMMDD")
339         CMDLINE_IMSG("", "YYYYMMDDHHMMSSUUUUUU")
340         CMDLINE_BLANK()
341         CMDLINE_INDENT(-8)
342         CMDLINE_BOOL("dryrun",0,"dryrun")
343         CMDLINE_HELP("", "do not send the update to the server")
344         CMDLINE_BOOL("wait", 0, "wait_for_yadifad")
345         CMDLINE_HELP("", "wait for yadifad to answer before starting to work (default)")
346         CMDLINE_BOOL_NOT("nowait", 0, "wait_for_yadifad")
347         CMDLINE_HELP("", "do not wait for yadifad to answer before starting to work")
348         CMDLINE_BOOL("daemon", 0, "daemonise")
349         CMDLINE_HELP("", "daemonise the program for supported modes (default)")
350         CMDLINE_BOOL_NOT("nodaemon", 0, "daemonise")
351         CMDLINE_HELP("", "do not daemonise the program (needed for systemd)")
352         CMDLINE_BOOL_NOT("noconfirm",'Y', "user_confirmation")
353         CMDLINE_HELP("", "do not ask for confirmation before destroying steps and .key and .private files")
354         CMDLINE_BOOL("print-plan", 0, "print_plan")
355         CMDLINE_HELP("", "prints the complete plan after generation or after loading")
356 #if DEBUG
357         CMDLINE_BOOL("with-secret-keys", 0, "with_secret_keys")
358 #endif
359 #if DEBUG
360         CMDLINE_SECTION("testing")
361         CMDLINE_OPT("timeus-offset", 0, "timeus_offset")
362         CMDLINE_HELP("", "fakes the current time changing the time by that many seconds (testing)")
363 #endif
364         CMDLINE_BLANK()
365 CMDLINE_END(keyroll_cmdline)
366 
367 static main_args g_config; // initilised in main_config(argc, argv)
368 static testing_args g_testing = {0};
369 
370 static void
help_print(const char * name)371 help_print(const char *name)
372 {
373     formatln("%s [-c configurationfile] [...]\n\n", name);
374     cmdline_print_help(keyroll_cmdline, 16, 28, " :  ", 48, termout);
375 }
376 
377 /**
378  * To abstract key generation or reading from storage
379  */
380 
381 static ya_result
main_config_main_postprocess(struct config_section_descriptor_s * csd)382 main_config_main_postprocess(struct config_section_descriptor_s *csd)
383 {
384     (void)csd;
385     // no logger if help is requested
386 
387     if(cmdline_help_get() || cmdline_version_get())
388     {
389         return SUCCESS;
390     }
391 
392     config_set_log_base_path(g_config.log_path);
393     keyroll_set_dryrun_mode(g_config.dryrun);
394 
395     logger_start();
396 
397     logger_handle_create("keyroll", &g_keyroll_logger);
398     logger_handle_create("dnssec", &g_dnssec_logger);
399 
400     timeus_set_offset(g_testing.timeus_offset);
401 
402     logger_flush();
403 
404     return SUCCESS;
405 }
406 
407 static void
yakeyrolld_print_authors()408 yakeyrolld_print_authors()
409 {
410     print("\n"
411           "\t\tYADIFAD authors:\n"
412           "\t\t---------------\n"
413           "\t\t\n"
414           "\t\tGery Van Emelen\n"
415           "\t\tEric Diaz Fernandez\n"
416           "\n"
417           "\t\tContact: " PACKAGE_BUGREPORT "\n"
418     );
419     flushout();
420 }
421 
422 static void
yakeyrolld_show_version(u8 level)423 yakeyrolld_show_version(u8 level)
424 {
425     switch(level)
426     {
427         case 1:
428             osformatln(termout, "%s %s (%s)\n", YKEYROLL_NAME, YKEYROLL_VERSION, RELEASEDATE);
429             break;
430         case 2:
431 #if HAS_BUILD_TIMESTAMP && defined(__DATE__)
432             osformatln(termout, "%s %s (released %s, compiled %s)\n\nbuild settings: %s\n", YKEYROLL_NAME, YKEYROLL_VERSION, RELEASEDATE, __DATE__, BUILD_OPTIONS);
433 #else
434             osformatln(termout, "%s %s (released %s)\n\nbuild settings: %s\n", YKEYROLL_NAME, YKEYROLL_VERSION, RELEASEDATE, BUILD_OPTIONS);
435 #endif
436             break;
437         case 3:
438         default:
439 #if HAS_BUILD_TIMESTAMP && defined(__DATE__)
440             osformatln(termout, "%s %s (released %s, compiled %s)\n", PROGRAM_NAME, YKEYROLL_VERSION, RELEASEDATE, __DATE__);
441 #else
442             osformatln(termout, "%s %s (released %s)\n", YKEYROLL_NAME, YKEYROLL_VERSION, RELEASEDATE);
443 #endif
444             yakeyrolld_print_authors();
445             break;
446     }
447 
448     flushout();
449 }
450 
451 /**
452  * Reads the configuration.
453  * It's only a command line but extending to a file is relatively trivial.
454  */
455 
456 static ya_result
main_config(int argc,char * argv[])457 main_config(int argc, char *argv[])
458 {
459     config_error_s cfg_error;
460     ya_result ret;
461 
462     config_init();
463 
464     memset(&g_config, 0, sizeof(g_config));
465 
466     ptr_vector_init(&g_config.domains);
467     ptr_vector_init(&g_config.fqdns);
468 
469     int priority = 0;
470 
471     if(FAIL(ret = config_register_cmdline(priority++))) // without this line, the help will not work
472     {
473         return ret;
474     }
475 
476     if(FAIL(ret = config_register_struct(KEYROLL_CONFIG_SECTION, main_args_desc, &g_config, priority++)))
477     {
478         return ret;
479     }
480 
481     if(FAIL(ret = config_register_struct("testing", testing_args_desc, &g_testing, priority++)))
482     {
483         return ret;
484     }
485 
486     // hook the post-processing to know what to do with the logger
487     // this is a bit dirty but the vtbl is a copy of an original and the const is a safeguard here
488     // I'll need to add a registration function that allows to overwrite these.
489 
490     // also registers key-roll denial key-template and key-suite (hence the + 5)
491 
492     if(FAIL(ret = config_register_dnssec_policy(NULL, priority)))
493     {
494         return ret;
495     }
496 
497     priority += 5;
498 
499     if(FAIL(ret = config_register_logger(NULL, NULL, priority)))
500     {
501         return ret;
502     }
503 
504     // priority += 2;
505 
506     // shouldn't this be 2 sources instead of twice one ?
507 
508     struct config_source_s sources[1];
509 
510     if(FAIL(ret = config_source_set_commandline(&sources[0], keyroll_cmdline, argc, argv)))
511     {
512         formatln("command line definition: %r", ret);
513         return ret;
514     }
515 
516     if(FAIL(ret = config_read_from_sources(sources, 1, &cfg_error)))
517     {
518         if(cmdline_help_get())
519         {
520             help_print(argv[0]);
521             ret = SUCCESS;
522         }
523         else if(cmdline_version_get())
524         {
525             yakeyrolld_show_version(cmdline_version_get());
526             ret = SUCCESS;
527         }
528         else
529         {
530             formatln("settings: (%s:%i) %s: %s: %r", cfg_error.file, cfg_error.line_number, cfg_error.line, cfg_error.variable_name, ret);
531         }
532         flushout();
533         return ret;
534     }
535 
536     if(cmdline_help_get())
537     {
538         help_print(argv[0]);
539         return SUCCESS;
540     }
541 
542     if(cmdline_version_get())
543     {
544         yakeyrolld_show_version(cmdline_version_get());
545         return SUCCESS;
546     }
547 
548     config_source_set_file(&sources[0], g_config.configuration_file_path, CONFIG_SOURCE_FILE);
549 
550     config_section_descriptor_s *main_desc = config_section_get_descriptor(KEYROLL_CONFIG_SECTION);
551     config_section_descriptor_vtbl_s *vtbl = (config_section_descriptor_vtbl_s*)main_desc->vtbl;
552     vtbl->postprocess = main_config_main_postprocess;
553 
554     if(FAIL(ret = config_read_from_sources(sources, 1, &cfg_error)))
555     {
556         formatln("settings: (%s:%i) %s %s: %r", cfg_error.file, cfg_error.line_number, cfg_error.line, cfg_error.variable_name, ret);
557         flushout();
558         return ret;
559     }
560 
561     if(g_config.server->port == 0)
562     {
563         g_config.server->port = NU16(DNS_DEFAULT_PORT);
564     }
565 
566     for(int i = 0; i <= ptr_vector_last_index(&g_config.domains); ++i)
567     {
568         const char *name = (const char*)ptr_vector_get(&g_config.domains, i);
569         u8 *fqdn = dnsname_zdup_from_name(name);
570         if(fqdn == NULL)
571         {
572             formatln("cannot parse domain name: %s", name);
573             ret = PARSESTRING_ERROR;
574             return ret;
575         }
576         ptr_vector_append(&g_config.fqdns, fqdn);
577     }
578 
579     // no stdout channel in daemon mode
580 
581     if(!((g_config.program_mode == PLAYLOOP) && g_config.daemonise))
582     {
583         if(!config_logger_isconfigured())
584         {
585             output_stream stdout_os;
586             logger_channel *stdout_channel;
587 
588             fd_output_stream_attach(&stdout_os, dup_ex(1));
589             buffer_output_stream_init(&stdout_os, &stdout_os, 65536);
590             stdout_channel = logger_channel_alloc();
591             if(stdout_channel == NULL)
592             {
593                 return INVALID_STATE_ERROR;
594             }
595             logger_channel_stream_open(&stdout_os, FALSE, stdout_channel);
596 
597             logger_channel_register("stdout", stdout_channel);
598 
599 #if !DEBUG
600             logger_handle_add_channel("keyroll", MSG_PROD_MASK, "stdout");
601 #else
602             logger_handle_add_channel("keyroll", MSG_ALL_MASK, "stdout");
603             logger_handle_add_channel("dnssec", MSG_ALL_MASK, "stdout");
604 #endif
605         }
606     }
607 
608     if(FAIL(ret = directory_writable(g_config.log_path)))
609     {
610         return ret;
611     }
612 
613     return ret;
614 }
615 
616 static ya_result
get_user_confirmation()617 get_user_confirmation()
618 {
619     ya_result ret = SUCCESS;
620 
621     log_notice("Asking user to confirm by typing '" PURGE_QUESTION "'");
622     print("Please confirm by typing '" PURGE_QUESTION "' (without the '') followed by the ENTER key: ");
623     flushout();
624 
625     char *line_buffer = NULL;
626     size_t line_buffer_size = 0;
627     ssize_t n = getline(&line_buffer, &line_buffer_size, stdin);
628 
629     if(n < 0)
630     {
631         ret = ERRNO_ERROR;
632         formatln("getline failed: %r", ret);
633         flushout();
634         free(line_buffer);
635         return ret;
636     }
637 
638     while((n > 0) && isspace(line_buffer[--n]))
639     {
640         line_buffer[n] = '\0';
641     }
642 
643     if(strcmp(line_buffer, PURGE_QUESTION) != 0)
644     {
645         log_err("expected: '" PURGE_QUESTION "', got '%s': stopping", line_buffer);
646         formatln("expected: '" PURGE_QUESTION "', got '%s': stopping", line_buffer);
647         flushout();
648         free(line_buffer);
649         return PARSEWORD_NOMATCH_ERROR;
650     }
651     else
652     {
653         log_notice("Got user confirmation");
654     }
655 
656     return ret;
657 }
658 
659 static ya_result
program_mode_generate(const u8 * domain)660 program_mode_generate(const u8 *domain)
661 {
662     ya_result ret;
663 
664     rnd = random_init(0);
665 
666     if(dirent_get_file_type(g_config.plan_path, ".") == DT_UNKNOWN)
667     {
668         formatln("%{dnsname}: having trouble with directory '%s'", domain, g_config.plan_path);
669         return INVALID_PATH;
670     }
671 
672     keyroll_t keyroll;
673 
674     if(FAIL(ret = keyroll_init(&keyroll, domain, g_config.plan_path, g_config.keys_path, g_config.server, TRUE)))
675     {
676         return ret;
677     }
678 
679     if(FAIL(ret = keyroll_update_apply_verify_retries_set(&keyroll, g_config.update_apply_verify_retries, g_config.update_apply_verify_retries_delay)))
680     {
681         log_err("%{dnsname}: update apply retry combination out of acceptable range", domain);
682         formatln("%{dnsname}: update apply retry combination out of acceptable range", domain);
683         keyroll_finalize(&keyroll);
684         return ret;
685     }
686 
687     if(FAIL(ret = keyroll_match_verify_retries_set(&keyroll, g_config.match_verify_retries, g_config.match_verify_retries_delay)))
688     {
689         log_err("%{dnsname}: match retry combination out of acceptable range", domain);
690         formatln("%{dnsname}: match retry combination out of acceptable range", domain);
691         keyroll_finalize(&keyroll);
692         return ret;
693     }
694 
695     // at this point:
696     // _ we know the present state on the server
697     // _ we know the plan folder for this domain exists
698 
699     if(g_config.reset)
700     {
701         // delete the content of the plan folder
702 
703         if(!g_config.dryrun)
704         {
705             log_info("%{dnsname}: deleting the plan and private keys for domain", keyroll.domain);
706             formatln("%{dnsname}: deleting the plan and private keys for domain", keyroll.domain);
707             keyroll_plan_purge(&keyroll);
708         }
709         else
710         {
711             log_info("%{dnsname}: dryrun: not really deleting the plan and private keys for domain", keyroll.domain);
712             formatln("%{dnsname}: dryrun: not really deleting the plan and private keys for domain", keyroll.domain);
713         }
714     }
715     else
716     {
717         if(FAIL(ret = keyroll_plan_load(&keyroll)))
718         {
719             if(ret != MAKE_ERRNO_ERROR(ENOENT))
720             {
721                 log_err("%{dnsname}: plan loading failed: %r", keyroll.domain, ret);
722                 formatln("%{dnsname}: plan loading failed: %r", keyroll.domain, ret);
723 
724                 keyroll_finalize(&keyroll);
725                 return ret;
726             }
727 
728             log_info("%{dnsname}: there are no plans for the domain in the directory '%s'", keyroll.domain, g_config.plan_path);
729             formatln("%{dnsname}: there are no plans for the domain in the directory '%s'", keyroll.domain, g_config.plan_path);
730         }
731     }
732 
733     s64 generate_from = timeus_from_smarttime(g_config.generate_from);
734 
735     if(generate_from < 0)
736     {
737         log_err("%{dnsname}: cannot parse '%s'", domain, g_config.generate_from);
738         formatln("%{dnsname}: cannot parse '%s'", domain, g_config.generate_from);
739         keyroll_finalize(&keyroll);
740         return ret;
741     }
742 
743     generate_from /= ONE_SECOND_US;
744     generate_from *= ONE_SECOND_US;
745 
746     s64 generate_until = timeus_from_smarttime_ex(g_config.generate_until, generate_from);
747 
748     if(generate_until < 0)
749     {
750         log_err("%{dnsname}: cannot parse '%s'", domain, g_config.generate_until);
751         formatln("%{dnsname}: cannot parse '%s'", domain, g_config.generate_until);
752         return ret;
753     }
754 
755     log_info("%{dnsname}: covering %llU to %llU", domain, generate_from, generate_until);
756     formatln("%{dnsname}: covering %llU to %llU", domain, generate_from, generate_until);
757 
758     if(FAIL(ret = keyroll_plan_with_policy(&keyroll, generate_from, generate_until, g_config.policy_name)))
759     {
760         log_err("%{dnsname}: policy-based planning failed: %r", domain, ret);
761         formatln("%{dnsname}: policy-based planning failed: %r", domain, ret);
762         return ret;
763     }
764 
765     if(g_config.print_plan)
766     {
767         if(FAIL(ret = keyroll_print(&keyroll, termout)))
768         {
769             log_err("%{dnsname}: the plan is not perfect", domain);
770             formatln("%{dnsname}: the plan is not perfect", domain);
771         }
772     }
773 
774     if(g_config.dryrun)
775     {
776         log_info("%{dnsname}: dryrun: not storing the plan", domain);
777         formatln("%{dnsname}: dryrun: not storing the plan", domain);
778         ret = SUCCESS;
779     }
780     else
781     {
782         if(ISOK(ret = keyroll_store(&keyroll)))
783         {
784             log_info("%{dnsname}: plan stored", domain);
785             formatln("%{dnsname}: plan stored", domain);
786         }
787         else
788         {
789             log_err("%{dnsname}: failed to store the plan: %r", domain, ret);
790             formatln("%{dnsname}: failed to store the plan: %r", domain, ret);
791         }
792     }
793 
794     keyroll_finalize(&keyroll);
795 
796     return ret;
797 }
798 
799 static ya_result
program_mode_generate_all()800 program_mode_generate_all()
801 {
802     pid_t pid;
803     ya_result ret;
804     char pid_file_path_buffer[PATH_MAX];
805     char *pid_file_path = &pid_file_path_buffer[0];
806 
807     snformat(pid_file_path_buffer, sizeof(pid_file_path_buffer), "%s/%s", g_config.pid_path, g_config.pid_file);
808 
809     if(FAIL(ret = pid_check_running_program(pid_file_path, &pid)))
810     {
811         log_err("already running with pid: %lu (%s)", pid, pid_file_path);
812         return ret;
813     }
814 
815     if(FAIL(ret = directory_writable(g_config.plan_path)))
816     {
817         return ret;
818     }
819 
820     if(ISOK(ret = server_setup_env(&pid, &pid_file_path, g_config.uid, g_config.gid, SETUP_CREATE_PID_FILE|SETUP_ID_CHANGE|SETUP_CORE_LIMITS)))
821     {
822         if(g_config.reset && g_config.user_confirmation)
823         {
824             println("WARNING: A full data reset has been required for the following domains:");
825             // delete the content of the plan folder
826             for(int i = 0; i <= ptr_vector_last_index(&g_config.fqdns); ++i)
827             {
828                 const u8 *domain = (const u8*)ptr_vector_get(&g_config.fqdns, i);
829                 formatln("    %{dnsname}", domain);
830             }
831             println("All currently stored steps and private keys for the above domains will be erased.\nThis operation cannot be undone.");
832             if(FAIL(ret = get_user_confirmation()))
833             {
834                 return ret;
835             }
836         }
837 
838         for(int i = 0; i <= ptr_vector_last_index(&g_config.fqdns); ++i)
839         {
840             const u8 *domain = (const u8*)ptr_vector_get(&g_config.fqdns, i);
841             log_info("zone generate: %{dnsname}", domain);
842             if(FAIL(ret = program_mode_generate(domain)))
843             {
844                 log_err("zone generate: %{dnsname} failed: %r", domain, ret);
845                 break;
846             }
847         }
848 
849         unlink(pid_file_path);
850     }
851 
852     return ret;
853 }
854 
855 static void
signal_int(u8 signum)856 signal_int(u8 signum)
857 {
858     (void)signum;
859 
860     if(!dnscore_shuttingdown())
861     {
862         dnscore_shutdown();
863     }
864 
865     signal_handler_stop();
866 }
867 
868 static void
signal_hup(u8 signum)869 signal_hup(u8 signum)
870 {
871     (void)signum;
872 
873     logger_reopen();
874 }
875 
876 static ya_result
program_mode_play(const u8 * domain,bool does_loop)877 program_mode_play(const u8 *domain, bool does_loop)
878 {
879     ya_result ret;
880     int consecutive_errors = 0;
881 
882     rnd = random_init(0);
883 
884     if(dirent_get_file_type(g_config.plan_path, ".") == DT_UNKNOWN)
885     {
886         log_info("play: %{dnsname}: having trouble with directory '%s'", domain, g_config.plan_path);
887         return INVALID_PATH;
888     }
889 
890     keyroll_t keyroll;
891 
892     // start from an empty state
893     if(FAIL(ret = keyroll_init(&keyroll, domain, g_config.plan_path, g_config.keys_path, g_config.server, FALSE)))
894     {
895         return ret;
896     }
897 
898     // at this point:
899     // _ we know the present state on the server
900     // _ we know the plan folder for this domain exists
901 
902     if(does_loop)
903     {
904         log_info("play: %{dnsname}: loading plan", domain);
905         logger_flush();
906     }
907 
908     if(FAIL(ret = keyroll_plan_load(&keyroll)))
909     {
910         if(ret != MAKE_ERRNO_ERROR(ENOENT))
911         {
912             log_info("play: %{dnsname}: plan loading failed: %r", domain, ret);
913         }
914         else
915         {
916             log_info("play: %{dnsname}: there are no plans on storage (%s)", domain, g_config.plan_path);
917         }
918 
919         logger_flush();
920 
921         keyroll_finalize(&keyroll);
922 
923         return ret;
924     }
925 
926     s64 step_time;
927 
928     do
929     {
930         step_time = timeus_with_offset();
931 
932         log_info("play: %{dnsname}: now is %llU (%lli)", domain, step_time, step_time);
933 
934         keyroll_step_t *current_step = keyroll_get_current_step_at(&keyroll, step_time);
935 
936         if(current_step == NULL)
937         {
938             log_info("play: %{dnsname}: there are no steps registered for this time", domain);
939 
940             keyroll_finalize(&keyroll);
941 
942             return INVALID_STATE_ERROR;
943         }
944 
945         log_info("play: %{dnsname}: the current step happened at %llU (%lli)", domain, current_step->epochus, current_step->epochus);
946 
947         keyroll_step_t *next_step = keyroll_get_next_step_from(&keyroll, step_time + 1);
948 
949         s64 next_step_time;
950 
951         if(next_step != NULL)
952         {
953             next_step_time = next_step->epochus;
954             log_info("play: %{dnsname}: the step that will follow will happen at %llU (%lli)", domain, next_step_time, next_step_time);
955         }
956         else
957         {
958             next_step_time = ONE_SECOND_US * MAX_U32;
959         }
960 
961         /*
962         // check the expected set with the server
963         // do a query for all DNSKEY + RRSIG and compare with the step
964 
965         ptr_vector current_dnskey_rrsig_rr;
966         ptr_vector_init_ex(&current_dnskey_rrsig_rr, 32);
967         */
968         const keyroll_step_t *matched_step = NULL;
969 
970         ret = keyroll_get_state_find_match_and_play(&keyroll, step_time, current_step, &matched_step);
971 
972         if(ISOK(ret))
973         {
974             log_info("play: %{dnsname}: first loop ended (%u)", domain, ret);
975             break;
976         }
977 
978         if(ret != STOPPED_BY_APPLICATION_SHUTDOWN)
979         {
980             log_info("play: %{dnsname}: keyroll_get_state_find_match returned %r (retrying in " TOSTRING(SERVER_FAILURE_RETRY_DELAY) " seconds)", domain, ret);
981 
982             s64 now = timeus();
983 
984             if(now + ONE_SECOND_US * SERVER_FAILURE_RETRY_DELAY < next_step_time)
985             {
986                 for(int i = 0; (i < SERVER_FAILURE_RETRY_DELAY) && !dnscore_shuttingdown(); ++i)
987                 {
988                     sleep(1);
989                 }
990             }
991             else
992             {
993                 // we have gone through a step, current computations are invalid : restart the roll for this domain
994                 ret = KEYROLL_MUST_REINITIALIZE;
995 
996                 keyroll_finalize(&keyroll);
997 
998                 return ret;
999             }
1000         }
1001     }
1002     while(g_config.wait_for_yadifad && !dnscore_shuttingdown());
1003 
1004     if(FAIL(ret))
1005     {
1006         log_notice("play: %{dnsname}: keyroll_get_state_find_match returned %r", domain, ret);
1007 
1008         keyroll_finalize(&keyroll);
1009 
1010         return ret;
1011     }
1012 
1013     keyroll_step_t *next_step = keyroll_get_next_step_from(&keyroll, step_time);
1014 
1015     if(next_step != NULL)
1016     {
1017         log_info("play: %{dnsname}: the next step happens at %llU (%lli)", domain, next_step->epochus, next_step->epochus);
1018 
1019         // find the interval for now
1020 
1021         s64 last_warning_us = 0;
1022 
1023         // wait until the next event
1024 
1025         while(!dnscore_shuttingdown())
1026         {
1027             log_info("play: %{dnsname}: waiting until %llU (%lli)", domain, next_step->epochus, next_step->epochus);
1028 
1029             do
1030             {
1031                 s64 now = timeus_with_offset();
1032 
1033                 if(now - WAIT_MARGIN >= next_step->epochus)
1034                 {
1035                     break;
1036                 }
1037                 else
1038                 {
1039                     usleep(MAX(MIN(next_step->epochus - (now - WAIT_MARGIN), WAIT_MARGIN), 1000));
1040                 }
1041             }
1042             while(!dnscore_shuttingdown());
1043 
1044             if(dnscore_shuttingdown())
1045             {
1046                 log_info("play: %{dnsname}: shutting down", domain);
1047                 logger_flush();
1048                 break;
1049             }
1050 
1051             step_time = timeus_with_offset();
1052 
1053             keyroll_step_t *current_step = keyroll_get_current_step_at(&keyroll, step_time);
1054 
1055             if(current_step == NULL)
1056             {
1057                 log_err("play: %{dnsname}: there are no steps registered for this time: shutting down", domain);
1058                 break;
1059             }
1060 
1061             ret = keyroll_get_state_find_match_and_play(&keyroll, step_time, current_step, NULL);
1062 
1063             log_warn("play: %{dnsname}: match and play returned: %r (%x)", domain, ret, ret);
1064 
1065             if(ISOK(ret))
1066             {
1067                 consecutive_errors = 0;
1068             }
1069             else
1070             {
1071                 // test for error conditions warranting a retry
1072 
1073                 switch(ret)
1074                 {
1075                     case MAKE_ERRNO_ERROR(ETIMEDOUT):
1076                     case MAKE_ERRNO_ERROR(EADDRNOTAVAIL):
1077                     case MAKE_ERRNO_ERROR(EAGAIN):
1078                     case MAKE_DNSMSG_ERROR(RCODE_SERVFAIL):
1079                     case MAKE_DNSMSG_ERROR(RCODE_REFUSED):
1080                     case UNABLE_TO_COMPLETE_FULL_READ:
1081                     {
1082                         ++consecutive_errors;
1083 
1084                         if(consecutive_errors < CONSECUTIVE_ERRORS_BEFORE_RESTART)
1085                         {
1086                             step_time = timeus_with_offset();
1087                             if(step_time - last_warning_us >= ONE_SECOND_US * 60)
1088                             {
1089                                 log_warn("play: %{dnsname}: step play failure: %r: trying again (this message will only be printed every minute)", domain, ret);
1090                                 last_warning_us = step_time;
1091                             }
1092 
1093                             sleep(1);
1094                             continue;
1095                         }
1096                         else
1097                         {
1098                             log_warn("play: %{dnsname}: step play failure: %r: restarting", domain, ret);
1099                             ret = KEYROLL_MUST_REINITIALIZE;
1100                             break;
1101                         }
1102                     }
1103                     default:
1104                     {
1105                         // unrecoverable error
1106                         log_err("play: %{dnsname}: step play failure: %r (%x): shutting down.  Please restart after fixing the issue.", domain, ret, ret);
1107                         break;
1108                     }
1109                 }
1110                 break;
1111             }
1112 
1113             if(!does_loop)
1114             {
1115                 break;
1116             }
1117 
1118             next_step = keyroll_get_next_step_from(&keyroll, next_step->epochus + 1);
1119         }
1120     }
1121     else
1122     {
1123         log_info("play: %{dnsname}: there is no next step recorded after %llU", domain, step_time);
1124     }
1125 
1126     keyroll_finalize(&keyroll);
1127 
1128     return ret;
1129 }
1130 
1131 struct program_mode_play_thread_args
1132 {
1133     const u8 *fqdn;
1134     bool does_loop;
1135 };
1136 
1137 typedef struct program_mode_play_thread_args program_mode_play_thread_args;
1138 
1139 static void*
program_mode_play_thread(void * args_)1140 program_mode_play_thread(void *args_)
1141 {
1142     program_mode_play_thread_args *args = (program_mode_play_thread_args*)args_;
1143     while(!dnscore_shuttingdown())
1144     {
1145         ya_result  ret = program_mode_play(args->fqdn, args->does_loop);
1146 
1147         if(ISOK(ret))
1148         {
1149             log_info("%{dnsname}: key roll stopped", args->fqdn);
1150             break;
1151         }
1152         else
1153         {
1154             if(ret == KEYROLL_MUST_REINITIALIZE)
1155             {
1156                 log_warn("%{dnsname}: trying again from the start", args->fqdn);
1157             }
1158             else if(ret == STOPPED_BY_APPLICATION_SHUTDOWN)
1159             {
1160                 log_warn("%{dnsname}: keyroll is shutting down", args->fqdn);
1161                 break;
1162             }
1163             else
1164             {
1165                 log_err("%{dnsname}: shutting down (%r)", args->fqdn, ret);
1166                 logger_flush();
1167                 dnscore_shutdown();
1168                 break;
1169             }
1170         }
1171     }
1172     return NULL;
1173 }
1174 
1175 static ya_result
program_mode_play_all(bool does_loop,bool daemonise)1176 program_mode_play_all(bool does_loop, bool daemonise)
1177 {
1178     ya_result ret;
1179 
1180     pid_t pid;
1181     char pid_file_path_buffer[PATH_MAX];
1182     char *pid_file_path = &pid_file_path_buffer[0];
1183     snformat(pid_file_path_buffer, sizeof(pid_file_path_buffer), "%s/%s", g_config.pid_path, g_config.pid_file);
1184 
1185     if(FAIL(ret = pid_check_running_program(pid_file_path, &pid)))
1186     {
1187         log_err("already running with pid: %lu (%s)", pid, pid_file_path);
1188         return ret;
1189     }
1190 
1191     if(FAIL(ret = directory_writable(g_config.keys_path)))
1192     {
1193         return ret;
1194     }
1195 
1196     if(ISOK(ret = server_setup_env(&pid, &pid_file_path, g_config.uid, g_config.gid, SETUP_CREATE_PID_FILE|SETUP_ID_CHANGE|SETUP_CORE_LIMITS)))
1197     {
1198         if(daemonise)
1199         {
1200             if(!does_loop)
1201             {
1202                 log_warn("daemonise requires to enable loops");
1203                 does_loop = true;
1204             }
1205 
1206             signal_handler_finalize();
1207 
1208             server_setup_daemon_go();
1209 
1210             u32 setup_flags = SETUP_CORE_LIMITS | SETUP_ID_CHANGE | SETUP_CREATE_PID_FILE;
1211 
1212             if(FAIL(ret = server_setup_env(NULL, &pid_file_path, g_config.uid, g_config.gid, setup_flags)))
1213             {
1214                 log_err("server setup failed: %r", ret);
1215                 return EXIT_FAILURE;
1216             }
1217 
1218             if(FAIL(ret = signal_handler_init()))
1219             {
1220                 log_err("failed to setup the signal handler: %r", ret);
1221 
1222                 osformatln(termerr, "error: failed to setup the signal handler: %r", ret);
1223                 flusherr();
1224 
1225                 logger_flush();
1226 
1227                 return ret;
1228             }
1229         }
1230     }
1231     else
1232     {
1233         log_err("server setup failed: %r", ret);
1234         return ret;
1235     }
1236 
1237     struct thread_pool_s *tp = thread_pool_init(ptr_vector_size(&g_config.fqdns), ptr_vector_size(&g_config.fqdns) * 2);
1238 
1239     if(tp != NULL)
1240     {
1241         program_mode_play_thread_args *args;
1242         MALLOC_OBJECT_ARRAY_OR_DIE(args, program_mode_play_thread_args, ptr_vector_size(&g_config.fqdns), GENERIC_TAG);
1243 
1244         thread_pool_task_counter counter;
1245         thread_pool_counter_init(&counter, 0);
1246 
1247         for(int i = 0; i <= ptr_vector_last_index(&g_config.fqdns); ++i)
1248         {
1249             char *domain = (char*)ptr_vector_get(&g_config.domains, i);
1250             u8 *fqdn = (u8*)ptr_vector_get(&g_config.fqdns, i);
1251             log_info("zone play: %{dnsname}", fqdn);
1252 
1253             args[i].fqdn = fqdn; // VS false positive (nonsense)
1254             args[i].does_loop = does_loop;
1255 
1256             thread_pool_enqueue_call(tp, program_mode_play_thread, &args[i], &counter, domain);
1257         }
1258 
1259         // ensure the counter was incremented
1260 
1261         thread_pool_wait_queue_empty(tp);
1262 
1263         // wait for the shutdown or for workers to stop
1264 
1265         for(;;)
1266         {
1267             ret = thread_pool_counter_wait_equal_with_timeout(&counter, 0, ONE_SECOND_US * 30);
1268 
1269             log_debug("keyroll: waiting for the threads to stop (%r)", ret);
1270 
1271             if(dnscore_shuttingdown())
1272             {
1273                 break;
1274             }
1275         }
1276 
1277         s64 wait_stop_begin = timeus();
1278         bool wait_stop_error_message = FALSE;
1279         for(u32 wait_count = 0; thread_pool_counter_get_value(&counter) > 0; ++wait_count)
1280         {
1281             sleep(1);
1282             s64 wait_stop_now = timeus();
1283             s64 wait_stop_duration = wait_stop_now - wait_stop_begin;
1284             if(dnscore_shuttingdown() && (wait_stop_duration > (ONE_SECOND_US * 30)) )
1285             {
1286                 if(!wait_stop_error_message)
1287                 {
1288                     log_err("keyroll workers aren't stopping");
1289                     logger_flush();
1290                     wait_stop_error_message = TRUE;
1291                 }
1292                 if(wait_stop_duration > (ONE_SECOND_US * 60))
1293                 {
1294                     log_err("not waiting anymore");
1295                     logger_flush();
1296                 }
1297             }
1298         }
1299 
1300         thread_pool_destroy(tp);
1301         tp = NULL;
1302 
1303         free(args);
1304     }
1305 
1306     if(does_loop)
1307     {
1308         log_info("keyroll stopped");
1309     }
1310 
1311     unlink(pid_file_path);
1312 
1313     return SUCCESS;
1314 }
1315 
1316 static ya_result
program_mode_print(const u8 * domain)1317 program_mode_print(const u8 *domain)
1318 {
1319     ya_result ret;
1320     if(dirent_get_file_type(g_config.plan_path, ".") == DT_UNKNOWN)
1321     {
1322         log_info("print: %{dnsname}: having trouble with directory '%s'", domain, g_config.plan_path);
1323         return INVALID_PATH;
1324     }
1325 
1326     keyroll_t keyroll;
1327 
1328     // start from an empty state
1329     if(FAIL(ret = keyroll_init(&keyroll, domain, g_config.plan_path, g_config.keys_path, g_config.server, FALSE)))
1330     {
1331         return ret;
1332     }
1333 
1334     // at this point:
1335     // _ we know the present state on the server
1336     // _ we know the plan folder for this domain exists
1337 
1338     if(FAIL(ret = keyroll_plan_load(&keyroll)))
1339     {
1340         if(ret != MAKE_ERRNO_ERROR(ENOENT))
1341         {
1342             log_info("print: %{dnsname}: plan loading failed: %r", domain, ret);
1343             formatln("print: %{dnsname}: plan loading failed: %r", domain, ret);
1344         }
1345         else
1346         {
1347             log_info("print: %{dnsname}: there are no plans on storage (%s)", domain, g_config.plan_path);
1348             formatln("print: %{dnsname}: there are no plans on storage (%s)", domain, g_config.plan_path);
1349         }
1350 
1351         return ret;
1352     }
1353 
1354     ret = keyroll_print(&keyroll, termout);
1355 
1356     return ret;
1357 }
1358 
1359 static ya_result
program_mode_print_json(const u8 * domain)1360 program_mode_print_json(const u8 *domain)
1361 {
1362     ya_result ret;
1363     if(dirent_get_file_type(g_config.plan_path, ".") == DT_UNKNOWN)
1364     {
1365         log_info("print: %{dnsname}: having trouble with directory '%s'", domain, g_config.plan_path);
1366         return INVALID_PATH;
1367     }
1368 
1369     keyroll_t keyroll;
1370 
1371     // start from an empty state
1372     if(FAIL(ret = keyroll_init(&keyroll, domain, g_config.plan_path, g_config.keys_path, g_config.server, FALSE)))
1373     {
1374         return ret;
1375     }
1376 
1377     // at this point:
1378     // _ we know the present state on the server
1379     // _ we know the plan folder for this domain exists
1380 
1381     if(FAIL(ret = keyroll_plan_load(&keyroll)))
1382     {
1383         if(ret != MAKE_ERRNO_ERROR(ENOENT))
1384         {
1385             log_info("print: %{dnsname}: plan loading failed: %r", domain, ret);
1386             formatln("print: %{dnsname}: plan loading failed: %r", domain, ret);
1387         }
1388         else
1389         {
1390             log_info("print: %{dnsname}: there are no plans on storage (%s)", domain, g_config.plan_path);
1391             formatln("print: %{dnsname}: there are no plans on storage (%s)", domain, g_config.plan_path);
1392         }
1393 
1394         return ret;
1395     }
1396 
1397     ret = keyroll_print_json(&keyroll, termout);
1398 
1399     return ret;
1400 }
1401 
1402 static ya_result
program_mode_test()1403 program_mode_test()
1404 {
1405     return SUCCESS;
1406 }
1407 
1408 int
main(int argc,char * argv[])1409 main(int argc, char *argv[])
1410 {
1411     /* initializes the core library */
1412     dnscore_init();
1413     keyroll_errors_register();
1414 
1415     ya_result ret = main_config(argc, argv);
1416 
1417     if(FAIL(ret) || cmdline_help_get() || cmdline_version_get())
1418     {
1419         return EXIT_FAILURE;
1420     }
1421 
1422     if(g_config.dryrun)
1423     {
1424         println("dryrun mode");
1425         log_notice("dryrun mode");
1426     }
1427 
1428     if(ptr_vector_size(&g_config.fqdns) == 0)
1429     {
1430         log_err("No domain has been configured.");
1431         println("No domain has been configured.");
1432         flushout();
1433         return EXIT_FAILURE;
1434     }
1435 
1436     signal_handler_init();
1437     signal_handler_set(SIGINT, signal_int);
1438     signal_handler_set(SIGTERM, signal_int);
1439     signal_handler_set(SIGHUP, signal_hup);
1440 
1441     flushout();
1442     flusherr();
1443     logger_flush();
1444 
1445     switch(g_config.program_mode)
1446     {
1447         case NONE:
1448         {
1449             println("\nno -m option given\n");
1450             help_print(argv[0]);
1451             break;
1452         }
1453         case GENERATE:
1454         {
1455             ret = program_mode_generate_all();
1456             break;
1457         }
1458         case PLAY:
1459         {
1460             ret = program_mode_play_all(FALSE, FALSE);
1461             break;
1462         }
1463         case PLAYLOOP:
1464         {
1465             ret = program_mode_play_all(TRUE, g_config.daemonise);
1466             break;
1467         }
1468         case PRINT:
1469         {
1470             for(int i = 0; i <= ptr_vector_last_index(&g_config.fqdns); ++i)
1471             {
1472                 u8 *fqdn = (u8*)ptr_vector_get(&g_config.fqdns, i);
1473                 program_mode_print(fqdn);
1474             }
1475             break;
1476         }
1477         case PRINT_JSON:
1478         {
1479             formatln("{\"version\": \"" YKEYROLL_VERSION "\", \"plans\": [");
1480             for(int i = 0; i <= ptr_vector_last_index(&g_config.fqdns); ++i)
1481             {
1482                 if(i > 0)
1483                 {
1484                     println(",");
1485                 }
1486                 u8 *fqdn = (u8*)ptr_vector_get(&g_config.fqdns, i);
1487                 program_mode_print_json(fqdn);
1488             }
1489             println("]}");
1490             break;
1491         }
1492 
1493         case TEST:
1494         {
1495             ret = program_mode_test();
1496             break;
1497         }
1498         default:
1499         {
1500             ret = INVALID_STATE_ERROR;
1501             break;
1502         }
1503     }
1504 
1505     if(FAIL(ret))
1506     {
1507         log_err("failed with: %r", ret);
1508         osformatln(termerr, "failed with: %r", ret);
1509     }
1510 
1511     flushout();
1512     flusherr();
1513     fflush(NULL);
1514 
1515     signal_handler_finalize();
1516     dnscore_finalize();
1517 
1518     return ISOK(ret)?EXIT_SUCCESS:EXIT_FAILURE;
1519 }
1520