Restconf RFC8071 call-home first working prototype
This commit is contained in:
parent
a3b94f4781
commit
7d8ddf7697
18 changed files with 1115 additions and 122 deletions
537
util/clixon_restconf_callhome_client.c
Normal file
537
util/clixon_restconf_callhome_client.c
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC (Netgate)
|
||||
|
||||
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 *****
|
||||
|
||||
See RFC 8071 NETCONF Call Home and RESTCONF Call Home
|
||||
|
||||
device/server client
|
||||
+-----------------+ 1) tcp connect +-----------------+
|
||||
| clixon_restconf | ----------------> | callhome-client | <------ 3) HTTP
|
||||
| | 2) tls | |
|
||||
+-----------------+ <--------------- +-----------------+
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <syslog.h>
|
||||
#include <signal.h>
|
||||
#include <netdb.h> /* gethostbyname */
|
||||
#include <arpa/inet.h> /* inet_pton */
|
||||
#include <netinet/tcp.h> /* TCP_NODELAY */
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include "clixon/clixon.h"
|
||||
|
||||
#define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:"
|
||||
|
||||
#define RESTCONF_CH_TLS 4336
|
||||
|
||||
/* User struct for context / accept */
|
||||
typedef struct {
|
||||
int ta_ss; /* accept socket */
|
||||
SSL_CTX *ta_ctx; /* SSL context */
|
||||
FILE *ta_f; /* Input data file */
|
||||
} tls_accept_handle;
|
||||
|
||||
/* User connection-specific data handle */
|
||||
typedef struct {
|
||||
int sd_s; /* data socket */
|
||||
SSL *sd_ssl; /* SSL connection data */
|
||||
} tls_session_data;
|
||||
|
||||
/*! Create and bind stream socket
|
||||
* @param[in] sa Socketaddress
|
||||
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
|
||||
* @param[in] backlog Listen backlog, queie of pending connections
|
||||
* @param[out] sock Server socket (bound for accept)
|
||||
*/
|
||||
int
|
||||
callhome_bind(struct sockaddr *sa,
|
||||
size_t sin_len,
|
||||
int backlog,
|
||||
int *sock)
|
||||
{
|
||||
int retval = -1;
|
||||
int s = -1;
|
||||
int on = 1;
|
||||
|
||||
if (sock == NULL){
|
||||
errno = EINVAL;
|
||||
perror("sock");
|
||||
goto done;
|
||||
}
|
||||
/* create inet socket */
|
||||
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
|
||||
perror("socket");
|
||||
goto done;
|
||||
}
|
||||
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
|
||||
perror("setsockopt SO_KEEPALIVE");
|
||||
goto done;
|
||||
}
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
|
||||
perror("setsockopt SO_REUSEADDR");
|
||||
goto done;
|
||||
}
|
||||
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
|
||||
if (sa->sa_family == AF_INET6 &&
|
||||
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
|
||||
perror("setsockopt IPPROTO_IPV6");
|
||||
goto done;
|
||||
}
|
||||
if (bind(s, sa, sin_len) == -1) {
|
||||
perror("bind");
|
||||
goto done;
|
||||
}
|
||||
if (listen(s, backlog) < 0){
|
||||
perror("listen");
|
||||
goto done;
|
||||
}
|
||||
if (sock)
|
||||
*sock = s;
|
||||
retval = 0;
|
||||
done:
|
||||
if (retval != 0 && s != -1)
|
||||
close(s);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Client data socket
|
||||
*/
|
||||
static int
|
||||
tls_input_cb(int s,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
tls_session_data *sd = (tls_session_data *)arg;
|
||||
SSL *ssl;
|
||||
char buf[1024];
|
||||
int n;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
ssl = sd->sd_ssl;
|
||||
/* get reply & decrypt */
|
||||
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
|
||||
clicon_err(OE_XML, errno, "SSL_read");
|
||||
goto done;
|
||||
}
|
||||
if (n == 0){
|
||||
clicon_debug(1, "%s closed", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
buf[n] = 0;
|
||||
fprintf(stdout, "%s\n", buf);
|
||||
SSL_shutdown(ssl);
|
||||
SSL_free(ssl);
|
||||
clixon_event_unreg_fd(s, tls_input_cb);
|
||||
close(s);
|
||||
free(sd);
|
||||
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create ssl connection, select alpn, connect and verify
|
||||
*/
|
||||
static int
|
||||
tls_ssl_init_connect(SSL_CTX *ctx,
|
||||
int s,
|
||||
SSL **sslp)
|
||||
{
|
||||
int retval = -1;
|
||||
SSL *ssl = NULL;
|
||||
unsigned char protos[10];
|
||||
int ret;
|
||||
int verify;
|
||||
int sslerr;
|
||||
|
||||
/* create new SSL connection state */
|
||||
if ((ssl = SSL_new(ctx)) == NULL){
|
||||
clicon_err(OE_SSL, 0, "SSL_new.");
|
||||
goto done;
|
||||
}
|
||||
SSL_set_fd(ssl, s); /* attach the socket descriptor */
|
||||
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
|
||||
|
||||
protos[0] = 8;
|
||||
strncpy((char*)&protos[1], "http/1.1", 9);
|
||||
if ((retval = SSL_set_alpn_protos(ssl, protos, 9)) != 0){
|
||||
clicon_err(OE_SSL, retval, "SSL_set_alpn_protos.");
|
||||
goto done;
|
||||
}
|
||||
#if 0
|
||||
SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||
SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
|
||||
#endif
|
||||
|
||||
/* perform the connection
|
||||
TLSEXT_TYPE_application_layer_protocol_negotiation
|
||||
int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
|
||||
unsigned int protos_len);
|
||||
see
|
||||
https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_alpn_select_cb.html
|
||||
*/
|
||||
if ((ret = SSL_connect(ssl)) < 1){
|
||||
sslerr = SSL_get_error(ssl, ret);
|
||||
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
||||
|
||||
switch (sslerr){
|
||||
case SSL_ERROR_SSL: /* 1 */
|
||||
goto done;
|
||||
break;
|
||||
default:
|
||||
clicon_err(OE_XML, errno, "SSL_connect");
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* check certificate verification result */
|
||||
verify = SSL_get_verify_result(ssl);
|
||||
switch (verify) {
|
||||
case X509_V_OK:
|
||||
break;
|
||||
default:
|
||||
clicon_err(OE_SSL, errno, "verify problems: %d", verify);
|
||||
goto done;
|
||||
}
|
||||
*sslp = ssl;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Read data from file/stdin and write to TLS data socket
|
||||
*/
|
||||
static int
|
||||
tls_write_file(FILE *fp,
|
||||
SSL *ssl)
|
||||
{
|
||||
int retval = -1;
|
||||
char *buf = NULL;
|
||||
int buflen = 1024; /* start size */
|
||||
char ch;
|
||||
int ret;
|
||||
int sslerr;
|
||||
size_t len = 0;
|
||||
|
||||
if ((buf = malloc(buflen)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(buf, 0, buflen);
|
||||
while (1){
|
||||
if ((ret = fread(&ch, 1, 1, fp)) < 0){
|
||||
clicon_err(OE_JSON, errno, "read");
|
||||
goto done;
|
||||
}
|
||||
if (ret == 0)
|
||||
break;
|
||||
buf[len++] = ch;
|
||||
// XXX No realloc, can overflow
|
||||
}
|
||||
if ((ret = SSL_write(ssl, buf, len)) < 1){
|
||||
sslerr = SSL_get_error(ssl, ret);
|
||||
clicon_debug(1, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (buf)
|
||||
free(buf);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Callhome-server accept socket
|
||||
*/
|
||||
static int
|
||||
tls_accept_cb(int ss,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
tls_accept_handle *ta = (tls_accept_handle *)arg;
|
||||
tls_session_data *sd = NULL;
|
||||
int s;
|
||||
struct sockaddr from = {0,};
|
||||
socklen_t len;
|
||||
SSL *ssl = NULL;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
len = sizeof(from);
|
||||
if ((s = accept(ss, &from, &len)) < 0){
|
||||
perror("accept");
|
||||
goto done;
|
||||
}
|
||||
clicon_debug(1, "accepted");
|
||||
if (tls_ssl_init_connect(ta->ta_ctx, s, &ssl) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "connected");
|
||||
if ((sd = malloc(sizeof(*sd))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(sd, 0, sizeof(*sd));
|
||||
sd->sd_s = s;
|
||||
sd->sd_ssl = ssl;
|
||||
/* Write HTTP request on socket */
|
||||
if (tls_write_file(ta->ta_f, ssl) < 0)
|
||||
goto done;
|
||||
/* register callback for reply */
|
||||
if (clixon_event_reg_fd(s, tls_input_cb, sd, "tls data") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
* out must be set to point to the selected protocol (which may be within in).
|
||||
*/
|
||||
static int
|
||||
tls_proto_select_cb(SSL *s,
|
||||
unsigned char **out,
|
||||
unsigned char *outlen,
|
||||
const unsigned char *in,
|
||||
unsigned int inlen,
|
||||
void *arg)
|
||||
{
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Verify tls auth
|
||||
* @see tlsauth_verify_callback
|
||||
* This code needs a "X509 store", see X509_STORE_new()
|
||||
* crl_file / crl_dir
|
||||
*/
|
||||
static int
|
||||
tls_auth_verify_callback(int preverify_ok,
|
||||
X509_STORE_CTX *x509_ctx)
|
||||
{
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
static SSL_CTX *
|
||||
tls_ctx_init(const char *cert_path,
|
||||
const char *key_path,
|
||||
const char *ca_cert_path)
|
||||
{
|
||||
SSL_CTX *ctx = NULL;
|
||||
|
||||
if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) {
|
||||
clicon_err(OE_SSL, 0, "SSL_CTX_new");
|
||||
goto done;
|
||||
}
|
||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, tls_auth_verify_callback);
|
||||
/* get peer certificate
|
||||
nc_client_tls_update_opts */
|
||||
if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) != 1) {
|
||||
clicon_err(OE_SSL, 0, "SSL_CTX_use_certificate_file");
|
||||
goto done;
|
||||
}
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) != 1) {
|
||||
clicon_err(OE_SSL, 0, "SSL_CTX_use_PrivateKey_file");
|
||||
goto done;
|
||||
}
|
||||
if (SSL_CTX_load_verify_locations(ctx, ca_cert_path, NULL) != 1) {
|
||||
clicon_err(OE_SSL, 0, "SSL_CTX_load_verify_locations");
|
||||
goto done;
|
||||
}
|
||||
(void)SSL_CTX_set_next_proto_select_cb(ctx, tls_proto_select_cb, NULL);
|
||||
return ctx;
|
||||
done:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int
|
||||
usage(char *argv0)
|
||||
{
|
||||
fprintf(stderr, "usage:%s [options]\n"
|
||||
"where options are\n"
|
||||
"\t-h \t\tHelp\n"
|
||||
"\t-D <level> \tDebug\n"
|
||||
"\t-f <file> \tHHTP input file (overrides stdin)\n"
|
||||
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
|
||||
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
|
||||
"\t-p <port> \tPort (default %d)\n"
|
||||
"\t-c <path> \tcert\n"
|
||||
"\t-C <path> \tcacert\n"
|
||||
"\t-k <path> \tkey\n"
|
||||
,
|
||||
argv0,
|
||||
RESTCONF_CH_TLS);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc,
|
||||
char **argv)
|
||||
{
|
||||
int retval = -1;
|
||||
clicon_handle h;
|
||||
int c;
|
||||
uint16_t port = RESTCONF_CH_TLS;
|
||||
SSL_CTX *ctx = NULL;
|
||||
int ss = -1;
|
||||
int dbg = 0;
|
||||
tls_accept_handle *ta = NULL;
|
||||
char *input_filename = NULL;
|
||||
char *ca_cert_path = NULL;
|
||||
char *cert_path = NULL;
|
||||
char *key_path = NULL;
|
||||
FILE *fp = stdin; /* base file, stdin, can be overridden with -f */
|
||||
size_t sa_len;
|
||||
char *addr = "127.0.0.1";
|
||||
char *family = "inet:ipv4-address";
|
||||
struct sockaddr sa = {0,};
|
||||
|
||||
/* 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_TLS_OPTS)) != -1)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'D':
|
||||
if (sscanf(optarg, "%d", &dbg) != 1)
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'f':
|
||||
if (optarg == NULL || *optarg == '-')
|
||||
usage(argv[0]);
|
||||
input_filename = optarg;
|
||||
break;
|
||||
case 'F':
|
||||
family = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
addr = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
if (sscanf(optarg, "%hu", &port) != 1)
|
||||
usage(argv[0]);
|
||||
break;
|
||||
case 'c':
|
||||
if (optarg == NULL || *optarg == '-')
|
||||
usage(argv[0]);
|
||||
cert_path = optarg;
|
||||
break;
|
||||
case 'C':
|
||||
if (optarg == NULL || *optarg == '-')
|
||||
usage(argv[0]);
|
||||
ca_cert_path = optarg;
|
||||
break;
|
||||
case 'k':
|
||||
if (optarg == NULL || *optarg == '-')
|
||||
usage(argv[0]);
|
||||
key_path = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
break;
|
||||
}
|
||||
if (cert_path == NULL || key_path == NULL || ca_cert_path == NULL){
|
||||
fprintf(stderr, "-c <cert path> and -k <key path> -C <ca-cert> are mandatory\n");
|
||||
usage(argv[0]);
|
||||
}
|
||||
clicon_debug_init(dbg, NULL);
|
||||
|
||||
if (input_filename){
|
||||
if ((fp = fopen(input_filename, "r")) == NULL){
|
||||
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if ((ctx = tls_ctx_init(cert_path, key_path, ca_cert_path)) == NULL)
|
||||
goto done;
|
||||
if (port == 0){
|
||||
fprintf(stderr, "-p <port> is invalid\n");
|
||||
usage(argv[0]);
|
||||
goto done;
|
||||
}
|
||||
if (addr == NULL){
|
||||
fprintf(stderr, "-a <addr> is NULL\n");
|
||||
usage(argv[0]);
|
||||
goto done;
|
||||
}
|
||||
if (clixon_inet2sin(family, addr, port, &sa, &sa_len) < 0)
|
||||
goto done;
|
||||
/* Bind port */
|
||||
if (callhome_bind(&sa, sa_len, 1, &ss) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "bind");
|
||||
if ((ta = malloc(sizeof(*ta))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
memset(ta, 0, sizeof(*ta));
|
||||
ta->ta_ctx = ctx;
|
||||
ta->ta_ss = ss;
|
||||
ta->ta_f = fp;
|
||||
if (clixon_event_reg_fd(ss, tls_accept_cb, ta, "tls accept socket") < 0)
|
||||
goto done;
|
||||
if (clixon_event_loop(h) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (ss != -1)
|
||||
clixon_event_unreg_fd(ss, tls_accept_cb);
|
||||
if (ta)
|
||||
free(ta);
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
if (ss != -1)
|
||||
close(ss);
|
||||
if (ctx)
|
||||
SSL_CTX_free(ctx); /* release context */
|
||||
clicon_handle_exit(h); /* frees h and options (and streams) */
|
||||
clixon_err_exit();
|
||||
clicon_debug(1, "clixon_restconf_callhome_client pid:%u done", getpid());
|
||||
clicon_log_exit(); /* Must be after last clicon_debug */
|
||||
return retval;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue