1 /*
2  * Copyright (c) 2013 Holger Weiss <holger@weiss.in-berlin.de>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  *    this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions and the following disclaimer in the documentation
13  *    and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #if HAVE_CONFIG_H
29 # include <config.h>
30 #endif
31 
32 #include <ctype.h>
33 #if HAVE_INTTYPES_H
34 # include <inttypes.h>
35 #endif
36 #include <limits.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #if HAVE_NANOSLEEP
41 # include <time.h>
42 #endif
43 #include <unistd.h>
44 
45 #include <ev.h>
46 #include <openssl/rand.h>
47 
48 #include "client.h"
49 #include "conf.h"
50 #include "log.h"
51 #include "send_nsca.h"
52 #include "system.h"
53 #include "util.h"
54 #include "wrappers.h"
55 
56 typedef struct {
57 	char *conf_file;
58 	char *port;
59 	char *server;
60 	int delay;
61 	int log_level;
62 	int log_target;
63 	int timeout;
64 	char delimiter;
65 	char separator;
66 	bool raw_commands;
67 } options;
68 
69 conf *cfg = NULL;
70 int exit_code = EXIT_SUCCESS;
71 
72 static options *get_options(int, char **);
73 static void free_options(options *);
74 static int parse_backslash_escape(const char *);
75 static void delay_execution(unsigned int);
76 static unsigned long random_number(unsigned long);
77 static void forget_config(void);
78 static void usage(int) __attribute__((__noreturn__));
79 
80 int
main(int argc,char ** argv)81 main(int argc, char **argv)
82 {
83 	options *opt;
84 	char *host_port;
85 
86 	setprogname(argv[0]);
87 
88 	log_set(LOG_LEVEL_WARNING, LOG_TARGET_STDERR);
89 
90 	if (atexit(log_close) != 0 || atexit(forget_config))
91 		die("Cannot register function to be called on exit");
92 
93 	if (!ev_default_loop(0))
94 		die("Cannot initialize libev");
95 
96 	opt = get_options(argc, argv);
97 	cfg = conf_init(opt->conf_file != NULL ?
98 	    opt->conf_file : DEFAULT_CONF_FILE);
99 
100 	if (opt->port != NULL)
101 		conf_setstr(cfg, "port", opt->port);
102 	if (opt->server != NULL)
103 		conf_setstr(cfg, "server", opt->server);
104 	if (opt->delay != -1)
105 		conf_setint(cfg, "delay", opt->delay);
106 	if (opt->timeout != -1)
107 		conf_setint(cfg, "timeout", opt->timeout);
108 
109 	log_set(opt->log_level, opt->log_target);
110 
111 	debug("%s starting up", nsca_version());
112 
113 	if (conf_getint(cfg, "delay") != 0)
114 		delay_execution((unsigned int)conf_getint(cfg, "delay"));
115 
116 	xasprintf(&host_port, "%s:%s", conf_getstr(cfg, "server"),
117 	    conf_getstr(cfg, "port"));
118 
119 	(void)client_start(host_port,
120 	    conf_getstr(cfg, "tls_ciphers"),
121 	    conf_getint(cfg, "timeout"),
122 	    opt->raw_commands ? CLIENT_MODE_COMMAND : CLIENT_MODE_CHECK_RESULT,
123 	    opt->delimiter,
124 	    opt->separator);
125 
126 	(void)ev_run(EV_DEFAULT_UC_ 0);
127 
128 	free(host_port);
129 	free_options(opt);
130 	return exit_code;
131 }
132 
133 static options *
get_options(int argc,char ** argv)134 get_options(int argc, char **argv)
135 {
136 	extern int optind;
137 	extern char *optarg;
138 	options *opt = xmalloc(sizeof(options));
139 	int option;
140 
141 	opt->conf_file = NULL;
142 	opt->port = NULL;
143 	opt->server = NULL;
144 	opt->delay = -1;
145 	opt->log_level = -1;
146 	opt->log_target = -1;
147 	opt->timeout = -1;
148 	opt->delimiter = '\t';
149 	opt->separator = '\27';
150 	opt->raw_commands = false;
151 
152 	if (argc == 2) {
153 		if (strcmp(argv[1], "--help") == 0)
154 			usage(EXIT_SUCCESS);
155 		if (strcmp(argv[1], "--version") == 0) {
156 			(void)puts(nsca_version());
157 			exit(EXIT_SUCCESS);
158 		}
159 	}
160 
161 	while ((option = getopt(argc, argv, "Cc:D:d:e:H:ho:p:SstVv")) != -1) {
162 		int character;
163 
164 		switch (option) {
165 		case 'C':
166 			opt->raw_commands = true;
167 			break;
168 		case 'c':
169 			if (opt->conf_file != NULL)
170 				free(opt->conf_file);
171 			opt->conf_file = xstrdup(optarg);
172 			break;
173 		case 'D':
174 			opt->delay = atoi(optarg);
175 			if (opt->delay < 0)
176 				die("-D argument must be a positive integer");
177 			break;
178 		case 'd':
179 			if ((character = parse_backslash_escape(optarg)) == EOF)
180 				die("-d argument must be a single character");
181 			if (character == '\27'
182 			    || character == '\n'
183 			    || character == '\0'
184 			    || character == '\\')
185 				die("Illegal delimiter specified with -d");
186 			opt->delimiter = (char)character;
187 			break;
188 		case 'e':
189 			if ((character = parse_backslash_escape(optarg)) == EOF)
190 				die("-e argument must be a single character");
191 			opt->separator = (char)character;
192 			break;
193 		case 'H':
194 			if (opt->server != NULL)
195 				free(opt->server);
196 			opt->server = xstrdup(optarg);
197 			break;
198 		case 'h':
199 			usage(EXIT_SUCCESS);
200 		case 'o':
201 			opt->timeout = atoi(optarg);
202 			if (opt->timeout < 0)
203 				die("-o argument must be a positive integer");
204 			break;
205 		case 'p':
206 			if (opt->port != NULL)
207 				free(opt->port);
208 			opt->port = xstrdup(optarg);
209 			break;
210 		case 'S':
211 			opt->log_target = opt->log_target != -1
212 			    ? LOG_TARGET_STDERR | opt->log_target
213 			    : LOG_TARGET_STDERR;
214 			break;
215 		case 's':
216 			opt->log_target = opt->log_target != -1
217 			    ? LOG_TARGET_SYSLOG | opt->log_target
218 			    : LOG_TARGET_SYSLOG;
219 			break;
220 		case 't':
221 			notice("Ignoring -t option for backward compatibility");
222 			break;
223 		case 'V':
224 			(void)puts(nsca_version());
225 			exit(EXIT_SUCCESS);
226 		case 'v':
227 			if (opt->log_level == -1)
228 				opt->log_level = LOG_LEVEL_NOTICE;
229 			else if (opt->log_level < LOG_LEVEL_DEBUG)
230 				opt->log_level++;
231 			break;
232 		default:
233 			usage(EXIT_FAILURE);
234 		}
235 	}
236 	if (opt->delimiter == opt->separator)
237 		die("Field delimiter must be different from record separator");
238 	if (argc - optind > 0)
239 		die("Unexpected non-option argument: %s", argv[optind]);
240 
241 	return opt;
242 }
243 
244 static void
free_options(options * opt)245 free_options(options *opt)
246 {
247 	if (opt->conf_file != NULL)
248 		free(opt->conf_file);
249 	if (opt->port != NULL)
250 		free(opt->port);
251 	if (opt->server != NULL)
252 		free(opt->server);
253 
254 	free(opt);
255 }
256 
257 static int
parse_backslash_escape(const char * sequence)258 parse_backslash_escape(const char *sequence)
259 {
260 	char numeric[6]; /* Space for "0x345". */
261 
262 	switch (strlen(sequence)) {
263 	case 1:
264 		return (unsigned char)sequence[0];
265 	case 2:
266 		if (sequence[0] == '\\')
267 			switch (sequence[1]) {
268 			case 'a':
269 				return '\a';
270 			case 'b':
271 				return '\b';
272 			case 'f':
273 				return '\f';
274 			case 'n':
275 				return '\n';
276 			case 'r':
277 				return '\r';
278 			case 't':
279 				return '\t';
280 			case 'v':
281 				return '\v';
282 			case 'x': /* Fall through. */
283 			case '0':
284 				break;
285 			}
286 		/* Otherwise, fall through. */
287 	case 3: /* Fall through. */
288 	case 4: /* Fall through. */
289 	case 5:
290 		/*
291 		 * We support octal numbers with a leading zero and hexadecimal
292 		 * numbers prefixed with "0x" in addition to numeric backslash
293 		 * escape sequences.
294 		 */
295 		if (sequence[0] == '0' || sequence[0] == '\\') {
296 			char *end;
297 			long value;
298 
299 			(void)strcpy(numeric, sequence);
300 			if (numeric[0] == '\\') /* \x42 -> 0x42 */
301 				numeric[0] = '0';
302 			value = strtol(numeric, &end, 0);
303 			if (*end == '\0' && value >= 0 && value <= CHAR_MAX)
304 				return (unsigned char)value;
305 			/* Otherwise, fall through. */
306 		}
307 		/* Otherwise, fall through. */
308 	default:
309 		return EOF;
310 	}
311 }
312 
313 static void
delay_execution(unsigned int max_delay)314 delay_execution(unsigned int max_delay)
315 {
316 #if HAVE_NANOSLEEP
317 	struct timespec delay;
318 
319 	delay.tv_sec = (time_t)random_number(max_delay);
320 	delay.tv_nsec = (long)random_number(1000000000U);
321 	debug("Sleeping %ld seconds and %ld nanoseconds",
322 	    (long)delay.tv_sec, delay.tv_nsec);
323 	(void)nanosleep(&delay, NULL);
324 #else
325 	unsigned int delay = random_number(max_delay + 1);
326 
327 	debug("Sleeping %u seconds", delay);
328 	(void)sleep(delay);
329 #endif
330 }
331 
332 static unsigned long
random_number(unsigned long range)333 random_number(unsigned long range)
334 {
335 	unsigned long random_value;
336 
337 #if HAVE_RAND_BYTES
338 	(void)RAND_bytes((unsigned char *)&random_value,
339 	    sizeof(random_value));
340 #else
341 	(void)RAND_pseudo_bytes((unsigned char *)&random_value,
342 	    sizeof(random_value));
343 #endif
344 	/* See http://c-faq.com/lib/randrange.html for some caveats. */
345 	return random_value % range;
346 }
347 
348 static void
forget_config(void)349 forget_config(void)
350 {
351 	if (cfg != NULL) {
352 		char *password = conf_getstr(cfg, "password");
353 
354 		if (password != NULL)
355 			(void)memset(password, 0, strlen(password));
356 		conf_free(cfg);
357 	}
358 }
359 
360 static void
usage(int status)361 usage(int status)
362 {
363 	(void)fprintf(status == EXIT_SUCCESS ? stdout : stderr,
364 	    "Usage: %s [<options>]\n\n"
365 	    "Options:\n"
366 	    " -C               Accept `raw' monitoring commands.\n"
367 	    " -c <file>        Use the specified configuration <file>.\n"
368 	    " -D <delay>       Sleep up to <delay> seconds on startup.\n"
369 	    " -d <delimiter>   Expect <delimiter> to separate input fields.\n"
370 	    " -e <separator>   Expect <separator> to separate check results.\n"
371 	    " -H <server>      Connect and talk to the specified <server>.\n"
372 	    " -h               Print this usage information and exit.\n"
373 	    " -o <timeout>     Use the specified connection <timeout>.\n"
374 	    " -p <port>        Connect to the specified <port> on the server.\n"
375 	    " -S               Write messages to the standard error output.\n"
376 	    " -s               Write messages to syslog.\n"
377 	    " -t               Ignore this option for backward compatibility.\n"
378 	    " -V               Print version information and exit.\n"
379 	    " -v [-v [-v]]     Increase the verbosity level.\n",
380 	    getprogname());
381 
382 	exit(status);
383 }
384 
385 /* vim:set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */
386