2 * Copyright (c) 2013 Holger Weiss <holger@weiss.in-berlin.de>
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.
39 #include <openssl/rand.h>
46 #include "send_nsca.h"
52 #ifndef NUM_SESSION_ID_BYTES
53 # define NUM_SESSION_ID_BYTES 6
56 struct client_state_s
{ /* This is typedef'd to `client_state' in client.h. */
57 tls_client_state
*tls_client
;
61 size_t command_length
;
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);
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
;
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
);
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
);
123 handle_input_chunk(input_state
* restrict input
, char * restrict chunk
)
125 client_state
*client
= input
->data
;
127 char *data
= skip_newlines(chunk
);
129 if (*data
== '\0') { /* Ignore empty input lines. */
131 input_read_chunk(input
, handle_input_chunk
);
135 if (client
->mode
== CLIENT_MODE_CHECK_RESULT
) {
137 client
->command
= parse_check_result(data
, client
->delimiter
);
139 client
->command
= parse_command(data
);
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
);
151 tls_read_line(client
->tls
, handle_tls_push_response
);
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
);
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
);
172 tls_set_connection_id(tls
, session_id
);
174 send_request(tls
, request
);
176 tls_read_line(tls
, handle_tls_moin_response
);
180 handle_tls_moin_response(tls_state
* restrict tls
, char * restrict line
)
182 client_state
*client
= tls
->data
;
183 int protocol_version
;
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",
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");
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");
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)");
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)
254 else if (!server_is_grumpy(tls
, line
))
255 bail(tls
, "Received unexpected QUIT response");
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
);
268 bail(tls_state
* restrict tls
, const char * restrict fmt
, ...)
272 client_state
*client
= tls
->data
;
275 xvasprintf(&message
, fmt
, 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
);
283 critical("%s", message
);
285 exit_code
= EXIT_FAILURE
;
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) {
296 critical("Server said: %s", line
);
297 exit_code
= EXIT_FAILURE
;
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
));
313 base64(const unsigned char *input
, size_t input_size
)
315 BIO
*bio_b64
, *bio_mem
;
316 char *mem_data
, *output
;
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
);
335 /* vim:set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */