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 "system.h"
33 
34 #define PROGRAM_NAME "test_nsca"
35 #define LISTEN_ADDRESS "127.0.0.1"
36 #define LISTEN_PORT "12345" /* Don't interfere with a poduction server. */
37 #define COMMAND_FILE "fifo"
38 #define SERVER_PID_FILE "server.pid"
39 #define CLIENT_CONF_FILE "client.cfg"
40 #define SERVER_CONF_FILE "server.cfg"
41 #define TIMEOUT 10
42 
43 #define DEFAULT_CLIENT_CONF "# Created by " PROGRAM_NAME "\n"   \
44     "password = \"forty-two\"\n"
45 
46 #define DEFAULT_SERVER_CONF "# Created by " PROGRAM_NAME "\n"   \
47     "authorize \"*\" {\n"                                       \
48     "    password = \"forty-two\"\n"                            \
49     "    commands = \".*\"\n"                                   \
50     "}\n"
51 
52 #define CLIENT_COMMAND_LINE "send_nsca "                        \
53     "-c `pwd`/" CLIENT_CONF_FILE " "                            \
54     "-H " LISTEN_ADDRESS " "                                    \
55     "-p " LISTEN_PORT
56 
57 #define SERVER_COMMAND_LINE "nsca-ng "                          \
58     "-c `pwd`/" SERVER_CONF_FILE " "                            \
59     "-C `pwd`/" COMMAND_FILE " "                                \
60     "-P `pwd`/" SERVER_PID_FILE " "                             \
61     "-b " LISTEN_ADDRESS ":" LISTEN_PORT " "                    \
62     "-l 0"
63 
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #include <sys/wait.h>
67 
68 #include <ctype.h>
69 #include <errno.h>
70 #include <fcntl.h>
71 #include <signal.h>
72 #include <stdarg.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <unistd.h>
77 
78 static long expected_num_lines = 1;
79 static volatile char got_signal = 0;
80 
81 static void get_options(int, char **);
82 static char *join(const char *, const char *);
83 static void run_command(const char *);
84 static void write_file(const char *, const char *);
85 static void create_fifo(void);
86 static void cat_fifo(long);
87 static void remove_fifo(void);
88 static void kill_server(void);
89 static void print_usage(FILE *);
90 static void print_version(void);
91 static void handle_signal(int);
92 static void xatexit(void (*)(void));
93 static void xsigaction(int, const struct sigaction * restrict,
94                        struct sigaction * restrict);
95 static void __attribute__((__format__(__printf__, 1, 2), __noreturn__))
96             die(const char *, ...);
97 
98 int
main(int argc,char ** argv)99 main(int argc, char **argv)
100 {
101 	struct sigaction sa;
102 #ifndef __gnu_hurd__
103 	int fd;
104 #endif
105 
106 	get_options(argc, argv);
107 
108 	sa.sa_flags = 0;
109 	sa.sa_handler = handle_signal;
110 	(void)sigemptyset(&sa.sa_mask);
111 	xsigaction(SIGINT, &sa, NULL);
112 	xsigaction(SIGTERM, &sa, NULL);
113 	xsigaction(SIGALRM, &sa, NULL);
114 	(void)alarm(TIMEOUT);
115 
116 	if (access(CLIENT_CONF_FILE, F_OK) == -1)
117 		write_file(CLIENT_CONF_FILE, DEFAULT_CLIENT_CONF);
118 	if (access(SERVER_CONF_FILE, F_OK) == -1)
119 		write_file(SERVER_CONF_FILE, DEFAULT_SERVER_CONF);
120 
121 	create_fifo();
122 	xatexit(remove_fifo);
123 
124 	/*
125 	 * Make sure there's a FIFO reader when the server starts up, in order
126 	 * to avoid having to wait ten seconds until the server notices that we
127 	 * opened the FIFO for reading (in the cat_fifo() function).  GNU Hurd
128 	 * doesn't like this trick, though.
129 	 */
130 #ifndef __gnu_hurd__
131 	if ((fd = open(COMMAND_FILE, O_RDONLY | O_NONBLOCK)) == -1)
132 		die("Cannot open %s: %s", COMMAND_FILE, strerror(errno));
133 #endif
134 	run_command(join(SERVER_COMMAND_LINE, getenv("NSCA_SERVER_FLAGS")));
135 	xatexit(kill_server);
136 
137 	run_command(join(CLIENT_COMMAND_LINE, getenv("NSCA_CLIENT_FLAGS")));
138 	cat_fifo(expected_num_lines);
139 #ifndef __gnu_hurd__
140 	(void)close(fd);
141 #endif
142 	return EXIT_SUCCESS;
143 }
144 
145 static void
get_options(int argc,char ** argv)146 get_options(int argc, char **argv)
147 {
148 	extern int optind;
149 	extern char *optarg;
150 	int option;
151 
152 	if (argc == 2) {
153 		if (strcmp(argv[1], "--help") == 0) {
154 			print_usage(stdout);
155 			exit(EXIT_SUCCESS);
156 		}
157 		if (strcmp(argv[1], "--version") == 0) {
158 			print_version();
159 			exit(EXIT_SUCCESS);
160 		}
161 	}
162 
163 	while ((option = getopt(argc, argv, "hl:V")) != -1)
164 		switch (option) {
165 		case 'h':
166 			print_usage(stdout);
167 			exit(EXIT_SUCCESS);
168 		case 'l':
169 			if ((expected_num_lines = atol(optarg)) < 1)
170 				die("-l must be a number greater than zero");
171 			break;
172 		case 'V':
173 			print_version();
174 			exit(EXIT_SUCCESS);
175 		default:
176 			print_usage(stderr);
177 			exit(EXIT_FAILURE);
178 		}
179 
180 	if (argc - optind > 0)
181 		die("Unexpected non-option argument: %s", argv[optind]);
182 }
183 
184 static char *
join(const char * part1,const char * part2)185 join(const char *part1, const char *part2)
186 {
187 	static char joined[4096];
188 	size_t len1 = strlen(part1);
189 
190 	if (len1 > sizeof(joined))
191 		die("Command line too long");
192 
193 	(void)strcpy(joined, part1);
194 
195 	if (part2 != NULL) {
196 		size_t len2 = strlen(part2);
197 
198 		if (len1 + 1 + len2 + 1 > sizeof(joined))
199 			die("Command line too long");
200 
201 		(void)strcat(joined, " ");
202 		(void)strcat(joined, part2);
203 	}
204 	return joined;
205 }
206 
207 static void
run_command(const char * command)208 run_command(const char *command)
209 {
210 	int status;
211 
212 	if ((status = system(command)) == -1)
213 		die("Cannot execute %s: %s", command, strerror(errno));
214 	if (WIFEXITED(status) && WEXITSTATUS(status) == 127)
215 		exit(77); /* Tell Autotest to skip this test. */
216 	if (status != 0)
217 		exit(1);
218 }
219 
220 static void
write_file(const char * file,const char * contents)221 write_file(const char *file, const char *contents)
222 {
223 	FILE *f;
224 
225 	if ((f = fopen(file, "w")) == NULL)
226 		die("Cannot open %s: %s", file, strerror(errno));
227 	if (fputs(contents, f) == EOF)
228 		die("Cannot write %s: %s", file, strerror(errno));
229 	if (fclose(f) == EOF)
230 		die("Cannot close %s: %s", file, strerror(errno));
231 }
232 
233 static void
create_fifo(void)234 create_fifo(void)
235 {
236 	if (mkfifo(COMMAND_FILE, 0666) == -1)
237 		(void)fprintf(stderr, "%s: Cannot create %s: %s\n",
238 		    PROGRAM_NAME, COMMAND_FILE, strerror(errno));
239 }
240 
241 static void
cat_fifo(long n_lines)242 cat_fifo(long n_lines)
243 {
244 	FILE *fifo;
245 	int c;
246 	enum {
247 		STATE_EAT_TIMESTAMP,
248 		STATE_PRINT_COMMAND
249 	} state = STATE_EAT_TIMESTAMP;
250 
251 	do
252 		fifo = fopen(COMMAND_FILE, "r");
253 	while (fifo == NULL && errno == EINTR); /* Happens on GNU Hurd. */
254 	if (fifo == NULL)
255 		die("Cannot open %s: %s", COMMAND_FILE, strerror(errno));
256 	while ((c = getc(fifo)) != EOF) {
257 		if (state == STATE_EAT_TIMESTAMP) {
258 			if (c == ' ')
259 				state = STATE_PRINT_COMMAND;
260 			else if (c != '[' && !isdigit(c) && c != ']')
261 				die("Got unexpected `%c' from FIFO", c);
262 		} else {
263 			if (putchar(c) == EOF)
264 				die("Cannot write to stdout: %s",
265 				    strerror(errno));
266 			if (c == '\n') {
267 				if (--n_lines > 0)
268 					state = STATE_EAT_TIMESTAMP;
269 				else
270 					break;
271 			}
272 		}
273 	}
274 	if (ferror(fifo))
275 		die("Cannot read %s: %s", COMMAND_FILE,
276 		    got_signal ? "Interrupted" : strerror(errno));
277 }
278 
279 static void
remove_fifo(void)280 remove_fifo(void)
281 {
282 	if (unlink(COMMAND_FILE) == -1)
283 		(void)fprintf(stderr, "%s: Cannot remove %s: %s\n",
284 		    PROGRAM_NAME, COMMAND_FILE, strerror(errno));
285 }
286 
287 static void
kill_server(void)288 kill_server(void)
289 {
290 	FILE *f;
291 	char buf[64];
292 	pid_t pid;
293 
294 	/*
295 	 * To minimize the risk that the server process interferes with any
296 	 * following test(s), we KILL the process instead of using the TERM
297 	 * signal.
298 	 */
299 	if ((f = fopen(SERVER_PID_FILE, "r")) == NULL) {
300 		(void)fprintf(stderr, "%s: Cannot open %s: %s\n", PROGRAM_NAME,
301 		    SERVER_PID_FILE, strerror(errno));
302 		return;
303 	}
304 
305 	if (fgets(buf, sizeof(buf), f) == NULL)
306 		(void)fprintf(stderr, "%s: Cannot read %s: %s\n", PROGRAM_NAME,
307 		    SERVER_PID_FILE, ferror(f) ? strerror(errno) : "EOF");
308 	else if ((pid = (pid_t)atol(buf)) < 1)
309 		(void)fprintf(stderr, "%s: PID file %s contains garbage\n",
310 		    PROGRAM_NAME, SERVER_PID_FILE);
311 	else if (kill(pid, SIGKILL) == -1)
312 		(void)fprintf(stderr, "%s: Cannot kill server PID %lu: %s\n",
313 		    PROGRAM_NAME, (unsigned long)pid, strerror(errno));
314 
315 	if (fclose(f) == EOF)
316 		(void)fprintf(stderr, "%s: Cannot close %s: %s\n", PROGRAM_NAME,
317 		    SERVER_PID_FILE, strerror(errno));
318 }
319 
320 static void
print_usage(FILE * stream)321 print_usage(FILE *stream)
322 {
323 	(void)fprintf(stream,
324 	    "Usage: %s [<options>]\n\n"
325 	    "Options:\n"
326 	    " -h          Print this usage information and exit.\n"
327 	    " -l <lines>  Read this number of lines from FIFO (default: 1).\n"
328 	    " -V          Print version information and exit.\n",
329 	    PROGRAM_NAME);
330 }
331 
332 static void
print_version(void)333 print_version(void)
334 {
335 	(void)system("send_nsca -V");
336 	(void)system("nsca-ng -V");
337 #if HAVE_POSIX_AIO
338 	(void)puts("The NSCA-ng server uses the POSIX AIO API on this system.");
339 #endif
340 }
341 
342 static void
handle_signal(int signal_number)343 handle_signal(int signal_number __attribute__((__unused__)))
344 {
345 	got_signal = 1;
346 }
347 
348 static void
xatexit(void function (void))349 xatexit(void function(void))
350 {
351 	if (atexit(function) != 0)
352 		die("Cannot register exit function");
353 }
354 
355 static void
xsigaction(int signal_number,const struct sigaction * restrict action,struct sigaction * restrict old_action)356 xsigaction(int signal_number, const struct sigaction * restrict action,
357            struct sigaction * restrict old_action)
358 {
359 	if (sigaction(signal_number, action, old_action) == -1)
360 		die("Cannot set handler for signal %d: %s", signal_number,
361 		    strerror(errno));
362 }
363 
364 static void
die(const char * format,...)365 die(const char *format, ...)
366 {
367 	va_list ap;
368 
369 	(void)fputs(PROGRAM_NAME ": ", stderr);
370 
371 	va_start(ap, format);
372 	(void)vfprintf(stderr, format, ap);
373 	va_end(ap);
374 
375 	(void)putc('\n', stderr);
376 
377 	exit(EXIT_FAILURE);
378 }
379 
380 /* vim:set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */
381