Teach the client about backslash escape sequences
[nsca-ng.git] / src / client / send_nsca.c
blobf986ad52ed66769c00691480338bbfd1015a35fd
1 /*
2 * Copyright (c) 2013 Holger Weiss <holger@weiss.in-berlin.de>
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
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.
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.
28 #if HAVE_CONFIG_H
29 # include <config.h>
30 #endif
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>
45 #include <ev.h>
46 #include <openssl/rand.h>
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"
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;
69 conf *cfg = NULL;
70 int exit_code = EXIT_SUCCESS;
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__));
80 int
81 main(int argc, char **argv)
83 options *opt;
84 char *host_port;
86 setprogname(argv[0]);
88 log_set(LOG_LEVEL_WARNING, LOG_TARGET_STDERR);
90 if (atexit(log_close) != 0 || atexit(forget_config))
91 die("Cannot register function to be called on exit");
93 if (!ev_default_loop(0))
94 die("Cannot initialize libev");
96 opt = get_options(argc, argv);
97 cfg = conf_init(opt->conf_file != NULL ?
98 opt->conf_file : DEFAULT_CONF_FILE);
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);
109 log_set(opt->log_level, opt->log_target);
111 debug("%s starting up", nsca_version());
113 if (conf_getint(cfg, "delay") != 0)
114 delay_execution((unsigned int)conf_getint(cfg, "delay"));
116 (void)xasprintf(&host_port, "%s:%s", conf_getstr(cfg, "server"),
117 conf_getstr(cfg, "port"));
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);
126 (void)ev_run(EV_DEFAULT_UC_ 0);
128 free(host_port);
129 free_options(opt);
130 return exit_code;
133 static options *
134 get_options(int argc, char **argv)
136 extern int optind;
137 extern char *optarg;
138 options *opt = xmalloc(sizeof(options));
139 int option;
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;
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);
161 while ((option = getopt(argc, argv, "Cc:D:d:e:H:ho:p:SstVv")) != -1) {
162 int character;
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);
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]);
241 return opt;
244 static void
245 free_options(options *opt)
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);
254 free(opt);
257 static int
258 parse_backslash_escape(const char *sequence)
260 char numeric[6]; /* Space for "0x345". */
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 default:
286 return (unsigned char)sequence[1];
288 /* Otherwise, fall through. */
289 case 3: /* Fall through. */
290 case 4: /* Fall through. */
291 case 5:
293 * We support octal numbers with a leading zero and hexadecimal
294 * numbers prefixed with "0x" in addition to numeric backslash
295 * escape sequences.
297 if (sequence[0] == '0' || sequence[0] == '\\') {
298 char *end;
299 long value;
301 (void)strcpy(numeric, sequence);
302 if (numeric[0] == '\\') /* \x42 -> 0x42 */
303 numeric[0] = '0';
304 value = strtol(numeric, &end, 0);
305 if (*end == '\0' && value >= 0 && value <= CHAR_MAX)
306 return (unsigned char)value;
307 /* Otherwise, fall through. */
309 /* Otherwise, fall through. */
310 default:
311 return EOF;
315 static void
316 delay_execution(unsigned int max_delay)
318 #if HAVE_NANOSLEEP
319 struct timespec delay;
321 delay.tv_sec = (time_t)random_number(max_delay);
322 delay.tv_nsec = (long)random_number(1000000000U);
323 debug("Sleeping %ld seconds and %ld nanoseconds",
324 (long)delay.tv_sec, delay.tv_nsec);
325 (void)nanosleep(&delay, NULL);
326 #else
327 unsigned int delay = random_number(max_delay + 1);
329 debug("Sleeping %u seconds", delay);
330 (void)sleep(delay);
331 #endif
334 static unsigned long
335 random_number(unsigned long range)
337 unsigned long random_value;
339 (void)RAND_pseudo_bytes((unsigned char *)&random_value,
340 sizeof(random_value));
342 /* See http://c-faq.com/lib/randrange.html for some caveats. */
343 return random_value % range;
346 static void
347 forget_config(void)
349 if (cfg != NULL) {
350 char *password = conf_getstr(cfg, "password");
352 if (password != NULL)
353 (void)memset(password, 0, strlen(password));
354 conf_free(cfg);
358 static void
359 usage(int status)
361 (void)fprintf(status == EXIT_SUCCESS ? stdout : stderr,
362 "Usage: %s [<options>]\n\n"
363 "Options:\n"
364 " -C Accept `raw' monitoring commands.\n"
365 " -c <file> Use the specified configuration <file>.\n"
366 " -D <delay> Sleep up to <delay> seconds on startup.\n"
367 " -d <delimiter> Expect <delimiter> to separate input fields.\n"
368 " -e <separator> Expect <separator> to separate check results.\n"
369 " -H <server> Connect and talk to the specified <server>.\n"
370 " -h Print this usage information and exit.\n"
371 " -o <timeout> Use the specified connection <timeout>.\n"
372 " -p <port> Connect to the specified <port> on the server.\n"
373 " -S Write messages to the standard error output.\n"
374 " -s Write messages to syslog.\n"
375 " -t Ignore this option for backward compatibility.\n"
376 " -V Print version information and exit.\n"
377 " -v [-v [-v]] Increase the verbosity level.\n",
378 getprogname());
380 exit(status);
383 /* vim:set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */