add simple test for command()
[nsca-ng.git] / src / client / client.c
blob43cdbec1bd642e4193ce244c896a337ddb9a420a
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 <stdlib.h>
33 #include <string.h>
34 #if HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
38 #include <ev.h>
39 #include <openssl/rand.h>
41 #include "auth.h"
42 #include "client.h"
43 #include "input.h"
44 #include "log.h"
45 #include "parse.h"
46 #include "send_nsca.h"
47 #include "system.h"
48 #include "tls.h"
49 #include "util.h"
50 #include "wrappers.h"
52 #ifndef NUM_SESSION_ID_BYTES
53 # define NUM_SESSION_ID_BYTES 6
54 #endif
56 struct client_state_s { /* This is typedef'd to `client_state' in client.h. */
57 tls_client_state *tls_client;
58 tls_state *tls;
59 input_state *input;
60 char *command;
61 size_t command_length;
62 int mode;
63 char delimiter;
64 char separator;
67 static void handle_input_chunk(input_state * restrict, char * restrict);
68 static void handle_input_eof(input_state *);
69 static void handle_tls_connect(tls_state *);
70 static void handle_tls_moin_response(tls_state * restrict, char * restrict);
71 static void handle_tls_push_response(tls_state * restrict, char * restrict);
72 static void handle_tls_command_response(tls_state * restrict, char * restrict);
73 static void handle_tls_quit_response(tls_state * restrict, char * restrict);
74 static void send_request(tls_state * restrict, const char * restrict);
75 static void bail(tls_state * restrict, const char * restrict, ...)
76 __attribute__((__format__(__printf__, 2, 3)));
77 static bool server_is_grumpy(tls_state * restrict, char * restrict);
78 static char *generate_session_id(void);
79 static char *base64(const unsigned char *, size_t);
82 * Exported functions.
85 client_state *
86 client_start(const char *server, const char *ciphers, ev_tstamp timeout,
87 int mode, char delimiter, char separator)
89 client_state *client = xmalloc(sizeof(client_state));
91 client->tls_client = tls_client_start(ciphers);
92 client->tls_client->data = client;
93 client->tls = NULL;
94 client->input = NULL;
95 client->mode = mode;
96 client->delimiter = delimiter;
97 client->separator = mode == CLIENT_MODE_COMMAND ? '\n' : separator;
99 tls_connect(client->tls_client, server, timeout, TLS_AUTO_DIE,
100 handle_tls_connect, NULL, set_psk);
102 return client;
105 void
106 client_stop(client_state *client)
108 if (client->input != NULL)
109 input_stop(client->input);
110 if (client->tls != NULL)
111 tls_shutdown(client->tls);
112 if (client->tls_client != NULL)
113 tls_client_stop(client->tls_client);
115 free(client);
119 * Static functions.
122 static void
123 handle_input_chunk(input_state * restrict input, char * restrict chunk)
125 client_state *client = input->data;
126 char *request;
127 char *data = skip_newlines(chunk);
129 if (*data == '\0') { /* Ignore empty input lines. */
130 free(chunk);
131 input_read_chunk(input, handle_input_chunk);
132 return;
135 if (client->mode == CLIENT_MODE_CHECK_RESULT) {
136 chomp(data);
137 client->command = parse_check_result(data, client->delimiter);
138 } else
139 client->command = parse_command(data);
141 free(chunk);
143 client->command_length = strlen(client->command);
144 client->command[client->command_length++] = '\n';
146 xasprintf(&request, "PUSH %u", client->command_length);
147 info("%s C: %s", client->tls->peer, request);
148 tls_write_line(client->tls, request);
149 free(request);
151 tls_read_line(client->tls, handle_tls_push_response);
154 static void
155 handle_input_eof(input_state *input)
157 client_state *client = input->data;
159 client->input = NULL;
160 send_request(client->tls, "QUIT");
161 tls_read_line(client->tls, handle_tls_quit_response);
164 static void
165 handle_tls_connect(tls_state *tls)
167 client_state *client = tls->data;
168 char *session_id = generate_session_id();
169 char *request = concat("MOIN 1 ", session_id);
171 client->tls = tls;
172 tls_set_connection_id(tls, session_id);
173 free(session_id);
174 send_request(tls, request);
175 free(request);
176 tls_read_line(tls, handle_tls_moin_response);
179 static void
180 handle_tls_moin_response(tls_state * restrict tls, char * restrict line)
182 client_state *client = tls->data;
183 int protocol_version;
184 char *args[2];
186 info("%s S: %s", tls->peer, line);
188 if (strncasecmp("MOIN", line, 4) == 0) {
189 if (!parse_line(line, args, 2))
190 bail(tls, "Cannot parse MOIN response");
191 else if ((protocol_version = atoi(args[1])) <= 0)
192 bail(tls, "Expected protocol version");
193 else if (protocol_version != 1)
194 bail(tls, "Protocol version %d not supported",
195 protocol_version);
196 else { /* The handshake succeeded. */
197 debug("Protocol handshake successful");
198 client->input = input_start(client->separator);
199 client->input->data = client;
201 input_on_eof(client->input, handle_input_eof);
202 input_read_chunk(client->input, handle_input_chunk);
204 } else if (!server_is_grumpy(tls, line))
205 bail(tls, "Received unexpected MOIN response");
207 free(line);
210 static void
211 handle_tls_push_response(tls_state * restrict tls, char * restrict line)
213 client_state *client = tls->data;
215 info("%s S: %s", tls->peer, line);
217 if (strcasecmp("OKAY", line) == 0) {
218 notice("Transmitting to %s: %.*s", tls->peer,
219 (int)client->command_length - 1, client->command);
220 tls_write(tls, client->command, client->command_length, free);
221 tls_read_line(tls, handle_tls_command_response);
222 } else if (!server_is_grumpy(tls, line)) {
223 free(client->command);
224 bail(tls, "Received unexpected PUSH response");
227 free(line);
230 static void
231 handle_tls_command_response(tls_state * restrict tls, char * restrict line)
233 client_state *client = tls->data;
235 info("%s S: %s", tls->peer, line);
237 if (strcasecmp("OKAY", line) == 0)
238 input_read_chunk(client->input, handle_input_chunk);
239 else if (!server_is_grumpy(tls, line))
240 bail(tls, "Received unexpected response after sending command(s)");
242 free(line);
245 static void
246 handle_tls_quit_response(tls_state * restrict tls, char * restrict line)
248 client_state *client = tls->data;
250 info("%s S: %s", tls->peer, line);
252 if (strcasecmp("OKAY", line) == 0)
253 client_stop(client);
254 else if (!server_is_grumpy(tls, line))
255 bail(tls, "Received unexpected QUIT response");
257 free(line);
260 static void
261 send_request(tls_state * restrict tls, const char * restrict request)
263 info("%s C: %s", tls->peer, request);
264 tls_write_line(tls, request);
267 static void
268 bail(tls_state * restrict tls, const char * restrict fmt, ...)
270 va_list ap;
271 char *message;
272 client_state *client = tls->data;
274 va_start(ap, fmt);
275 xvasprintf(&message, fmt, ap);
276 va_end(ap);
278 info("%s C: %s %s", tls->peer, "BAIL", message);
280 tls_write(tls, "BAIL ", sizeof("BAIL ") - 1, NULL);
281 tls_write_line(tls, message);
282 client_stop(client);
283 critical("%s", message);
284 free(message);
285 exit_code = EXIT_FAILURE;
288 static bool
289 server_is_grumpy(tls_state * restrict tls, char * restrict line)
291 client_state *client = tls->data;
293 if (strncasecmp("FAIL", line, 4) == 0
294 || strncasecmp("BAIL", line, 4) == 0) {
295 client_stop(client);
296 critical("Server said: %s", line);
297 exit_code = EXIT_FAILURE;
298 return true;
300 return false;
303 static char *
304 generate_session_id(void)
306 unsigned char random_bytes[NUM_SESSION_ID_BYTES];
308 (void)RAND_pseudo_bytes(random_bytes, sizeof(random_bytes));
309 return base64(random_bytes, sizeof(random_bytes));
312 static char *
313 base64(const unsigned char *input, size_t input_size)
315 BIO *bio_b64, *bio_mem;
316 char *mem_data, *output;
317 long output_size;
319 bio_mem = BIO_new(BIO_s_mem());
320 bio_b64 = BIO_new(BIO_f_base64());
321 BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
322 bio_b64 = BIO_push(bio_b64, bio_mem);
323 (void)BIO_write(bio_b64, input, (int)input_size);
324 (void)BIO_flush(bio_b64);
326 output_size = BIO_get_mem_data(bio_b64, &mem_data) + 1;
327 output = xmalloc((size_t)output_size);
328 (void)memcpy(output, mem_data, (size_t)output_size);
329 output[output_size - 1] = '\0';
331 BIO_free_all(bio_b64);
332 return output;
335 /* vim:set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */