2 * Copyright (c) 2014 Alexander Golovko <alexandro@onsec.ru>
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 #include <openssl/ssl.h>
29 #include <openssl/err.h>
36 static nscang_client_t
*nscang_client_instances
;
39 set_psk(SSL
*ssl
, const char *hint
, char *identity
,
40 unsigned int max_identity_length
, unsigned char *psk
,
41 unsigned int max_psk_length
)
43 nscang_client_t
*c
= NULL
;
45 HASH_FIND_PTR(nscang_client_instances
, &ssl
, c
);
47 strncpy(identity
, c
->identity
, max_identity_length
);
48 identity
[max_identity_length
- 1] = 0x00;
50 strncpy((char *)psk
, c
->psk
, max_psk_length
);
51 psk
[max_psk_length
- 1] = 0x00;
53 return strlen((char *)psk
);
61 nscang_client_init(nscang_client_t
*c
, char *host
, int port
, char *ciphers
,
62 char *identity
, char *psk
)
64 memset(c
, 0x00, sizeof(nscang_client_t
));
66 c
->ssl_ctx
= SSL_CTX_new(SSLv23_client_method());
67 if (c
->ssl_ctx
== NULL
) {
68 c
->_errno
= NSCANG_ERROR_SSL_CTX_CREATE
;
72 if (ciphers
!= NULL
) {
73 if (SSL_CTX_set_cipher_list(c
->ssl_ctx
, ciphers
) != 1) {
74 c
->_errno
= NSCANG_ERROR_SSL_CIPHERS
;
79 SSL_CTX_set_options(c
->ssl_ctx
, SSL_OP_NO_SSLv2
| SSL_OP_NO_SSLv3
);
81 c
->bio
= BIO_new(BIO_s_connect());
83 c
->_errno
= NSCANG_ERROR_SSL_BIO_CREATE
;
86 BIO_set_conn_hostname(c
->bio
, host
);
87 BIO_set_conn_int_port(c
->bio
, &port
);
88 BIO_set_nbio(c
->bio
, 1);
90 c
->ssl
= SSL_new(c
->ssl_ctx
);
92 nscang_client_free(c
);
93 c
->_errno
= NSCANG_ERROR_SSL_CREATE
;
97 SSL_set_bio(c
->ssl
, c
->bio
, c
->bio
);
98 SSL_set_connect_state(c
->ssl
);
99 SSL_set_psk_client_callback(c
->ssl
, set_psk
);
101 c
->identity
= malloc(strlen(identity
) + 1);
102 c
->psk
= malloc(strlen(psk
) + 1);
104 if ((c
->identity
== NULL
) || (c
->psk
== NULL
)) {
105 c
->_errno
= NSCANG_ERROR_MALLOC
;
109 strcpy(c
->identity
, identity
);
112 HASH_ADD_PTR(nscang_client_instances
, ssl
, c
);
114 c
->state
= NSCANG_STATE_NEW
;
120 nscang_client_free(nscang_client_t
*c
)
122 if (c
->state
!= NSCANG_STATE_NONE
)
123 nscang_client_disconnect(c
);
125 if (c
->ssl
!= NULL
) {
126 HASH_DEL(nscang_client_instances
, c
);
128 if (c
->ssl
!= NULL
) {
133 if (c
->ssl_ctx
!= NULL
) {
134 SSL_CTX_free(c
->ssl_ctx
);
137 if (c
->identity
!= NULL
) {
141 if (c
->psk
!= NULL
) {
146 c
->state
= NSCANG_STATE_NONE
;
150 nscang_client_write(nscang_client_t
*c
, void *buf
, int len
, int timeout
)
160 endtime
= time(NULL
) + timeout
;
163 if ((rc
= SSL_write(c
->ssl
, buf
, len
)) > 0)
166 if (!BIO_should_retry(c
->bio
)) {
167 c
->_errno
= NSCANG_ERROR_SSL
;
171 if (time(NULL
) > endtime
) {
172 c
->_errno
= NSCANG_ERROR_TIMEOUT
;
177 FD_SET(BIO_get_fd(c
->bio
, NULL
), &fdset
);
178 select(1, NULL
, &fdset
, NULL
, &tv
);
183 nscang_client_response(nscang_client_t
*c
, int timeout
)
196 endtime
= time(NULL
) + timeout
;
199 rc
= SSL_read(c
->ssl
, ptr
, 1024 - was_read
);
202 was_read
= was_read
+ rc
;
203 ptr
= buf
+ was_read
;
205 if (buf
[was_read
- 1] == '\n') {
206 if (buf
[was_read
- 2] == '\r')
207 buf
[was_read
- 2] = 0x00;
209 buf
[was_read
- 1] = 0x00;
214 if (was_read
== 1024) {
215 c
->_errno
= NSCANG_ERROR_TOO_LONG_RESPONSE
;
218 } else if (!BIO_should_retry(c
->bio
)) {
219 c
->_errno
= NSCANG_ERROR_SSL
;
223 if (time(NULL
) > endtime
) {
224 c
->_errno
= NSCANG_ERROR_TIMEOUT
;
229 FD_SET(BIO_get_fd(c
->bio
, NULL
), &fdset
);
230 select(1, &fdset
, NULL
, NULL
, &tv
);
233 if (strncmp(buf
, "MOIN", 4) == 0) {
234 if (strcmp(buf
, "MOIN 1") == 0) {
235 return NSCANG_RESP_MOIN
;
237 c
->_errno
= NSCANG_ERROR_BAD_PROTO_VERSION
;
238 strncpy(c
->errstr
, buf
+ 5, sizeof(c
->errstr
) - 1);
239 c
->errstr
[sizeof(c
->errstr
) - 1] = 0x00;
242 } else if (strncmp(buf
, "OKAY", 4) == 0) {
243 return NSCANG_RESP_OKAY
;
244 } else if (strncmp(buf
, "FAIL", 4) == 0) {
245 c
->_errno
= NSCANG_ERROR_FAIL
;
246 strncpy(c
->errstr
, buf
+ 5, sizeof(c
->errstr
) - 1);
247 c
->errstr
[sizeof(c
->errstr
) - 1] = 0x00;
249 } else if (strncmp(buf
, "BAIL", 4) == 0) {
250 nscang_client_disconnect(c
);
251 c
->_errno
= NSCANG_ERROR_BAIL
;
252 strncpy(c
->errstr
, buf
+ 5, sizeof(c
->errstr
) - 1);
253 c
->errstr
[sizeof(c
->errstr
) - 1] = 0x00;
256 char *msg
= "BAIL Unknown response!";
258 nscang_client_write(c
, msg
, sizeof(msg
), 0);
259 nscang_client_disconnect(c
);
260 c
->_errno
= NSCANG_ERROR_UNKNOWN_RESPONSE
;
261 strncpy(c
->errstr
, buf
, sizeof(c
->errstr
) - 1);
262 c
->errstr
[sizeof(c
->errstr
) - 1] = 0x00;
268 nscang_client_disconnect(nscang_client_t
*c
)
276 if (c
->state
== NSCANG_STATE_MOIN
)
277 nscang_client_send_quit(c
);
279 if ((SSL_shutdown(c
->ssl
) == -1) && BIO_should_retry(c
->bio
)) {
281 FD_SET(BIO_get_fd(c
->bio
, NULL
), &fdset
);
282 select(1, NULL
, &fdset
, NULL
, &tv
);
283 SSL_shutdown(c
->ssl
);
289 c
->state
= NSCANG_STATE_NEW
;
293 nscang_client_send_moin(nscang_client_t
*c
, int timeout
)
298 if (c
->state
== NSCANG_STATE_MOIN
)
301 if (c
->state
!= NSCANG_STATE_NEW
) {
302 c
->_errno
= NSCANG_ERROR_BAD_STATE
;
307 len
= snprintf(cmd
, sizeof(cmd
), "MOIN 1 %08ld%08ld\r\n", random(),
310 if (!nscang_client_write(c
, cmd
, len
, timeout
))
313 rc
= nscang_client_response(c
, timeout
);
318 if (rc
!= NSCANG_RESP_MOIN
) {
319 c
->_errno
= NSCANG_ERROR_PROTOCOL_MISMATCH
;
323 c
->state
= NSCANG_STATE_MOIN
;
327 /* Check whether a string's prefix matches /\[\d{1,9}\]/
328 * Returns 1 for match, 0 for no match, -1 for incomplete/bad match
331 has_timestamp_prefix(const char *s
)
335 if(*s
++ != '[') return 0;
336 if((*s
< '0') || (*s
> '9')) return -1; // counter strtol's skip-whitespace and sign permissiveness
337 (void)strtol(s
, &end
, 10); // discard result
339 if(*end
== ']') return 1;
344 nscang_client_send_command(nscang_client_t
*c
, const char *command
, int timeout
)
346 const int command_len
= strlen(command
);
347 char command_buf
[1024];
351 switch( has_timestamp_prefix(command
) ) {
353 // There was no timestamp. Add one.
354 snprintf(command_buf
, sizeof(command_buf
)-1, "[%u] %s", (unsigned int)time(NULL
), command
);
357 // Timestamp found, just copy everything
358 strncpy(command_buf
, command
, sizeof(command_buf
)-1);
359 command_buf
[sizeof(command_buf
)-1] = '\0';
362 // Malformed timestamp
363 c
->_errno
= NSCANG_ERROR_FAIL
;
364 snprintf(c
->errstr
, sizeof(c
->errstr
), "invalid time stamp format in command `%s'", command
);
368 /* make sure command_buf is newline-terminated */
369 len
= strlen(command_buf
);
370 if(command_buf
[len
-1] != '\n') {
371 command_buf
[len
] = '\n';
373 command_buf
[len
] = '\0';
376 if (!nscang_client_send_moin(c
, timeout
))
379 snprintf(cmd
, sizeof(cmd
), "PUSH %d\r\n", len
);
380 if (!nscang_client_write(c
, cmd
, strlen(cmd
), timeout
))
383 rc
= nscang_client_response(c
, timeout
);
388 if (rc
!= NSCANG_RESP_OKAY
) {
389 c
->_errno
= NSCANG_ERROR_PROTOCOL_MISMATCH
;
393 if (!nscang_client_write(c
, command_buf
, len
, timeout
))
396 rc
= nscang_client_response(c
, timeout
);
401 if (rc
!= NSCANG_RESP_OKAY
) {
402 c
->_errno
= NSCANG_ERROR_PROTOCOL_MISMATCH
;
410 nscang_client_send_push(nscang_client_t
*c
, char *host
, char *service
,
411 int status
, char *message
, int timeout
)
416 snprintf(command
, sizeof(command
) - 1,
417 "PROCESS_HOST_CHECK_RESULT;%s;%d;%s",
418 host
, status
, message
);
420 snprintf(command
, sizeof(command
) - 1,
421 "PROCESS_SERVICE_CHECK_RESULT;%s;%s;%d;%s",
422 host
, service
, status
, message
);
424 return nscang_client_send_command(c
, command
, timeout
);
428 nscang_client_send_quit(nscang_client_t
*c
)
430 if (c
->state
!= NSCANG_STATE_MOIN
) {
431 c
->_errno
= NSCANG_ERROR_BAD_STATE
;
435 if (!nscang_client_write(c
, "QUIT\n", 5, 0))
442 nscang_client_ssl_error(char *buf
, int buf_size
, const char *msg
) {
443 strncpy(buf
, msg
, buf_size
);
445 strncat(buf
, ":", buf_size
);
446 strncat(buf
, ERR_reason_error_string(ERR_peek_error()), buf_size
);
450 nscang_client_errstr(nscang_client_t
*c
, char *buf
, int buf_size
)
453 case NSCANG_ERROR_SSL_CTX_CREATE
:
454 nscang_client_ssl_error(buf
, buf_size
, "Can't create SSL context");
456 case NSCANG_ERROR_SSL_CIPHERS
:
457 nscang_client_ssl_error(buf
, buf_size
, "Bad ciphers list");
459 case NSCANG_ERROR_SSL_BIO_CREATE
:
460 nscang_client_ssl_error(buf
, buf_size
, "Can't create BIO socket");
462 case NSCANG_ERROR_SSL_CREATE
:
463 nscang_client_ssl_error(buf
, buf_size
, "Can't create SSL");
465 case NSCANG_ERROR_SSL
:
466 nscang_client_ssl_error(buf
, buf_size
, "SSL error");
468 case NSCANG_ERROR_MALLOC
:
469 strncpy(buf
, "Can't allocate memory", buf_size
);
471 case NSCANG_ERROR_TIMEOUT
:
472 strncpy(buf
, "Timeout was reached", buf_size
);
474 case NSCANG_ERROR_TOO_LONG_RESPONSE
:
475 strncpy(buf
, "Protocol mismatch - too long response", buf_size
);
477 case NSCANG_ERROR_BAD_PROTO_VERSION
:
478 snprintf(buf
, buf_size
, "Protocol mismatch - bad version '%s'",
481 case NSCANG_ERROR_PROTOCOL_MISMATCH
:
482 strncpy(buf
, "Protocol mismatch - unexpected server response",
485 case NSCANG_ERROR_UNKNOWN_RESPONSE
:
486 strncpy(buf
, "Protocol mismatch - unknown server response",
489 case NSCANG_ERROR_BAIL
:
490 snprintf(buf
, buf_size
, "BAIL: %s", c
->errstr
);
492 case NSCANG_ERROR_FAIL
:
493 snprintf(buf
, buf_size
, "FAIL: %s", c
->errstr
);
495 case NSCANG_ERROR_BAD_STATE
:
496 snprintf(buf
, buf_size
, "Operation not permitted in state %d",
502 buf
[buf_size
- 1] = 0x00;