1 /*
2  * The dhcpd-pools has BSD 2-clause license which also known as "Simplified
3  * BSD License" or "FreeBSD License".
4  *
5  * Copyright 2006- Sami Kerola. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are
9  * met:
10  *
11  *    1. Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *
14  *    2. Redistributions in binary form must reproduce the above copyright
15  *       notice, this list of conditions and the following disclaimer in the
16  *       documentation and/or other materials provided with the
17  *       distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR AND CONTRIBUTORS OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  *
31  * The views and conclusions contained in the software and documentation are
32  * those of the authors and should not be interpreted as representing
33  * official policies, either expressed or implied, of Sami Kerola.
34  */
35 
36 /*! \file dhcpd-pools.c
37  * \brief The main(), and core initialization.
38  */
39 
40 #include <config.h>
41 
42 #include <errno.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <getopt.h>
46 #include <stdio.h>
47 #include <limits.h>
48 
49 #include "close-stream.h"
50 #include "closeout.h"
51 #include "error.h"
52 #include "progname.h"
53 #include "quote.h"
54 #include "xalloc.h"
55 
56 #include "dhcpd-pools.h"
57 
58 /* Function pointers */
59 int (*parse_ipaddr) (struct conf_t *state, const char *restrict src, union ipaddr_t *restrict dst);
60 void (*copy_ipaddr) (union ipaddr_t *restrict dst, const union ipaddr_t *restrict src);
61 const char *(*ntop_ipaddr) (const union ipaddr_t *ip);
62 double (*get_range_size) (const struct range_t *r);
63 int (*xstrstr) (struct conf_t *state, const char *restrict str);
64 int (*ipcomp) (const union ipaddr_t *restrict a, const union ipaddr_t *restrict b);
65 int (*leasecomp) (const struct leases_t *restrict a, const struct leases_t *restrict b);
66 void (*add_lease) (struct conf_t *state, union ipaddr_t *ip, enum ltype type);
67 struct leases_t *(*find_lease) (struct conf_t *state, union ipaddr_t *ip);
68 
69 /*! \brief An option argument parser to populate state header_limit and
70  * number_limit values.
71  */
return_limit(const char c)72 static int return_limit(const char c)
73 {
74 	if ('0' <= c && c < '8')
75 		return c - '0';
76 	error(EXIT_FAILURE, 0, "return_limit: output mask %s is illegal", quote(optarg));
77 	return 0;
78 }
79 
80 /*! \brief Run time initialization. Global allocations, counter
81  * initializations, etc are here. */
prepare_memory(struct conf_t * state)82 static void prepare_memory(struct conf_t *state)
83 {
84 	state->ranges = xmalloc(sizeof(struct range_t) * state->ranges_size);
85 	/* First shared network entry is all networks */
86 	state->shared_net_root = xcalloc(sizeof(struct shared_network_t), 1);
87 	state->shared_net_root->name = xstrdup("All networks");
88 	state->shared_net_head = state->shared_net_root;
89 }
90 
91 /*! \brief The --skip option argument parser. */
skip_arg_parse(struct conf_t * state,char * arg)92 static void skip_arg_parse(struct conf_t *state, char *arg)
93 {
94 	enum {
95 		OPT_ARG_OK = 0,
96 		OPT_ARG_WARNING,
97 		OPT_ARG_CRITICAL,
98 		OPT_ARG_MINSIZE,
99 		OPT_ARG_SUPPRESSED
100 	};
101 
102 	char *const tokens[] = {
103 		[OPT_ARG_OK] = "ok",
104 		[OPT_ARG_WARNING] = "warning",
105 		[OPT_ARG_CRITICAL] = "critical",
106 		[OPT_ARG_MINSIZE] = "minsize",
107 		[OPT_ARG_SUPPRESSED] = "suppressed",
108 		NULL
109 	};
110 	char *value;
111 
112 	while (*arg != '\0') {
113 		switch (getsubopt(&arg, tokens, &value)) {
114 		case OPT_ARG_OK:
115 			state->skip_ok = 1;
116 			break;
117 		case OPT_ARG_WARNING:
118 			state->skip_warning = 1;
119 			break;
120 		case OPT_ARG_CRITICAL:
121 			state->skip_critical = 1;
122 			break;
123 		case OPT_ARG_MINSIZE:
124 			state->skip_minsize = 1;
125 			break;
126 		case OPT_ARG_SUPPRESSED:
127 			state->skip_suppressed = 1;
128 			break;
129 		default:
130 			error(EXIT_FAILURE, 0, "unknown --skip specifier: %s", value);
131 		}
132 	}
133 }
134 
135 /*! \brief Command line options parser. */
parse_command_line_opts(struct conf_t * state,int argc,char ** argv)136 static void parse_command_line_opts(struct conf_t *state, int argc, char **argv)
137 {
138 	enum {
139 		OPT_SNET_ALARMS = CHAR_MAX + 1,
140 		OPT_WARN,
141 		OPT_CRIT,
142 		OPT_MINSIZE,
143 		OPT_WARN_COUNT,
144 		OPT_CRIT_COUNT,
145 		OPT_COLOR,
146 		OPT_SKIP,
147 		OPT_SET_IPV,
148 		OPT_MUSTACH
149 	};
150 
151 	static struct option const long_options[] = {
152 		{"config", required_argument, NULL, 'c'},
153 		{"leases", required_argument, NULL, 'l'},
154 		{"color", required_argument, NULL, OPT_COLOR},
155 		{"skip", required_argument, NULL, OPT_SKIP},
156 		{"format", required_argument, NULL, 'f'},
157 		{"sort", required_argument, NULL, 's'},
158 		{"reverse", no_argument, NULL, 'r'},
159 		{"output", required_argument, NULL, 'o'},
160 		{"limit", required_argument, NULL, 'L'},
161 		{"mustach", required_argument, NULL, OPT_MUSTACH},
162 		{"version", no_argument, NULL, 'v'},
163 		{"help", no_argument, NULL, 'h'},
164 		{"snet-alarms", no_argument, NULL, OPT_SNET_ALARMS},
165 		{"warning", required_argument, NULL, OPT_WARN},
166 		{"critical", required_argument, NULL, OPT_CRIT},
167 		{"warn-count", required_argument, NULL, OPT_WARN_COUNT},
168 		{"crit-count", required_argument, NULL, OPT_CRIT_COUNT},
169 		{"minsize", required_argument, NULL, OPT_MINSIZE},
170 		{"perfdata", no_argument, NULL, 'p'},
171 		{"all-as-shared", no_argument, NULL, 'A'},
172 		{"ip-version", required_argument, NULL, OPT_SET_IPV},
173 		{NULL, 0, NULL, 0}
174 	};
175 	int alarming = 0;
176 
177 	while (1) {
178 		int c;
179 
180 		c = getopt_long(argc, argv, "c:l:f:o:s:rL:pAvh", long_options, NULL);
181 		if (c == EOF)
182 			break;
183 		switch (c) {
184 		case 'c':
185 			/* config file */
186 			state->dhcpdconf_file = optarg;
187 			break;
188 		case 'l':
189 			/* lease file */
190 			state->dhcpdlease_file = optarg;
191 			break;
192 		case 'f':
193 			/* Output format */
194 			state->output_format = optarg[0];
195 			break;
196 		case 's':
197 			{
198 				/* Output sorting option */
199 				struct output_sort *p = state->sorts;
200 				char *ptr = optarg;
201 
202 				while (p && p->next)
203 					p = p->next;
204 				while (*ptr) {
205 					if (state->sorts == NULL) {
206 						state->sorts =
207 						    xcalloc(1, sizeof(struct output_sort));
208 						p = state->sorts;
209 					} else {
210 						p->next = xcalloc(1, sizeof(struct output_sort));
211 						p = p->next;
212 					}
213 					p->func = field_selector(*ptr++);
214 				}
215 			}
216 			break;
217 		case 'r':
218 			/* What ever sort in reverse order */
219 			state->reverse_order = 1;
220 			break;
221 		case 'o':
222 			/* Output file */
223 			state->output_file = optarg;
224 			break;
225 		case 'L':
226 			/* Specification what will be printed */
227 			state->header_limit = return_limit(optarg[0]);
228 			state->number_limit = return_limit(optarg[1]);
229 			break;
230 		case OPT_MUSTACH:
231 #ifdef BUILD_MUSTACH
232 			state->mustach_template = optarg;
233 			state->output_format = 'm';
234 			state->print_mac_addreses = 1;
235 #else
236 			error(EXIT_FAILURE, 0, "compiled without mustach support");
237 #endif
238 			break;
239 		case OPT_COLOR:
240 			state->color_mode = parse_color_mode(optarg);
241 			if (state->color_mode == color_unknown)
242 				error(EXIT_FAILURE, errno, "unknown color mode: %s", quote(optarg));
243 			break;
244 		case OPT_SKIP:
245 			skip_arg_parse(state, optarg);
246 			break;
247 		case OPT_SNET_ALARMS:
248 			state->snet_alarms = 1;
249 			break;
250 		case OPT_WARN:
251 			alarming = 1;
252 			state->warning = strtod_or_err(optarg, "illegal argument");
253 			break;
254 		case OPT_CRIT:
255 			alarming = 1;
256 			state->critical = strtod_or_err(optarg, "illegal argument");
257 			break;
258 		case OPT_WARN_COUNT:
259 			alarming = 1;
260 			state->warn_count = strtod_or_err(optarg, "illegal argument");
261 			break;
262 		case OPT_CRIT_COUNT:
263 			alarming = 1;
264 			state->crit_count = strtod_or_err(optarg, "illegal argument");
265 			break;
266 		case OPT_MINSIZE:
267 			state->minsize = strtod_or_err(optarg, "illegal argument");
268 			break;
269 		case OPT_SET_IPV:
270 			switch (optarg[0]) {
271 			case '4':
272 				set_ipv_functions(state, IPv4);
273 				break;
274 			case '6':
275 				set_ipv_functions(state, IPv6);
276 				break;
277 			default:
278 				error(EXIT_FAILURE, 0, "unknown --ip-version argument: %s", optarg);
279 			}
280 			break;
281 		case 'p':
282 			/* Print additional performance data in alarming mode */
283 			state->perfdata = 1;
284 			break;
285 		case 'A':
286 			/* Treat single networks as shared with network CIDR as name */
287 			state->all_as_shared = 1;
288 			break;
289 		case 'v':
290 			/* Print version */
291 			print_version();
292 		case 'h':
293 			/* Print help */
294 			usage(EXIT_SUCCESS);
295 		default:
296 			error(EXIT_FAILURE, 0, "Try %s --help for more information.", program_name);
297 		}
298 	}
299 
300 	/* Use default dhcpd.conf when user did not define anything. */
301 	if (state->dhcpdconf_file == NULL)
302 		state->dhcpdconf_file = DHCPDCONF_FILE;
303 	/* Use default dhcpd.leases when user did not define anything. */
304 	if (state->dhcpdlease_file == NULL)
305 		state->dhcpdlease_file = DHCPDLEASE_FILE;
306 	/* Use default limits when user did not define anything. */
307 	if (state->header_limit == 8) {
308 		char const *default_limit = OUTPUT_LIMIT;
309 
310 		state->header_limit = return_limit(default_limit[0]);
311 		state->number_limit = return_limit(default_limit[1]);
312 	}
313 	/* Output format is not defined, if alarm thresholds are then it's alarming, else use the
314 	 * default.  */
315 	if (state->output_format == '\0') {
316 		if (alarming == 1)
317 			state->output_format = 'a';
318 		else {
319 			const char *const default_format = OUTPUT_FORMAT;
320 
321 			state->output_format = default_format[0];
322 		}
323 	}
324 	if (state->output_format == 'X' || state->output_format == 'J') {
325 		state->print_mac_addreses = 1;
326 	}
327 }
328 
329 /*!\brief Start of execution.  This will mostly call other functions one
330  * after another.
331  *
332  * \return Return value indicates success or fail or analysis, unless
333  * either --warning or --critical options are in use, which makes the
334  * return value in some cases to match with Nagios expectations about
335  * alarming. */
main(int argc,char ** argv)336 int main(int argc, char **argv)
337 {
338 	struct conf_t state = {
339 		.warning = ALARM_WARN,
340 		.critical = ALARM_CRIT,
341 		.warn_count = 0x100000000,	/* == 2^32 that is the entire IPv4 space */
342 		.crit_count = 0x100000000,	/* basically turns off the count criteria */
343 		.header_limit = 8,
344 		.ranges_size = 64,
345 		.ip_version = IPvUNKNOWN,
346 		.color_mode = color_auto,
347 		0
348 	};
349 	int ret_val;
350 
351 	atexit(close_stdout);
352 	set_program_name(argv[0]);
353 	prepare_memory(&state);
354 	set_ipv_functions(&state, IPvUNKNOWN);
355 	parse_command_line_opts(&state, argc, argv);
356 
357 	/* Do the job */
358 	parse_config(&state, 1, state.dhcpdconf_file, state.shared_net_root);
359 	parse_leases(&state);
360 	prepare_data(&state);
361 	do_counting(&state);
362 	if (state.sorts != NULL)
363 		mergesort_ranges(&state, state.ranges, state.num_ranges, NULL, 1);
364 	if (state.reverse_order == 1)
365 		flip_ranges(&state);
366 	ret_val = output_analysis(&state);
367 	clean_up(&state);
368 	return (ret_val);
369 }
370