diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4cbc11..4d86ee98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,9 +49,9 @@ Expected: May 2022 * path: Local static files within `CLICON_WWW_DATA_ROOT` * operation GET, HEAD, or OPTIONS * query parameters not supported - 5. indata should be NULL (no write operations) - 6. Limited media: text/html, JavaScript, image, and css - 7. Authentication as restconf + * no indata + * media: html, css, js, fonts, image, + 7. Authentication, TLS, http/2 as restconf Generic changes: * Uniform path selection across fcgi, native http/1 + http/2 diff --git a/apps/restconf/clixon_http_data.c b/apps/restconf/clixon_http_data.c index b2b8f348..72249aed 100644 --- a/apps/restconf/clixon_http_data.c +++ b/apps/restconf/clixon_http_data.c @@ -87,7 +87,7 @@ static const map_str2str mime_map[] = { /*! Check if uri path denotes a data path * * @param[out] data Pointer to string where data starts if retval = 1 - * @retval 0 No, not a data path + * @retval 0 No, not a data path, or not enabled * @retval 1 Yes, a data path and "data" points to www-data if given */ int @@ -97,19 +97,21 @@ api_path_is_data(clicon_handle h, char *path; char *http_data_path; - if ((path = restconf_uripath(h)) == NULL) - return 0; - if ((http_data_path = clicon_option_str(h, "CLICON_HTTP_DATA_PATH")) == NULL) - return 0; - if (strlen(path) < strlen(http_data_path)) - return 0; - if (path[0] != '/') - return 0; - if (strncmp(path, http_data_path, strlen(http_data_path)) != 0) - return 0; - if (data) - *data = path + strlen(http_data_path); - return 1; + if (restconf_http_data_get(h) == 0) + return 0; + if ((path = restconf_uripath(h)) == NULL) + return 0; + if ((http_data_path = clicon_option_str(h, "CLICON_HTTP_DATA_PATH")) == NULL) + return 0; + if (strlen(path) < strlen(http_data_path)) + return 0; + if (path[0] != '/') + return 0; + if (strncmp(path, http_data_path, strlen(http_data_path)) != 0) + return 0; + if (data) + *data = path + strlen(http_data_path); + return 1; } /*! Generic restconf error function on get/head request diff --git a/apps/restconf/restconf_handle.c b/apps/restconf/restconf_handle.c index 21c9843f..65d6ca53 100644 --- a/apps/restconf/restconf_handle.c +++ b/apps/restconf/restconf_handle.c @@ -90,9 +90,10 @@ struct restconf_handle { event_stream_t *rh_stream; /* notification streams, see clixon_stream.[ch] */ /* ------ end of common handle ------ */ - clicon_hash_t *rh_params; /* restconf parameters, including http headers */ - clixon_auth_type_t rh_auth_type; /* authentication type */ - int rh_pretty; /* pretty-print for http replies */ + clicon_hash_t *rh_params; /* restconf parameters, including http headers */ + clixon_auth_type_t rh_auth_type; /* authentication type */ + int rh_pretty; /* pretty-print for http replies */ + int rh_http_data; /* enable-http-data (and if-feature http-data) */ char *rh_fcgi_socket; /* if-feature fcgi, XXX: use WITH_RESTCONF_FCGI ? */ }; @@ -228,12 +229,10 @@ restconf_pretty_get(clicon_handle h) } /*! Set restconf pretty-print - * @param[in] h Clicon handle - * @param[in] name Data name - * @param[in] val Data value as null-terminated string - * @retval 0 OK - * @retval -1 Error - * Currently using clixon runtime data but there is risk for colliding names + * @param[in] h Clicon handle + * @param[in] pretty 0 or 1 + * @retval 0 OK + * @retval -1 Error */ int restconf_pretty_set(clicon_handle h, @@ -245,6 +244,34 @@ restconf_pretty_set(clicon_handle h, return 0; } +/*! Get restconf http-data + * @param[in] h Clixon handle + * @retval 0 Yes, http-data enabled + * @retval 1 No, http-data disabled + */ +int +restconf_http_data_get(clicon_handle h) +{ + struct restconf_handle *rh = handle(h); + + return rh->rh_http_data; +} + +/*! Set restconf http-data + * @param[in] h Clixon handle + * @retval 0 OK + * @retval -1 Error + */ +int +restconf_http_data_set(clicon_handle h, + int http_data) +{ + struct restconf_handle *rh = handle(h); + + rh->rh_http_data = http_data; + return 0; +} + /*! Get restconf fcgi socket path * @param[in] h Clicon handle * @retval socketpath diff --git a/apps/restconf/restconf_handle.h b/apps/restconf/restconf_handle.h index 52fc9fc1..9dee70be 100644 --- a/apps/restconf/restconf_handle.h +++ b/apps/restconf/restconf_handle.h @@ -51,6 +51,8 @@ clixon_auth_type_t restconf_auth_type_get(clicon_handle h); int restconf_auth_type_set(clicon_handle h, clixon_auth_type_t type); int restconf_pretty_get(clicon_handle h); int restconf_pretty_set(clicon_handle h, int pretty); +int restconf_http_data_get(clicon_handle h); +int restconf_http_data_set(clicon_handle h, int http_data); char *restconf_fcgi_socket_get(clicon_handle h); int restconf_fcgi_socket_set(clicon_handle h, char *socketpath); diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 1eaf49d8..2980261d 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -786,6 +786,13 @@ restconf_config_init(clicon_handle h, else if (strcmp(bstr, "false") == 0) restconf_pretty_set(h, 0); } + if ((x = xpath_first(xrestconf, nsc, "enable-http-data")) != NULL && + (bstr = xml_body(x)) != NULL){ + if (strcmp(bstr, "true") == 0) + restconf_http_data_set(h, 1); + else if (strcmp(bstr, "false") == 0) + restconf_http_data_set(h, 0); + } if ((x = xpath_first(xrestconf, nsc, "fcgi-socket")) != NULL && (bstr = xml_body(x)) != NULL){ if (restconf_fcgi_socket_set(h, bstr) < 0) diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 008482d0..059c50b2 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -479,8 +479,10 @@ http2_exec(restconf_conn *rc, if (restconf_nghttp2_path(sd) < 0) goto done; } - else + else{ + sd->sd_code = 400; ; /* ignore */ + } /* If body, add a content-length header * A server MUST NOT send a Content-Length header field in any response * with a status code of 1xx (Informational) or 204 (No Content). A diff --git a/test/config.sh.in b/test/config.sh.in index 368610fe..6823eff3 100755 --- a/test/config.sh.in +++ b/test/config.sh.in @@ -71,8 +71,8 @@ DATASTORE_TOP="config" # clixon yang revisions occuring in tests (see eg yang/clixon/Makefile.in) CLIXON_AUTOCLI_REV="2022-02-11" CLIXON_LIB_REV="2021-12-05" -CLIXON_CONFIG_REV="2022-02-11" -CLIXON_RESTCONF_REV="2021-05-20" +CLIXON_CONFIG_REV="2022-03-21" +CLIXON_RESTCONF_REV="2022-03-21" CLIXON_EXAMPLE_REV="2020-12-01" # Length of TSL RSA key diff --git a/test/lib.sh b/test/lib.sh index 4e21bfbe..c4a2a771 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -228,6 +228,8 @@ fi # Args: # 1: auth-type (one of none, client-cert, user) # 2: pretty (if true pretty-print restconf return values) +# [3: proto: http or https] +# [4: http_data: true or false] # Note feature http-data must be enabled # Note, if AUTH=none then FEATURE clixon-restconf:allow-auth-none must be enabled # Note if https, check if server cert/key exists, if not generate them function restconf_config() @@ -235,26 +237,42 @@ function restconf_config() AUTH=$1 PRETTY=$2 - if [ false -a ${WITH_RESTCONF} = "fcgi" ]; then - echo "clixon-restconf:fcgitrue$AUTH$PRETTY$DBG" + # Change this to fixed parameters + if [ $# -gt 2 ]; then + proto=$3 else - FEATURES="clixon-restconf:fcgi" - if [ $RCPROTO = http ]; then - echo "${FEATURES}true$AUTH$PRETTY$DBGdefault
0.0.0.0
80false
" - else - certdir=$dir/certs - if [ ! -f ${dir}/clixon-server-crt.pem ]; then - certdir=$dir/certs - test -d $certdir || mkdir $certdir - srvcert=${certdir}/clixon-server-crt.pem - srvkey=${certdir}/clixon-server-key.pem - cacert=${certdir}/clixon-ca-crt.pem - cakey=${certdir}/clixon-ca-key.pem - cacerts $cakey $cacert - servercerts $cakey $cacert $srvkey $srvcert - fi - echo "${FEATURES}true$AUTH$PRETTY${certdir}/clixon-server-crt.pem${certdir}/clixon-server-key.pem${certdir}/clixon-ca-crt.pem$DBGdefault
0.0.0.0
443true
" + proto=$RCPROTO + fi + if [ $# -gt 3 ]; then + http_data=$4 + else + http_data=false + fi + + echo -n "clixon-restconf:fcgi" + if [ $proto = http ]; then + echo -n "true" + if ${http_data}; then + echo -n "true" fi + echo "$AUTH$PRETTY$DBGdefault
0.0.0.0
80false
" + else + certdir=$dir/certs + if [ ! -f ${dir}/clixon-server-crt.pem ]; then + certdir=$dir/certs + test -d $certdir || mkdir $certdir + srvcert=${certdir}/clixon-server-crt.pem + srvkey=${certdir}/clixon-server-key.pem + cacert=${certdir}/clixon-ca-crt.pem + cakey=${certdir}/clixon-ca-key.pem + cacerts $cakey $cacert + servercerts $cakey $cacert $srvkey $srvcert + fi + echo -n "true" + if ${http_data}; then + echo -n "true" + fi + echo "$AUTH$PRETTY${certdir}/clixon-server-crt.pem${certdir}/clixon-server-key.pem${certdir}/clixon-ca-crt.pem$DBGdefault
0.0.0.0
443true
" fi } diff --git a/test/test_http_data.sh b/test/test_http_data.sh index 717bc9e5..84612b13 100755 --- a/test/test_http_data.sh +++ b/test/test_http_data.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash -# Simple web data +# Simple http data test +# Create an html and css file +# Get them via http and https +# Send options and head request +# Errors: not found, post, +# XXX: feature disabled # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -7,9 +12,8 @@ s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi APPNAME=example cfg=$dir/conf.xml -wdir=$dir/www -rm -rf $wdir -mkdir $wdir +rm -rf $dir/www +mkdir $dir/www # Does not work with fcgi if [ "${WITH_RESTCONF}" = "fcgi" ]; then @@ -17,40 +21,8 @@ if [ "${WITH_RESTCONF}" = "fcgi" ]; then if [ "$s" = $0 ]; then exit 0; else return 0; fi fi -RESTCONFIG=$(restconf_config none false) - - -# Clixon config -cat < $cfg - - $cfg - clixon-restconf:allow-auth-none - clixon-restconf:http-data - ${YANG_INSTALLDIR} - $IETFRFC - $dir - /usr/local/lib/$APPNAME/clispec - /usr/local/lib/$APPNAME/backend - example_backend.so$ - /usr/local/lib/$APPNAME/restconf - /data - $wdir - /usr/local/lib/$APPNAME/cli - $APPNAME - /usr/local/var/$APPNAME/$APPNAME.sock - /usr/local/var/$APPNAME/$APPNAME.pidfile - /usr/local/var/$APPNAME - true - $RESTCONFIG - -EOF - -# Host setup: -# / -# /var/www/html - # Data file -cat < $wdir/index.html +cat < $dir/www/index.html @@ -71,7 +43,7 @@ working. Further configuration is required.

EOF -cat < $wdir/example.css +cat < $dir/www/example.css img { display: inline; border: @@ -95,62 +67,138 @@ h1,h2,h3,h4,h5,h6 { } EOF - -new "test params: -f $cfg" -if [ $BE -ne 0 ]; then - new "kill old backend" - sudo clixon_backend -zf $cfg - if [ $? -ne 0 ]; then - err - fi - sudo pkill -f clixon_backend # to be sure +# Http test routine with arguments: +# 1. proto:http/https +function testrun() +{ + proto=$1 # http/https + enable=$2 # true/false - new "start backend -s init -f $cfg" - start_backend -s init -f $cfg -fi + RESTCONFIG=$(restconf_config none false $proto $enable) -new "wait backend" -wait_backend + datapath=/data + wdir=$dir/www +# Host setup: +# datapath=/ +# wdir=/var/www/html -if [ $RC -ne 0 ]; then - new "kill old restconf daemon" - stop_restconf_pre + # Clixon config + cat < $cfg + + $cfg + clixon-restconf:allow-auth-none + clixon-restconf:http-data + ${YANG_INSTALLDIR} + $IETFRFC + $dir + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/restconf + $datapath + $wdir + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + true + $RESTCONFIG + +EOF - new "start restconf daemon" - start_restconf -f $cfg -fi - -new "wait restconf" -wait_restconf - -new "WWW get html" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $RCPROTO://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "Welcome to Clixon!" - -new "WWW get css" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $RCPROTO://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html" - -new "WWW head" -expectpart "$(curl $CURLOPTS --head -H 'Accept: text/html' $RCPROTO://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" --not-- "Welcome to Clixon!" - -new "WWW options" -expectpart "$(curl $CURLOPTS -X OPTIONS $RCPROTO://localhost/data/index.html)" 0 "HTTP/$HVER 200" "allow: OPTIONS,HEAD,GET" - -new "WWW get http not found" -expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $RCPROTO://localhost/data/notfound.html)" 0 "HTTP/$HVER 404" "Content-Type: text/html" "404 Not Found" - -new "WWW post not allowed" -expectpart "$(curl $CURLOPTS -X POST -H 'Accept: text/html' -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $RCPROTO://localhost/data/notfound.html)" 0 "HTTP/$HVER 405" "Content-Type: text/html" "405 Method Not Allowed" - -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" + new "test params: -f $cfg" + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + sudo pkill -f clixon_backend # to be sure + + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg fi - # kill backend - stop_backend -f $cfg + + 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 $proto + +# echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html" + if $enable; then + new "WWW get html" + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "Welcome to Clixon!" + else + new "WWW get html, not enabled, expect bad request" + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 400" + return + fi + + new "WWW get css" + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html" + + new "WWW head" + expectpart "$(curl $CURLOPTS --head -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" --not-- "Welcome to Clixon!" + + new "WWW options" + expectpart "$(curl $CURLOPTS -X OPTIONS $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "allow: OPTIONS,HEAD,GET" + + # negative errors + new "WWW get http not found" + expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 404" "Content-Type: text/html" "404 Not Found" + + new "WWW post not allowed" + expectpart "$(curl $CURLOPTS -X POST -H 'Accept: text/html' -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 405" "Content-Type: text/html" "405 Method Not Allowed" + + 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 +} + +protos= +# Go thru all combinations of IPv4/IPv6, http/https, local/backend config +if [ "${WITH_RESTCONF}" = "fcgi" ]; then + protos="http" +elif ${HAVE_HTTP1}; then + protos="http" # No plain http for http/2 only fi +if [ "${WITH_RESTCONF}" = "native" ]; then + # https only relevant for internal (for fcgi: need nginx config) + protos="$protos https" +fi + +for proto in $protos; do + for enable in true false; do + new "http-data proto:$proto enabled:$enable" + testrun $proto $enable + done +done + +# unset conditional parameters +unset RCPROTO +unset RESTCONFIG rm -rf $dir diff --git a/test/test_restconf.sh b/test/test_restconf.sh index d550b07d..38b79478 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -534,6 +534,7 @@ function testrun() fi } +protos= # Go thru all combinations of IPv4/IPv6, http/https, local/backend config if [ "${WITH_RESTCONF}" = "fcgi" ]; then protos="http" @@ -541,7 +542,7 @@ elif ${HAVE_HTTP1}; then protos="http" # No plain http for http/2 only fi if [ "${WITH_RESTCONF}" = "native" ]; then - # http only relevant for internal (for fcgi: need nginx config) + # https only relevant for internal (for fcgi: need nginx config) protos="$protos https" fi for proto in $protos; do