1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include <getopt.h>
18 #include <libgen.h>
19 #include <stdio.h>
20 
21 #include "contrib/time.h"
22 #include "contrib/tolower.h"
23 #include "libknot/libknot.h"
24 #include "knot/common/log.h"
25 #include "knot/zone/semantic-check.h"
26 #include "utils/common/params.h"
27 #include "utils/kzonecheck/zone_check.h"
28 
29 #define PROGRAM_NAME "kzonecheck"
30 
31 #define STDIN_SUBST "-"
32 #define STDIN_REPL "/dev/stdin"
33 
print_help(void)34 static void print_help(void)
35 {
36 	printf("Usage: %s [parameters] <filename>\n"
37 	       "\n"
38 	       "Parameters:\n"
39 	       " -o, --origin <zone_origin>  Zone name.\n"
40 	       "                              (default filename without .zone)\n"
41 	       " -d, --dnssec <on|off>       Also check DNSSEC-related records.\n"
42 	       " -t, --time <timestamp>      Current time specification.\n"
43 	       "                              (default current UNIX time)\n"
44 	       " -v, --verbose               Enable debug output.\n"
45 	       " -h, --help                  Print the program help.\n"
46 	       " -V, --version               Print the program version.\n"
47 	       "\n",
48 	       PROGRAM_NAME);
49 }
50 
str2bool(const char * s)51 static bool str2bool(const char *s)
52 {
53 	switch (knot_tolower(s[0])) {
54 	case '1':
55 	case 'y':
56 	case 't':
57 		return true;
58 	case 'o':
59 		return knot_tolower(s[1]) == 'n';
60 	default:
61 		return false;
62 	}
63 }
64 
main(int argc,char * argv[])65 int main(int argc, char *argv[])
66 {
67 	const char *origin = NULL;
68 	bool verbose = false;
69 	semcheck_optional_t optional = SEMCHECK_AUTO_DNSSEC; // default value for --dnssec
70 	knot_time_t check_time = (knot_time_t)time(NULL);
71 
72 	/* Long options. */
73 	struct option opts[] = {
74 		{ "origin",  required_argument, NULL, 'o' },
75 		{ "time",    required_argument, NULL, 't' },
76 		{ "dnssec",  required_argument, NULL, 'd' },
77 		{ "verbose", no_argument,       NULL, 'v' },
78 		{ "help",    no_argument,       NULL, 'h' },
79 		{ "version", no_argument,       NULL, 'V' },
80 		{ NULL }
81 	};
82 
83 	/* Set the time zone. */
84 	tzset();
85 
86 	/* Parse command line arguments */
87 	int opt = 0;
88 	while ((opt = getopt_long(argc, argv, "o:t:d:vVh", opts, NULL)) != -1) {
89 		switch (opt) {
90 		case 'o':
91 			origin = optarg;
92 			break;
93 		case 'v':
94 			verbose = true;
95 			break;
96 		case 'h':
97 			print_help();
98 			return EXIT_SUCCESS;
99 		case 'V':
100 			print_version(PROGRAM_NAME);
101 			return EXIT_SUCCESS;
102 		case 'd':
103 			optional = str2bool(optarg) ? SEMCHECK_DNSSEC : SEMCHECK_NO_DNSSEC;
104 			break;
105 		case 't':
106 			if (knot_time_parse("YMDhms|#|+-#U|+-#",
107 			                    optarg, &check_time) != KNOT_EOK) {
108 				fprintf(stderr, "Unknown time format\n");
109 				return EXIT_FAILURE;
110 			}
111 			break;
112 		default:
113 			print_help();
114 			return EXIT_FAILURE;
115 		}
116 	}
117 
118 	/* Check if there's at least one remaining non-option. */
119 	if (optind >= argc) {
120 		fprintf(stderr, "Expected zone file name\n");
121 		print_help();
122 		return EXIT_FAILURE;
123 	}
124 
125 	char *filename = argv[optind];
126 	if (strncmp(filename, STDIN_SUBST, sizeof(STDIN_SUBST)) == 0) {
127 		filename = STDIN_REPL;
128 	}
129 
130 	char *zonename;
131 	if (origin == NULL) {
132 		/* Get zone name from file name. */
133 		const char *ext = ".zone";
134 		zonename = basename(filename);
135 		if (strcmp(zonename + strlen(zonename) - strlen(ext), ext) == 0) {
136 			zonename = strndup(zonename, strlen(zonename) - strlen(ext));
137 		} else {
138 			zonename = strdup(zonename);
139 		}
140 	} else {
141 		zonename = strdup(origin);
142 	}
143 
144 	log_init();
145 	log_levels_set(LOG_TARGET_STDOUT, LOG_SOURCE_ANY, 0);
146 	log_levels_set(LOG_TARGET_STDERR, LOG_SOURCE_ANY, 0);
147 	log_levels_set(LOG_TARGET_SYSLOG, LOG_SOURCE_ANY, 0);
148 	log_flag_set(LOG_FLAG_NOTIMESTAMP | LOG_FLAG_NOINFO);
149 	if (verbose) {
150 		log_levels_add(LOG_TARGET_STDOUT, LOG_SOURCE_ANY, LOG_UPTO(LOG_DEBUG));
151 	}
152 
153 	knot_dname_t *dname = knot_dname_from_str_alloc(zonename);
154 	knot_dname_to_lower(dname);
155 	free(zonename);
156 	int ret = zone_check(filename, dname, stdout, optional, (time_t)check_time);
157 	knot_dname_free(dname, NULL);
158 
159 	log_close();
160 
161 	switch (ret) {
162 	case KNOT_EOK:
163 		if (verbose) {
164 			fprintf(stdout, "No semantic error found\n");
165 		}
166 		return EXIT_SUCCESS;
167 	case KNOT_EZONEINVAL:
168 		fprintf(stdout, "Serious semantic error detected\n");
169 		// FALLTHROUGH
170 	case KNOT_ESEMCHECK:
171 		return EXIT_FAILURE;
172 	case KNOT_EACCES:
173 	case KNOT_EFILE:
174 		fprintf(stderr, "Failed to load the zone file\n");
175 		return EXIT_FAILURE;
176 	default:
177 		fprintf(stderr, "Failed to run semantic checks (%s)\n", knot_strerror(ret));
178 		return EXIT_FAILURE;
179 	}
180 }
181