diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d8fb98..82bd194f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,10 @@ ## 5.9.0 Expected: September 2022 +### New features + +* RESTCONF call home according to RFC 8071 + ## 5.8.0 28 July 2022 diff --git a/apps/restconf/restconf_http1.c b/apps/restconf/restconf_http1.c index 6a73307f..3cd1ec8f 100644 --- a/apps/restconf/restconf_http1.c +++ b/apps/restconf/restconf_http1.c @@ -455,6 +455,8 @@ restconf_http1_path_root(clicon_handle h, retval = 0; done: clicon_debug(1, "%s %d", __FUNCTION__, retval); + if (subject) + free(subject); if (xerr) xml_free(xerr); if (cvv) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index cff775bd..51fa6a6b 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -820,6 +820,7 @@ restconf_config_init(clicon_handle h, goto done; } + /*! Create and bind restconf socket * * @param[in] netns0 Network namespace, special value "default" is same as NULL @@ -840,10 +841,8 @@ restconf_socket_init(const char *netns0, int *ss) { int retval = -1; - struct sockaddr * sa; - struct sockaddr_in6 sin6 = { 0 }; - struct sockaddr_in sin = { 0 }; - size_t sin_len; + struct sockaddr sa = {0,}; + size_t sa_len; const char *netns; clicon_debug(1, "%s %s %s %s %hu", __FUNCTION__, netns0, addrtype, addrstr, port); @@ -852,27 +851,9 @@ restconf_socket_init(const char *netns0, netns = NULL; else netns = netns0; - if (strcmp(addrtype, "inet:ipv6-address") == 0) { - sin_len = sizeof(struct sockaddr_in6); - sin6.sin6_port = htons(port); - sin6.sin6_family = AF_INET6; - - inet_pton(AF_INET6, addrstr, &sin6.sin6_addr); - sa = (struct sockaddr *)&sin6; - } - else if (strcmp(addrtype, "inet:ipv4-address") == 0) { - sin_len = sizeof(struct sockaddr_in); - sin.sin_family = AF_INET; - sin.sin_port = htons(port); - sin.sin_addr.s_addr = inet_addr(addrstr); - - sa = (struct sockaddr *)&sin; - } - else{ - clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype); - return -1; - } - if (clixon_netns_socket(netns, sa, sin_len, backlog, flags, addrstr, ss) < 0) + if (clixon_inet2sin(addrtype, addrstr, port, &sa, &sa_len) < 0) + goto done; + if (clixon_netns_socket(netns, &sa, sa_len, backlog, flags, addrstr, ss) < 0) goto done; clicon_debug(1, "%s ss=%d", __FUNCTION__, *ss); retval = 0; @@ -888,8 +869,9 @@ restconf_socket_init(const char *netns0, * @param[out] namespace * @param[out] address Address as string, eg "0.0.0.0", "::" * @param[out] addrtype One of inet:ipv4-address or inet:ipv6-address - * @param[out] port - * @param[out] ssl + * @param[out] port TCP Port + * @param[out] ssl SSL enabled? + * @param[out] callhome Callhome enabled? */ int restconf_socket_extract(clicon_handle h, @@ -899,7 +881,8 @@ restconf_socket_extract(clicon_handle h, char **address, char **addrtype, uint16_t *port, - uint16_t *ssl) + uint16_t *ssl, + int *callhome) { int retval = -1; cxobj *x; @@ -979,6 +962,10 @@ restconf_socket_extract(clicon_handle h, goto done; } } + if ((x = xpath_first(xs, nsc, "call-home")) != NULL) + *callhome = 1; + else + *callhome = 0; retval = 0; done: if (cv) diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 90144581..c1f4eaae 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -97,7 +97,7 @@ int restconf_drop_privileges(clicon_handle h); int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out); int restconf_config_init(clicon_handle h, cxobj *xrestconf); int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss); -int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl); +int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl, int *callhome); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 888be1fe..d7fbcff5 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -583,7 +583,7 @@ ssl_alpn_check(clicon_handle h, int ret; cbuf *cberr = NULL; - clicon_debug(1, "%s %s", __FUNCTION__, alpn); + clicon_debug(1, "%s", __FUNCTION__); /* Alternatively, call restconf_str2proto but alpn is not a proper string */ if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){ *proto = HTTP_11; @@ -642,23 +642,20 @@ ssl_alpn_check(clicon_handle h, return retval; } /* ssl_alpn_check */ -/*! Accept new socket client + +/*! Accept new socket client (ssl not ip) * @param[in] fd Socket (unix or ip) * @param[in] arg typecast clicon_handle * @see openssl_init_socket where this callback is registered */ static int -restconf_accept_client(int fd, - void *arg) +restconf_ssl_accept_client(clicon_handle h, + int s, + restconf_socket *rsock) { int retval = -1; - restconf_socket *rsock; restconf_native_handle *rh = NULL; restconf_conn *rc = NULL; - clicon_handle h; - int s; - struct sockaddr from = {0,}; - socklen_t len; char *name = NULL; int ret; int e; @@ -668,30 +665,18 @@ restconf_accept_client(int fd, unsigned int alpnlen = 0; restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */ - clicon_debug(1, "%s %d", __FUNCTION__, fd); + clicon_debug(1, "%s", __FUNCTION__); #ifdef HAVE_LIBNGHTTP2 #ifndef HAVE_HTTP1 proto = HTTP_2; /* If nghttp2 only let default be 2.0 */ #endif #endif - if ((rsock = (restconf_socket *)arg) == NULL){ - clicon_err(OE_YANG, EINVAL, "rsock is NULL"); - goto done; - } - clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__, - rsock->rs_addrtype, - rsock->rs_addrstr, - rsock->rs_port); - h = rsock->rs_h; +#if 1 if ((rh = restconf_native_handle_get(h)) == NULL){ clicon_err(OE_XML, EFAULT, "No openssl handle"); goto done; } - len = sizeof(from); - if ((s = accept(rsock->rs_ss, &from, &len)) < 0){ - clicon_err(OE_UNIX, errno, "accept"); - goto done; - } +#endif /* * Register callbacks for actual data socket */ @@ -919,6 +904,50 @@ restconf_accept_client(int fd, if (name) free(name); return retval; +} /* restconf_ssl_accept_client */ + +/*! Accept new socket client + * @param[in] fd Socket (unix or ip) + * @param[in] arg typecast clicon_handle + * @see openssl_init_socket where this callback is registered + */ +static int +restconf_accept_client(int fd, + void *arg) + +{ + int retval = -1; + restconf_socket *rsock; + clicon_handle h; + int s; + struct sockaddr from = {0,}; + socklen_t len; + char *name = NULL; + + clicon_debug(1, "%s %d", __FUNCTION__, fd); + if ((rsock = (restconf_socket *)arg) == NULL){ + clicon_err(OE_YANG, EINVAL, "rsock is NULL"); + goto done; + } + clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__, + rsock->rs_addrtype, + rsock->rs_addrstr, + rsock->rs_port); + h = rsock->rs_h; + len = sizeof(from); + if ((s = accept(rsock->rs_ss, &from, &len)) < 0){ + clicon_err(OE_UNIX, errno, "accept"); + goto done; + } + /* Accept SSL */ + if (restconf_ssl_accept_client(h, s, rsock) < 0) + goto done; + retval = 0; + done: + clicon_debug(1, "%s retval %d", __FUNCTION__, retval); + if (name) + free(name); + return retval; } /* restconf_accept_client */ /*! @@ -1019,10 +1048,63 @@ restconf_clixon_backend(clicon_handle h, goto done; } +/*! Periodically try to connect to callhome client + */ +int +restconf_callhome_timer(int fd, + void *arg) +{ + int retval = -1; + clicon_handle h; + struct timeval now; + struct timeval t; + struct timeval t1 = {1, 0}; // XXX once every second + restconf_socket *rs; + struct sockaddr sa = {0,}; + size_t sa_len; + int s; + + clicon_debug(1, "%s", __FUNCTION__); + gettimeofday(&now, NULL); + if ((rs = (restconf_socket *)arg) == NULL){ + clicon_err(OE_YANG, EINVAL, "rsock is NULL"); + goto done; + } + h = rs->rs_h; + if (clixon_inet2sin(rs->rs_addrtype, rs->rs_addrstr, rs->rs_port, &sa, &sa_len) < 0) + goto done; + if ((s = socket(sa.sa_family, SOCK_STREAM, 0)) < 0) { + clicon_err(OE_UNIX, errno, "socket"); + goto done; + } + clicon_debug(1, "%s connect", __FUNCTION__); + if (connect(s, &sa, sa_len) < 0){ + close(s); + /* Fail: Initiate new timer */ + timeradd(&now, &t1, &t); + if (clixon_event_reg_timeout(t, + restconf_callhome_timer, /* this function */ + rs, + "restconf callhome timer") < 0) + goto done; + } + else { + rs->rs_ss = s; + if (restconf_ssl_accept_client(h, rs->rs_ss, rs) < 0) + goto done; + } + clicon_debug(1, "%s connect done", __FUNCTION__); + retval = 0; + done: + return retval; +} + /*! Per-socket openssl inits * @param[in] h Clicon handle * @param[in] xs XML config of single restconf socket * @param[in] nsc Namespace context + * @retval 0 OK + * @retval -1 Error */ static int openssl_init_socket(clicon_handle h, @@ -1033,6 +1115,7 @@ openssl_init_socket(clicon_handle h, char *netns = NULL; char *address = NULL; char *addrtype = NULL; + int callhome = 0; uint16_t ssl = 0; uint16_t port = 0; int ss = -1; @@ -1041,23 +1124,9 @@ openssl_init_socket(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); /* Extract socket parameters from single socket config: ns, addr, port, ssl */ - if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl) < 0) + if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl, &callhome) < 0) goto done; - /* Open restconf socket and bind */ - if (restconf_socket_init(netns, address, addrtype, port, - SOCKET_LISTEN_BACKLOG, -#ifdef RESTCONF_OPENSSL_NONBLOCKING - SOCK_NONBLOCK, /* Also 0 is possible */ -#else /* blocking */ - 0, -#endif - &ss - ) < 0) - goto done; - if ((rh = restconf_native_handle_get(h)) == NULL){ - clicon_err(OE_XML, EFAULT, "No openssl handle"); - goto done; - } + /* * Create per-socket openssl handle * See restconf_native_terminate for freeing @@ -1068,8 +1137,33 @@ openssl_init_socket(clicon_handle h, } memset(rsock, 0, sizeof *rsock); rsock->rs_h = h; + rsock->rs_ssl = ssl; /* true/false */ + if (callhome){ + if (!ssl){ + clicon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL"); + goto done; + } + } + else { /* listen/accept */ + /* Open restconf socket and bind for later accept */ + if (restconf_socket_init(netns, address, addrtype, port, + SOCKET_LISTEN_BACKLOG, +#ifdef RESTCONF_OPENSSL_NONBLOCKING + SOCK_NONBLOCK, /* Also 0 is possible */ +#else /* blocking */ + 0, +#endif + &ss + ) < 0) + goto done; + } + if ((rh = restconf_native_handle_get(h)) == NULL){ + clicon_err(OE_XML, EFAULT, "No openssl handle"); + goto done; + } + rsock->rs_ss = ss; - rsock->rs_ssl = ssl; + if ((rsock->rs_addrstr = strdup(address)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; @@ -1081,29 +1175,24 @@ openssl_init_socket(clicon_handle h, rsock->rs_port = port; INSQ(rsock, rh->rh_sockets); - /* ss is a server socket that the clients connect to. The callback - therefore accepts clients on ss */ -#ifdef RESTCONF_HTTP1_UNITTEST - { - restconf_conn *rc; - if ((rc = restconf_conn_new(h, 0)) == NULL) - goto done; - rc->rc_s = 0; - if (restconf_stream_data_new(rc, 0) == NULL) - goto done; - if (clixon_event_reg_fd(0, restconf_connection, rc, "restconf socket") < 0) + if (callhome){ + if (restconf_callhome_timer(ss, rsock) < 0) + goto done; + } + else { + /* ss is a server socket that the clients connect to. The callback + therefore accepts clients on ss */ + if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0) goto done; } -#else - if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0) - goto done; -#endif retval = 0; done: return retval; } /*! Init openssl, open and register server socket (ready for accept) + * + * Given a fully populated configuration tree. * @param[in] h Clicon handle * @param[in] dbg0 Manually set debug flag, if set overrides configuration setting * @param[in] xrestconf XML tree containing restconf config diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c index 591109bb..0377dfee 100644 --- a/apps/restconf/restconf_native.c +++ b/apps/restconf/restconf_native.c @@ -702,12 +702,10 @@ restconf_http1_process(restconf_conn *rc, */ if ((ret = http1_check_content_length(h, sd, &status)) < 0) goto done; -#ifndef RESTCONF_HTTP1_UNITTEST /* Ignore content-length */ if (status == 1){ (*readmore)++; goto ok; } -#endif /* nginx compatible, set HTTPS parameter if SSL */ if (rc->rc_ssl) if (restconf_param_set(h, "HTTPS", "https") < 0) @@ -715,15 +713,9 @@ restconf_http1_process(restconf_conn *rc, /* main restconf processing */ if (restconf_http1_path_root(h, rc) < 0) goto done; -#ifdef RESTCONF_HTTP1_UNITTEST - if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), - 1, rc->rc_ssl) < 0) - goto done; -#else if (native_buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf), rc->rc_s, rc->rc_ssl) < 0) goto done; -#endif cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */ cbuf_reset(sd->sd_outp_buf); cbuf_reset(sd->sd_inbuf); @@ -951,16 +943,5 @@ restconf_connection(int s, retval = 0; done: clicon_debug(1, "%s retval %d", __FUNCTION__, retval); -#ifdef RESTCONF_HTTP1_UNITTEST /* unit test */ - if (rc){ - if (close(rc->rc_s) < 0){ - clicon_err(OE_UNIX, errno, "close"); - goto done; - } - clixon_event_unreg_fd(rc->rc_s, restconf_connection); - restconf_conn_free(rc); - } - exit(0); -#endif return retval; } /* restconf_connection */ diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 0f47244d..f10680be 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -152,11 +152,6 @@ */ #define USE_CONFIG_FLAG_CACHE -/*! Restconf native unit test for fuzzing of http/1 parser - * See test/fuzz/http1 - */ -#undef RESTCONF_HTTP1_UNITTEST - /*! If backend is restarted, cli and netconf client will retry (once) and reconnect * Note, if client has locked or had edits in progress, these will be lost * A warning will be printed diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index cc402e0f..b56b879b 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -97,4 +97,6 @@ int send_msg_reply(int s, char *data, uint32_t datalen); int detect_endtag(char *tag, char ch, int *state); +int clixon_inet2sin(const char *addrtype, const char *addrstr, uint16_t port, struct sockaddr *sa, size_t *sa_len); + #endif /* _CLIXON_PROTO_H_ */ diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index 6af59560..36dfcdc4 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -172,6 +172,7 @@ clicon_sig_ignore_get(void) * } * clixon_event_reg_fd(fd, fn, (void*)42, "call fn on input on fd"); * @endcode + * @see clixon_event_unreg_fd */ int clixon_event_reg_fd(int fd, diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index d4222bf3..ac067301 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -810,3 +810,49 @@ detect_endtag(char *tag, *state = 0; return retval; } + +/*! Given family, addr str, port, return sockaddr and length + * + * @param[in] addrtype Address family: inet:ipv4-address or inet:ipv6-address + * @param[in] addrstr IP address as string + * @param[in] port TCP port host byte order + * @param[out] sa sockaddr, should be allocated + * @param[out] salen length of sockaddr data + * @code + * struct sockaddr sa = {0,}; + * size_t sa_len; + * if (clixon_inet2sin(inet:ipv4-address, "0.0.0.0", 80, &sa, &sa_len) < 0) + * err; + * @endcode + * Probably misplaced, need a clixon_network file? + */ +int +clixon_inet2sin(const char *addrtype, + const char *addrstr, + uint16_t port, + struct sockaddr *sa, + size_t *sa_len) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (strcmp(addrtype, "inet:ipv6-address") == 0) { + sin6 = (struct sockaddr_in6 *)sa; + *sa_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = htons(port); + sin6->sin6_family = AF_INET6; + inet_pton(AF_INET6, addrstr, &sin6->sin6_addr); + } + else if (strcmp(addrtype, "inet:ipv4-address") == 0) { + sin = (struct sockaddr_in *)sa; + *sa_len = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + sin->sin_addr.s_addr = inet_addr(addrstr); + } + else{ + clicon_err(OE_XML, EINVAL, "Unexpected addrtype: %s", addrtype); + return -1; + } + return 0; +} diff --git a/test/config.sh.in b/test/config.sh.in index f58f13e7..f7ef199d 100755 --- a/test/config.sh.in +++ b/test/config.sh.in @@ -75,7 +75,7 @@ DATASTORE_TOP="config" CLIXON_AUTOCLI_REV="2022-02-11" CLIXON_LIB_REV="2021-12-05" CLIXON_CONFIG_REV="2022-03-21" -CLIXON_RESTCONF_REV="2022-03-21" +CLIXON_RESTCONF_REV="2022-08-01" CLIXON_EXAMPLE_REV="2020-12-01" # Length of TSL RSA key diff --git a/test/mem.sh b/test/mem.sh index b592c06d..d5a63fd9 100755 --- a/test/mem.sh +++ b/test/mem.sh @@ -61,6 +61,12 @@ function memonce(){ memerr=0 for test in $pattern; do + # Can happen if no pattern, eg pattern=foo but "foo" does not exist + if [ ! -f $test ]; then + echo -e "\e[31mNo such file: $test" + echo -ne "\e[0m" + exit -1 + fi if [ $testnr != 0 ]; then echo; fi perfnr=1000 # Limit performance tests testfile=$test diff --git a/test/test_restconf_callhome.sh b/test/test_restconf_callhome.sh new file mode 100755 index 00000000..61d02d8e --- /dev/null +++ b/test/test_restconf_callhome.sh @@ -0,0 +1,243 @@ +#!/usr/bin/env bash +# test of Restconf callhome +# See RFC 8071 NETCONF Call Home and RESTCONF Call Home +# No NACM for now + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +: ${clixon_restconf_callhome_client:=clixon_restconf_callhome_client} + +cfg=$dir/conf_yang.xml +fyang=$dir/clixon-example.yang +clispec=$dir/spec.cli + +certdir=$dir/certs +cakey=$certdir/ca_key.pem +cacert=$certdir/ca_cert.pem +srvkey=$certdir/srv_key.pem +srvcert=$certdir/srv_cert.pem + +users="andy" # generate certs for some users + +RCPROTO=https +# Callhome stream is HTTP/1.1, other communication is HTTP/2 +HVER=2 +HVERCH=1.1 + +test -d $certdir || mkdir $certdir + +cat < $cfg + + $cfg + ietf-netconf:startup + ${YANG_INSTALLDIR} + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/cli + $APPNAME + $dir + false + $dir/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + + true + client-certificate + false + $srvcert + $srvkey + $cacert + 1 + + default + + + + + +
127.0.0.1
+ 4336 + true +
+ + default +
0.0.0.0
+ 443 + true +
+
+ + false + kw-nokey + false + + include clixon-example + enable + clixon-example + + + include ietf-netconf-server + enable + ietf-netconf-server + + + include ietf-keystore + enable + ietf-keystore + + + include ietf-truststore + enable + ietf-truststore + + +
+EOF + +cat < $fyang +module clixon-example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + /* Generic config data */ + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } +} +EOF + +cat < $clispec +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; +CLICON_PLUGIN="example_cli"; + +# Autocli syntax tree operations +set @datamodel, cli_auto_set(); +merge @datamodel, cli_auto_merge(); +create @datamodel, cli_auto_create(); +delete("Delete a configuration item") @datamodel, cli_auto_del(); +validate("Validate changes"), cli_validate(); +commit("Commit the changes"), cli_commit(); +quit("Quit"), cli_quit(); +show("Show a particular state of the system"){ + configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "text", true, false);{ + xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", true, false); + cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", false, false, "set "); + netconf("Show configuration as netconf edit-config operation"), cli_auto_show("datamodel", "candidate", "netconf", false, false); + text("Show configuration as text"), cli_auto_show("datamodel", "candidate", "text", false, false); + json("Show configuration as JSON"), cli_auto_show("datamodel", "candidate", "json", false, false); + } + state("Show configuration and state"), cli_auto_show("datamodel", "running", "xml", false, true); +} +EOF + +# Create server certs +cacerts $cakey $cacert +servercerts $cakey $cacert $srvkey $srvcert + +# Create client certs + +for name in $users; do + cat< $dir/$name.cnf +[req] +prompt = no +distinguished_name = dn +[dn] +CN = $name # This can be verified using SSL_set1_host +emailAddress = $name@foo.bar +O = Clixon +L = Stockholm +C = SE +EOF + # Create client key + openssl genpkey -algorithm RSA -out "$certdir/$name.key" || err "Generate client key" + + # Generate CSR (signing request) + openssl req -new -config $dir/$name.cnf -key $certdir/$name.key -out $certdir/$name.csr + + # Sign by CA + openssl x509 -req -extfile $dir/$name.cnf -days 7 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt || err "Generate signing client cert" +done + +# hardcoded to andy +cat < $dir/startup_db +<${DATASTORE_TOP}> + +EOF + +# Callhome request from client +cat < $dir/data +GET /restconf/data HTTP/$HVERCH +Host: localhost +Accept: application/yang-data+xml + +EOF + +new "test params: -f $cfg" +# Bring your own backend +if [ $BE -ne 0 ]; then + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "wait backend" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + stop_restconf_pre + + new "start restconf daemon" + start_restconf -f $cfg +fi + +new "wait restconf" +wait_restconf + +new "restconf Add init data" +expectpart "$(curl $CURLOPTS --key $certdir/andy.key --cert $certdir/andy.crt -X POST -H "Accept: application/yang-data+json" -H "Content-Type: application/yang-data+json" -d '{"clixon-example:table":{"parameter":{"name":"x","value":"foo"}}}' $RCPROTO://127.0.0.1/restconf/data)" 0 "HTTP/$HVER 201" + +new "Send GET via callhome client" +expectpart "$(${clixon_restconf_callhome_client} -D $DBG -f $dir/data -a 127.0.0.1 -c $srvcert -k $srvkey -C $cacert)" 0 "HTTP/$HVERCH 200 OK" "Content-Type: application/yang-data+xml" + +# Kill old +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir + +new "endtest" +endtest diff --git a/util/Makefile.in b/util/Makefile.in index e53760a5..b716490b 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -97,6 +97,7 @@ APPSRC += clixon_netconf_ssh_callhome.c APPSRC += clixon_netconf_ssh_callhome_client.c ifdef with_restconf APPSRC += clixon_util_stream.c # Needs curl +APPSRC += clixon_restconf_callhome_client.c endif ifdef with_http2 APPSRC += clixon_util_ssl.c # requires http/2 @@ -156,13 +157,13 @@ ifdef with_restconf clixon_util_stream: clixon_util_stream.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@ -clixon_util_ssl: clixon_util_ssl.c $(LIBDEPS) - $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ - -clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS) +clixon_restconf_callhome_client: clixon_restconf_callhome_client.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ endif +#clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS) +# $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ + distclean: clean rm -f Makefile *~ .depend diff --git a/util/clixon_netconf_ssh_callhome.c b/util/clixon_netconf_ssh_callhome.c index 2ddcc2d5..7e1b478a 100644 --- a/util/clixon_netconf_ssh_callhome.c +++ b/util/clixon_netconf_ssh_callhome.c @@ -105,6 +105,39 @@ callhome_connect(struct sockaddr *sa, return retval; } +/* @see clixon_inet2sin */ +static int +inet2sin(const char *addrtype, + const char *addrstr, + uint16_t port, + struct sockaddr *sa, + size_t *sa_len) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (strcmp(addrtype, "inet:ipv6-address") == 0) { + sin6 = (struct sockaddr_in6 *)sa; + *sa_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = htons(port); + sin6->sin6_family = AF_INET6; + inet_pton(AF_INET6, addrstr, &sin6->sin6_addr); + } + else if (strcmp(addrtype, "inet:ipv4-address") == 0) { + sin = (struct sockaddr_in *)sa; + *sa_len = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_port = htons(port); + sin->sin_addr.s_addr = inet_addr(addrstr); + } + else{ + fprintf(stderr, "Unexpected addrtype: %s\n", addrtype); + return -1; + } + return 0; +} + + static int ssh_server_exec(int s, char *sshdbin, @@ -220,10 +253,8 @@ main(int argc, int c; char *family = "ipv4"; char *addr = NULL; - struct sockaddr *sa; - struct sockaddr_in6 sin6 = { 0 }; - struct sockaddr_in sin = { 0 }; - size_t sin_len; + struct sockaddr sa = {0, }; + size_t sa_len; int dbg = 0; uint16_t port = NETCONF_CH_SSH; int s = -1; @@ -273,6 +304,10 @@ main(int argc, usage(argv[0]); goto done; } +#if 1 + if (inet2sin(family, addr, port, &sa, &sa_len) < 0) + goto done; +#else if (strcmp(family, "ipv6") == 0){ sin_len = sizeof(struct sockaddr_in6); sin6.sin6_port = htons(port); @@ -291,7 +326,8 @@ main(int argc, fprintf(stderr, "-f <%s> is invalid family\n", family); goto done; } - if (callhome_connect(sa, sin_len, &s) < 0) +#endif + if (callhome_connect(&sa, sa_len, &s) < 0) goto done; /* For some reason this sshd returns -1 which is unclear why */ if (ssh_server_exec(s, sshdbin, sshdconfigfile, clixonconfigfile, dbg) < 0) diff --git a/util/clixon_restconf_callhome_client.c b/util/clixon_restconf_callhome_client.c new file mode 100644 index 00000000..c3cf14a9 --- /dev/null +++ b/util/clixon_restconf_callhome_client.c @@ -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 +#include +#include +#include +#include +#include +#include +#include /* gethostbyname */ +#include /* inet_pton */ +#include /* TCP_NODELAY */ + +#include + +/* cligen */ +#include + +/* 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 \tDebug\n" + "\t-f \tHHTP input file (overrides stdin)\n" + "\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n" + "\t-a \tIP address (eg 1.2.3.4) - mandatory\n" + "\t-p \tPort (default %d)\n" + "\t-c \tcert\n" + "\t-C \tcacert\n" + "\t-k \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 and -k -C 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 is invalid\n"); + usage(argv[0]); + goto done; + } + if (addr == NULL){ + fprintf(stderr, "-a 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; +} diff --git a/yang/clixon/Makefile.in b/yang/clixon/Makefile.in index 2f66b2e6..a39d498d 100644 --- a/yang/clixon/Makefile.in +++ b/yang/clixon/Makefile.in @@ -46,7 +46,7 @@ YANGSPECS = clixon-config@2022-03-21.yang # 5.7 YANGSPECS += clixon-lib@2021-12-05.yang # 5.5 YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang -YANGSPECS += clixon-restconf@2022-03-21.yang # 5.7 +YANGSPECS += clixon-restconf@2022-08-01.yang # 5.9 YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6 all: diff --git a/yang/clixon/clixon-restconf@2021-05-20.yang b/yang/clixon/clixon-restconf@2022-08-01.yang similarity index 80% rename from yang/clixon/clixon-restconf@2021-05-20.yang rename to yang/clixon/clixon-restconf@2022-08-01.yang index 53cf34cc..2b85634f 100644 --- a/yang/clixon/clixon-restconf@2021-05-20.yang +++ b/yang/clixon/clixon-restconf@2022-08-01.yang @@ -26,7 +26,7 @@ module clixon-restconf { in clixon-config ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON @@ -53,6 +53,17 @@ module clixon-restconf { ***** END LICENSE BLOCK *****"; + revision 2022-08-01 { + description + "Added socket/call-home container + Released in Clixon 5.9"; + } + revision 2022-03-21 { + description + "Added feature: + http-data - Limited static http server + Released in Clixon 5.7"; + } revision 2021-05-20 { description "Added log-destination for restconf @@ -74,7 +85,6 @@ module clixon-restconf { description "Initial release"; } - feature fcgi { description "This feature indicates that the restconf server supports the fast-cgi reverse @@ -89,6 +99,19 @@ module clixon-restconf { "This feature allows the use of authentication-type none."; } + feature http-data { + description + "This feature allows for a very limited static http-data function as + addition to RESTCONF. + It is limited to: + 1. path: Local static files within WWW_DATA_ROOT + 2. operation GET, HEAD, OPTIONS + 3. query parameters not supported + 4. indata should be NULL (no write operations) + 5. Limited media: text/html, JavaScript, image, and css + 6. Authentication as restconf + 7. HTTP/1+2, TLS as restconf"; + } typedef http-auth-type { type enumeration { enum none { @@ -140,6 +163,14 @@ module clixon-restconf { For example, if the restconf daemon is under systemd management, the restconf daemon will only start if enable=true."; } + leaf enable-http-data { + type boolean; + default "false"; + if-feature "http-data"; + description + "Enables Limited static http-data functionality. + enable must be true for this option to be meaningful."; + } leaf auth-type { type http-auth-type; description @@ -240,6 +271,38 @@ module clixon-restconf { default true; description "Enable for HTTPS otherwise HTTP protocol"; } + /* Some of this in-lined from ietf-restconf-server@2022-05-24.yang */ + container call-home { + presence + "Identifies that the server has been configured to initiate + call home connections. + If set, address/port refers to destination."; + description + "See RFC 8071 NETCONF Call Home and RESTCONF Call Home"; + container connection-type { + description + "Indicates the RESTCONF server's preference for how the + RESTCONF connection is maintained."; + choice connection-type { + mandatory true; + description + "Selects between available connection types."; + case persistent-connection { + container persistent { + presence + "Indicates that a persistent connection is to be + maintained."; + } + } + case periodic-connection { + container periodic { + presence + "Indicates periodic connects"; + } + } + } + } + } } } container restconf {