diff --git a/util/clixon_util_grpc.c b/util/clixon_util_grpc.c new file mode 100644 index 00000000..4df16d1e --- /dev/null +++ b/util/clixon_util_grpc.c @@ -0,0 +1,553 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * XML support functions. + * @see https://www.w3.org/TR/2008/REC-xml-20081126 + * https://www.w3.org/TR/2009/REC-xml-names-20091208 + * The function can do yang validation, process xml and json, etc. + * On success, nothing is printed and exitcode 0 + * On failure, an error is printed on stderr and exitcode != 0 + * Failure error prints are different, it would be nice to make them more + * uniform. (see clicon_rpc_generate_error) + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include /* gethostbyname */ +#include /* inet_pton */ +#include /* TCP_NODELAY */ + + +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +#define UTIL_SSL_OPTS "hD:H:" + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +/* User data handle to nghttp2 lib */ +typedef struct { + int sd_s; + SSL *sd_ssl; + nghttp2_session *sd_session; + int32_t sd_stream_id; +} session_data; + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#if 1 /* DEBUG */ +static void +print_header(const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen) +{ + clicon_debug(1, "%s %s", name, value); +} + +/* Print HTTP headers to |f|. Please note that this function does not + take into account that header name and value are sequence of + octets, therefore they may contain non-printable characters. */ +static void +print_headers(nghttp2_nv *nva, + size_t nvlen) +{ + size_t i; + + for (i = 0; i < nvlen; ++i) + print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen); +} +#endif /* DEBUG */ + + +/* Transmit the |data|, |length| bytes, to the network. + * type is: nghttp2_on_header_callback + */ +static ssize_t +send_callback(nghttp2_session *session, + const uint8_t *data, + size_t length, + int flags, + void *user_data) +{ + session_data *sd = (session_data*)user_data; + int ret; + + + clicon_debug(1, "%s %d:", __FUNCTION__, length); +#if 0 + { + int i; + for (i=0; isd_ssl, data, length)) < 0) + return ret; + return ret; +} + +/*! + */ +static int +on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + //session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s %d", __FUNCTION__, frame->hd.stream_id); + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE) + clicon_debug(1, "All headers received %d", frame->hd.stream_id); + + return 0; +} + +/*! + */ +static int +on_data_chunk_recv_callback(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data) +{ + session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s %d", __FUNCTION__, stream_id); + if (sd->sd_session == session && + sd->sd_stream_id == stream_id) + fwrite(data, 1, len, stdout); /* This is where data is printed */ + return 0; +} + +/*! + */ +static int +on_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + nghttp2_error_code error_code, + void *user_data) +{ + //session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +/*! + */ +static int +on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen, + uint8_t flags, + void *user_data) +{ + // session_data *sd = (session_data*)user_data; + + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE){ + clicon_debug(1, "%s %d:", __FUNCTION__, frame->hd.stream_id); + print_header(name, namelen, value, valuelen); + } + return 0; +} + +/*! + */ +static int +on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + // session_data *sd = (session_data*)user_data; + + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE) + clicon_debug(1, "%s Response headers %d", + __FUNCTION__, frame->hd.stream_id); + return 0; +} + +/*! Initialize callbacks + */ +static int +session_init(nghttp2_session **session, + session_data *sd) +{ + nghttp2_session_callbacks *callbacks = NULL; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_client_new(session, callbacks, sd); + + nghttp2_session_callbacks_del(callbacks); + return 0; +} + +static int +send_client_connection_header(nghttp2_session *session) +{ + int retval = -1; + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + clicon_debug(1, "%s", __FUNCTION__); + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)); + if (rv != 0) { + clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); + goto done; + } + retval = 0; + done: + return retval; +} + + +/*! + * Sets sd->sd_stream_id + */ +static int +submit_request(session_data *sd, + char *schema, + char *hostname, + char *path) +{ + int retval = -1; + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", schema, strlen(schema)), + MAKE_NV(":authority", hostname, strlen(hostname)), + MAKE_NV(":path", path, strlen(path)) + }; + + clicon_debug(1, "%s Request headers:", __FUNCTION__); + print_headers(hdrs, ARRLEN(hdrs)); + if ((sd->sd_stream_id = nghttp2_submit_request(sd->sd_session, + NULL, + hdrs, + ARRLEN(hdrs), + NULL, + NULL)) < 0){ + clicon_err(OE_XML, 0, "Could not submit HTTP request: %s", + nghttp2_strerror(sd->sd_stream_id)); + goto done; + } + + retval = 0; + done: + return retval; +} + +static int +socket_connect_inet(char *hostname, + uint16_t port, + int *sock0) +{ + int retval = -1; + int s = -1; + struct sockaddr_in addr; + int ret; + struct hostent *host; + int one = 1; + + clicon_debug(1, "%s to %s:%hu", __FUNCTION__, hostname, port); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if ((ret = inet_pton(addr.sin_family, hostname, &addr.sin_addr)) < 0){ + clicon_err(OE_UNIX, errno, "inet_pton"); + goto done; + } + if (ret == 0){ /* Try DNS NOTE OBSOLETE */ + if ((host = gethostbyname(hostname)) == NULL){ + clicon_err(OE_UNIX, errno, "gethostbyname"); + goto done; + } + addr.sin_addr.s_addr = *(long*)(host->h_addr); /* XXX Just to get it to work */ + } + /* special error handling to get understandable messages (otherwise ENOENT) */ + if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) { + clicon_err(OE_CFG, errno, "socket"); + return -1; + } + if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){ + clicon_err(OE_CFG, errno, "connecting socket inet4"); + close(s); + goto done; + } + /* libev requires this */ + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)); + if (sock0 != NULL) + *sock0 = s; + retval = 0; + done: + if (sock0 == NULL && s >= 0) + close(s); + return retval; +} + +/* NPN TLS extension client callback. We check that server advertised + the HTTP/2 protocol the nghttp2 library supports. If not, exit + the program. */ +static int +select_next_proto_cb(SSL *ssl, + unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + clicon_debug(1, "%s", __FUNCTION__); + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) + return -1; + clicon_debug(1, "%s out: %s in:%s", __FUNCTION__, *out, in); + return SSL_TLSEXT_ERR_OK; +} + + +static SSL_CTX* +InitCTX(void) +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + +#if 1 + method = SSLv23_client_method(); +#else + OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */ + SSL_load_error_strings(); /* Bring in and register error messages */ + method = TLSv1_2_client_method(); /* Create new client-method instance */ +#endif + /* Create new context */ + if ((ctx = SSL_CTX_new(method)) == NULL){ + clicon_err(OE_XML, errno, "SSL_CTX_new"); + goto done; + } + + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3); +#endif + + done: + return ctx; +} + +static int +ssl_input_cb(int s, + void *arg) +{ + int retval = -1; + session_data *sd = (session_data *)arg; + SSL *ssl; + char buf[1024]; + int n; + nghttp2_session *session; + int readlen; + + ssl = sd->sd_ssl; + session = sd->sd_session; + /* get reply & decrypt */ + if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){ + clicon_err(OE_XML, errno, "SSL_read"); + goto done; + } + if (n == 0){ + fprintf(stdout, "%s closed\n", __FUNCTION__); + goto done; + } + if ((readlen = nghttp2_session_mem_recv(session, (unsigned char*)buf, n)) < 0){ + clicon_err(OE_XML, errno, "nghttp2_session_mem_recv"); + goto done; + } + nghttp2_session_send(session); +#if 0 + buf[n] = 0; + fprintf(stdout, "%s\n", buf); +#endif + retval = 0; + done: + return retval; +} + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options] with xml on stdin\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-H \tURI hostname\n" + , + argv0); + exit(0); +} + +int +main(int argc, + char **argv) +{ + int retval = -1; + clicon_handle h; + int c; + char *hostname = NULL; + uint16_t port = 443; + SSL_CTX *ctx = NULL; + int ss = -1; + SSL *ssl; + int ret; + nghttp2_session *session = NULL; + session_data *sd = NULL; + + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + + if ((h = clicon_handle_init()) == NULL) + goto done; + while ((c = getopt(argc, argv, UTIL_SSL_OPTS)) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv[0]); + break; + case 'H': /* hostname */ + hostname = optarg; + break; + default: + usage(argv[0]); + break; + } + if (hostname == NULL){ + fprintf(stderr, "-H is mandatory\n"); + usage(argv[0]); + } + SSL_library_init(); + if ((ctx = InitCTX()) == NULL) + goto done; + if (socket_connect_inet(hostname, port, &ss) < 0) + goto done; + ssl = SSL_new(ctx); /* create new SSL connection state */ + SSL_set_fd(ssl, ss); /* attach the socket descriptor */ + /* perform the connection */ + if ((ret = SSL_connect(ssl)) < 0){ + clicon_err(OE_XML, errno, "SSL_connect"); + goto done; + } + /* In the nghttp2 code, there is an asynchronous step for + * a connected socket, here I just assume it is connected. */ + if ((sd = malloc(sizeof(*sd))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(sd, 0, sizeof(*sd)); + sd->sd_s = ss; + sd->sd_ssl = ssl; + if (session_init(&session, sd) < 0) + goto done; + sd->sd_session = session; + if (send_client_connection_header(session) < 0) + goto done; + if (submit_request(sd, "https", hostname, "/") < 0) + goto done; + if (nghttp2_session_send(session) != 0){ + clicon_err(OE_XML, errno, "nghttp2_session_send"); + goto done; + } + if (event_reg_fd(ss, ssl_input_cb, sd, "ssl socket") < 0) + goto done; + if (event_loop() < 0) + goto done; + retval = 0; + done: + if (ss != -1) + close(ss); + if (ctx) + SSL_CTX_free(ctx); /* release context */ + return retval; +} + + diff --git a/util/clixon_util_socket.c b/util/clixon_util_socket.c index 6c1cbfcf..9aae8b96 100644 --- a/util/clixon_util_socket.c +++ b/util/clixon_util_socket.c @@ -84,7 +84,7 @@ main(int argc, int c; int logdst = CLICON_LOG_STDERR; struct clicon_msg *msg = NULL; - char *sockpath; + char *sockpath = NULL; char *retdata = NULL; int jsonin = 0; char *input_filename = NULL; diff --git a/util/clixon_util_ssl.c b/util/clixon_util_ssl.c new file mode 100644 index 00000000..97ff696f --- /dev/null +++ b/util/clixon_util_ssl.c @@ -0,0 +1,547 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * HTTP2 + OPENSSL client integrated with clixon events + * Example run: clixon_util_ssl -H nghttp2.org + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include /* gethostbyname */ +#include /* inet_pton */ +#include /* TCP_NODELAY */ + + +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +#define UTIL_SSL_OPTS "hD:H:" + +#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) + +/* User data handle to nghttp2 lib */ +typedef struct { + int sd_s; + SSL *sd_ssl; + nghttp2_session *sd_session; + int32_t sd_stream_id; +} session_data; + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +#if 1 /* DEBUG */ +static void +print_header(const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen) +{ + clicon_debug(1, "%s %s", name, value); +} + +/* Print HTTP headers to |f|. Please note that this function does not + take into account that header name and value are sequence of + octets, therefore they may contain non-printable characters. */ +static void +print_headers(nghttp2_nv *nva, + size_t nvlen) +{ + size_t i; + + for (i = 0; i < nvlen; ++i) + print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen); +} +#endif /* DEBUG */ + + +/* Transmit the |data|, |length| bytes, to the network. + * type is: nghttp2_on_header_callback + */ +static ssize_t +send_callback(nghttp2_session *session, + const uint8_t *data, + size_t length, + int flags, + void *user_data) +{ + session_data *sd = (session_data*)user_data; + int ret; + + + clicon_debug(1, "%s %d:", __FUNCTION__, length); +#if 0 + { + int i; + for (i=0; isd_ssl, data, length)) < 0) + return ret; + return ret; +} + +/*! + */ +static int +on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + //session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s %d", __FUNCTION__, frame->hd.stream_id); + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE) + clicon_debug(1, "All headers received %d", frame->hd.stream_id); + + return 0; +} + +/*! + */ +static int +on_data_chunk_recv_callback(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, + void *user_data) +{ + session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s %d", __FUNCTION__, stream_id); + if (sd->sd_session == session && + sd->sd_stream_id == stream_id) + fwrite(data, 1, len, stdout); /* This is where data is printed */ + return 0; +} + +/*! + */ +static int +on_stream_close_callback(nghttp2_session *session, + int32_t stream_id, + nghttp2_error_code error_code, + void *user_data) +{ + //session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +/*! + */ +static int +on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen, + uint8_t flags, + void *user_data) +{ + // session_data *sd = (session_data*)user_data; + + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE){ + clicon_debug(1, "%s %d:", __FUNCTION__, frame->hd.stream_id); + print_header(name, namelen, value, valuelen); + } + return 0; +} + +/*! + */ +static int +on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + // session_data *sd = (session_data*)user_data; + + if (frame->hd.type == NGHTTP2_HEADERS && + frame->headers.cat == NGHTTP2_HCAT_RESPONSE) + clicon_debug(1, "%s Response headers %d", + __FUNCTION__, frame->hd.stream_id); + return 0; +} + +/*! Initialize callbacks + */ +static int +session_init(nghttp2_session **session, + session_data *sd) +{ + nghttp2_session_callbacks *callbacks = NULL; + + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_client_new(session, callbacks, sd); + + nghttp2_session_callbacks_del(callbacks); + return 0; +} + +static int +send_client_connection_header(nghttp2_session *session) +{ + int retval = -1; + nghttp2_settings_entry iv[1] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; + int rv; + + clicon_debug(1, "%s", __FUNCTION__); + /* client 24 bytes magic string will be sent by nghttp2 library */ + rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)); + if (rv != 0) { + clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv)); + goto done; + } + retval = 0; + done: + return retval; +} + + +/*! + * Sets sd->sd_stream_id + */ +static int +submit_request(session_data *sd, + char *schema, + char *hostname, + char *path) +{ + int retval = -1; + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", schema, strlen(schema)), + MAKE_NV(":authority", hostname, strlen(hostname)), + MAKE_NV(":path", path, strlen(path)) + }; + + clicon_debug(1, "%s Request headers:", __FUNCTION__); + print_headers(hdrs, ARRLEN(hdrs)); + if ((sd->sd_stream_id = nghttp2_submit_request(sd->sd_session, + NULL, + hdrs, + ARRLEN(hdrs), + NULL, + NULL)) < 0){ + clicon_err(OE_XML, 0, "Could not submit HTTP request: %s", + nghttp2_strerror(sd->sd_stream_id)); + goto done; + } + + retval = 0; + done: + return retval; +} + +static int +socket_connect_inet(char *hostname, + uint16_t port, + int *sock0) +{ + int retval = -1; + int s = -1; + struct sockaddr_in addr; + int ret; + struct hostent *host; + int one = 1; + + clicon_debug(1, "%s to %s:%hu", __FUNCTION__, hostname, port); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if ((ret = inet_pton(addr.sin_family, hostname, &addr.sin_addr)) < 0){ + clicon_err(OE_UNIX, errno, "inet_pton"); + goto done; + } + if (ret == 0){ /* Try DNS NOTE OBSOLETE */ + if ((host = gethostbyname(hostname)) == NULL){ + clicon_err(OE_UNIX, errno, "gethostbyname"); + goto done; + } + addr.sin_addr.s_addr = *(long*)(host->h_addr); /* XXX Just to get it to work */ + } + /* special error handling to get understandable messages (otherwise ENOENT) */ + if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) { + clicon_err(OE_CFG, errno, "socket"); + return -1; + } + if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){ + clicon_err(OE_CFG, errno, "connecting socket inet4"); + close(s); + goto done; + } + /* libev requires this */ + setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)); + if (sock0 != NULL) + *sock0 = s; + retval = 0; + done: + if (sock0 == NULL && s >= 0) + close(s); + return retval; +} + +/* NPN TLS extension client callback. We check that server advertised + the HTTP/2 protocol the nghttp2 library supports. If not, exit + the program. */ +static int +select_next_proto_cb(SSL *ssl, + unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg) +{ + clicon_debug(1, "%s", __FUNCTION__); + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) + return -1; + clicon_debug(1, "%s out: %s in:%s", __FUNCTION__, *out, in); + return SSL_TLSEXT_ERR_OK; +} + + +static SSL_CTX* +InitCTX(void) +{ + const SSL_METHOD *method; + SSL_CTX *ctx; + +#if 1 + method = SSLv23_client_method(); +#else + OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */ + SSL_load_error_strings(); /* Bring in and register error messages */ + method = TLSv1_2_client_method(); /* Create new client-method instance */ +#endif + /* Create new context */ + if ((ctx = SSL_CTX_new(method)) == NULL){ + clicon_err(OE_XML, errno, "SSL_CTX_new"); + goto done; + } + + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3); +#endif + + done: + return ctx; +} + +static int +ssl_input_cb(int s, + void *arg) +{ + int retval = -1; + session_data *sd = (session_data *)arg; + SSL *ssl; + char buf[1024]; + int n; + nghttp2_session *session; + int readlen; + + ssl = sd->sd_ssl; + session = sd->sd_session; + /* get reply & decrypt */ + if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){ + clicon_err(OE_XML, errno, "SSL_read"); + goto done; + } + if (n == 0){ + fprintf(stdout, "%s closed\n", __FUNCTION__); + goto done; + } + if ((readlen = nghttp2_session_mem_recv(session, (unsigned char*)buf, n)) < 0){ + clicon_err(OE_XML, errno, "nghttp2_session_mem_recv"); + goto done; + } + nghttp2_session_send(session); +#if 0 + buf[n] = 0; + fprintf(stdout, "%s\n", buf); +#endif + retval = 0; + done: + return retval; +} + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options] with xml on stdin\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-H \tURI hostname\n" + , + argv0); + exit(0); +} + +int +main(int argc, + char **argv) +{ + int retval = -1; + clicon_handle h; + int c; + char *hostname = NULL; + uint16_t port = 443; + SSL_CTX *ctx = NULL; + int ss = -1; + SSL *ssl; + int ret; + nghttp2_session *session = NULL; + session_data *sd = NULL; + + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + + if ((h = clicon_handle_init()) == NULL) + goto done; + while ((c = getopt(argc, argv, UTIL_SSL_OPTS)) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv[0]); + break; + case 'H': /* hostname */ + hostname = optarg; + break; + default: + usage(argv[0]); + break; + } + if (hostname == NULL){ + fprintf(stderr, "-H is mandatory\n"); + usage(argv[0]); + } + SSL_library_init(); + if ((ctx = InitCTX()) == NULL) + goto done; + if (socket_connect_inet(hostname, port, &ss) < 0) + goto done; + ssl = SSL_new(ctx); /* create new SSL connection state */ + SSL_set_fd(ssl, ss); /* attach the socket descriptor */ + /* perform the connection */ + if ((ret = SSL_connect(ssl)) < 0){ + clicon_err(OE_XML, errno, "SSL_connect"); + goto done; + } + /* In the nghttp2 code, there is an asynchronous step for + * a connected socket, here I just assume it is connected. */ + if ((sd = malloc(sizeof(*sd))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(sd, 0, sizeof(*sd)); + sd->sd_s = ss; + sd->sd_ssl = ssl; + if (session_init(&session, sd) < 0) + goto done; + sd->sd_session = session; + if (send_client_connection_header(session) < 0) + goto done; + if (submit_request(sd, "https", hostname, "/") < 0) + goto done; + if (nghttp2_session_send(session) != 0){ + clicon_err(OE_XML, errno, "nghttp2_session_send"); + goto done; + } + if (event_reg_fd(ss, ssl_input_cb, sd, "ssl socket") < 0) + goto done; + if (event_loop() < 0) + goto done; + retval = 0; + done: + if (ss != -1) + close(ss); + if (ctx) + SSL_CTX_free(ctx); /* release context */ + return retval; +} + +