* Restconf native http/1, first working version
* Renamed files clixon_http -> restconf_http * Split main file into restconf_native.c * Remove all evhtp code and libevhtp/libevent dependency
This commit is contained in:
parent
dadf4a778a
commit
4aa74fa1d8
27 changed files with 1291 additions and 789 deletions
|
|
@ -100,9 +100,9 @@ APPSRC += restconf_methods_post.c
|
||||||
APPSRC += restconf_methods_get.c
|
APPSRC += restconf_methods_get.c
|
||||||
APPSRC += restconf_methods_patch.c
|
APPSRC += restconf_methods_patch.c
|
||||||
APPSRC += restconf_root.c
|
APPSRC += restconf_root.c
|
||||||
APPSRC += clixon_http1.c
|
|
||||||
APPSRC += restconf_main_$(with_restconf).c
|
APPSRC += restconf_main_$(with_restconf).c
|
||||||
ifeq ($(with_restconf),native)
|
ifeq ($(with_restconf),native)
|
||||||
|
APPSRC += restconf_http1.c
|
||||||
APPSRC += restconf_native.c
|
APPSRC += restconf_native.c
|
||||||
APPSRC += restconf_nghttp2.c # HTTP/2
|
APPSRC += restconf_nghttp2.c # HTTP/2
|
||||||
endif
|
endif
|
||||||
|
|
@ -128,8 +128,6 @@ LIBSRC += restconf_api_$(with_restconf).c
|
||||||
|
|
||||||
LIBOBJ = $(LIBSRC:.c=.o)
|
LIBOBJ = $(LIBSRC:.c=.o)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# This lib is very small but used for clixon restconf applications to access clixon restconf lib
|
# This lib is very small but used for clixon restconf applications to access clixon restconf lib
|
||||||
# functions. Mostly for future use
|
# functions. Mostly for future use
|
||||||
MYNAME = clixon_restconf
|
MYNAME = clixon_restconf
|
||||||
|
|
|
||||||
|
|
@ -1,248 +0,0 @@
|
||||||
/*
|
|
||||||
*
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
|
||||||
|
|
||||||
Copyright (C) 2020-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 *****
|
|
||||||
|
|
||||||
* HTTP/1.1 parser according to RFC 7230
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include "clixon_config.h" /* generated by config & autoconf */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <syslog.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
#include <nghttp2/nghttp2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* cligen */
|
|
||||||
#include <cligen/cligen.h>
|
|
||||||
|
|
||||||
/* clixon */
|
|
||||||
#include <clixon/clixon.h>
|
|
||||||
|
|
||||||
#include "restconf_lib.h"
|
|
||||||
#include "restconf_native.h"
|
|
||||||
#include "clixon_http1_parse.h"
|
|
||||||
|
|
||||||
/* Size of xml read buffer */
|
|
||||||
#define BUFLEN 1024
|
|
||||||
|
|
||||||
static int
|
|
||||||
_http1_parse(clicon_handle h,
|
|
||||||
restconf_conn *rc,
|
|
||||||
char *str,
|
|
||||||
const char *filename)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
clixon_http1_yacc hy = {0,};
|
|
||||||
char *ptr;
|
|
||||||
size_t sz;
|
|
||||||
|
|
||||||
clicon_debug(2, "%s", __FUNCTION__);
|
|
||||||
if (strlen(str) == 0)
|
|
||||||
goto ok;
|
|
||||||
hy.hy_parse_string = str;
|
|
||||||
hy.hy_name = filename;
|
|
||||||
hy.hy_h = h;
|
|
||||||
hy.hy_rc = rc;
|
|
||||||
hy.hy_linenum = 1;
|
|
||||||
if (http1_scan_init(&hy) < 0)
|
|
||||||
goto done;
|
|
||||||
if (http1_parse_init(&hy) < 0)
|
|
||||||
goto done;
|
|
||||||
ptr = clixon_http1_parsetext;
|
|
||||||
if (clixon_http1_parseparse(&hy) != 0) { /* yacc returns 1 on error */
|
|
||||||
if (filename)
|
|
||||||
clicon_log(LOG_NOTICE, "HTTP1 error: on line %d in %s", hy.hy_linenum, filename);
|
|
||||||
else
|
|
||||||
clicon_log(LOG_NOTICE, "HTTP1 error: on line %d", hy.hy_linenum);
|
|
||||||
if (clicon_errno == 0)
|
|
||||||
clicon_err(OE_RESTCONF, 0, "HTTP1 parser error with no error code (should not happen)");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (0){
|
|
||||||
sz = (clixon_http1_parsetext - ptr) + strlen(clixon_http1_parsetext);
|
|
||||||
fprintf(stderr,"%s %p diff:%ld %ld\n", __FUNCTION__,
|
|
||||||
clixon_http1_parsetext,
|
|
||||||
sz,
|
|
||||||
strlen(ptr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
http1_parse_exit(&hy);
|
|
||||||
http1_scan_exit(&hy);
|
|
||||||
ok:
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Read an XML definition from file and parse it into a parse-tree, advanced API
|
|
||||||
*
|
|
||||||
* @param[in] fd A file descriptor containing the XML file (as ASCII characters)
|
|
||||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
|
||||||
* @param[in] yspec Yang specification (only if bind is TOP or CONFIG)
|
|
||||||
* @param[in,out] xt Pointer to XML parse tree. If empty, create.
|
|
||||||
* @param[out] xerr Pointer to XML error tree, if retval is 0
|
|
||||||
* @retval 1 Parse OK and all yang assignment made
|
|
||||||
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
|
|
||||||
* @retval -1 Error with clicon_err called. Includes parse error
|
|
||||||
*
|
|
||||||
* @code
|
|
||||||
* cxobj *xt = NULL;
|
|
||||||
* cxobj *xerr = NULL;
|
|
||||||
* FILE *f;
|
|
||||||
* if ((f = fopen(filename, "r")) == NULL)
|
|
||||||
* err;
|
|
||||||
* if ((ret = clixon_xml_parse_file(f, YB_MODULE, yspec, &xt, &xerr)) < 0)
|
|
||||||
* err;
|
|
||||||
* xml_free(xt);
|
|
||||||
* @endcode
|
|
||||||
* @see clixon_xml_parse_string
|
|
||||||
* @see clixon_json_parse_file
|
|
||||||
* @note, If xt empty, a top-level symbol will be added so that <tree../> will be: <top><tree.../></tree></top>
|
|
||||||
* @note May block on file I/O
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
clixon_http1_parse_file(clicon_handle h,
|
|
||||||
restconf_conn *rc,
|
|
||||||
FILE *f,
|
|
||||||
const char *filename)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
int ret;
|
|
||||||
char ch;
|
|
||||||
char *buf = NULL;
|
|
||||||
char *ptr;
|
|
||||||
int buflen = BUFLEN; /* start size */
|
|
||||||
int len = 0;
|
|
||||||
int oldbuflen;
|
|
||||||
|
|
||||||
clicon_debug(1, "%s %s", __FUNCTION__, filename);
|
|
||||||
if (f == NULL){
|
|
||||||
clicon_err(OE_RESTCONF, EINVAL, "f is NULL");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if ((buf = malloc(buflen)) == NULL){
|
|
||||||
clicon_err(OE_XML, errno, "malloc");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
memset(buf, 0, buflen);
|
|
||||||
ptr = buf;
|
|
||||||
while (1){
|
|
||||||
if ((ret = fread(&ch, 1, 1, f)) < 0){
|
|
||||||
clicon_err(OE_XML, errno, "read");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ret != 0){
|
|
||||||
buf[len++] = ch;
|
|
||||||
}
|
|
||||||
if (ret == 0) { /* buffer read */
|
|
||||||
if (_http1_parse(h, rc, ptr, filename) < 0)
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (len >= buflen-1){ /* Space: one for the null character */
|
|
||||||
oldbuflen = buflen;
|
|
||||||
buflen *= 2;
|
|
||||||
if ((buf = realloc(buf, buflen)) == NULL){
|
|
||||||
clicon_err(OE_XML, errno, "realloc");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
memset(buf+oldbuflen, 0, buflen-oldbuflen);
|
|
||||||
ptr = buf;
|
|
||||||
}
|
|
||||||
} /* while */
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
if (buf)
|
|
||||||
free(buf);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
clixon_http1_parse_string(clicon_handle h,
|
|
||||||
restconf_conn *rc,
|
|
||||||
char *str)
|
|
||||||
{
|
|
||||||
return _http1_parse(h, rc, str, "http1-parse");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Convert buffer to null-terminated string
|
|
||||||
* I dont know how to do this without copying, OR
|
|
||||||
* input flex with a non-null terminated string
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
clixon_http1_parse_buf(clicon_handle h,
|
|
||||||
restconf_conn *rc,
|
|
||||||
char *buf,
|
|
||||||
size_t n)
|
|
||||||
{
|
|
||||||
char *str = NULL;
|
|
||||||
|
|
||||||
if ((str = malloc(n+1)) == NULL){
|
|
||||||
clicon_err(OE_RESTCONF, errno, "malloc");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
memcpy(str, buf, n);
|
|
||||||
str[n] = '\0';
|
|
||||||
return _http1_parse(h, rc, str, "http1-parse");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* @param[in] h Clixon handle
|
|
||||||
* @param[in] rc Clixon request connect pointer
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
restconf_http1_path_root(clicon_handle h,
|
|
||||||
restconf_conn *rc)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
restconf_stream_data *sd;
|
|
||||||
|
|
||||||
clicon_debug(1, "------------");
|
|
||||||
if ((sd = restconf_stream_find(rc, 0)) == NULL){
|
|
||||||
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
@ -46,7 +46,6 @@ struct clixon_http1_yacc {
|
||||||
int hy_linenum; /* Number of \n in parsed buffer */
|
int hy_linenum; /* Number of \n in parsed buffer */
|
||||||
char *hy_parse_string; /* original (copy of) parse string */
|
char *hy_parse_string; /* original (copy of) parse string */
|
||||||
void *hy_lexbuf; /* internal parse buffer from lex */
|
void *hy_lexbuf; /* internal parse buffer from lex */
|
||||||
void *hy_top;
|
|
||||||
};
|
};
|
||||||
typedef struct clixon_http1_yacc clixon_http1_yacc;
|
typedef struct clixon_http1_yacc clixon_http1_yacc;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
|
@ -93,46 +94,57 @@ query [A-Za-z0-9\-\._~!$&'()*+,;=:@?/]|%[0-9a-fA-F][0-9a-fA-F]
|
||||||
%x REQHTTP
|
%x REQHTTP
|
||||||
%x FLDNAME
|
%x FLDNAME
|
||||||
%x FLDVALUE
|
%x FLDVALUE
|
||||||
|
%x BODYM
|
||||||
|
|
||||||
%%
|
%%
|
||||||
<REQLINE,REQTARG,REQUERY,REQHTTP,FLDNAME,FLDVALUE><<EOF>> { return X_EOF; }
|
<REQLINE,REQTARG,REQUERY,REQHTTP,FLDNAME,FLDVALUE><<EOF>> { return X_EOF; }
|
||||||
<REQLINE>[ ] { BEGIN(REQTARG); return SP; }
|
<REQLINE>[ ] { BEGIN(REQTARG); return SP; }
|
||||||
<REQLINE>{token} { clixon_http1_parselval.string = yytext;
|
<REQLINE>{token} { clixon_http1_parselval.string = strdup(yytext);
|
||||||
return TOKEN; }
|
return TOKEN; }
|
||||||
<REQLINE>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
<REQLINE>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
<REQTARG>\? { BEGIN(REQUERY); return QMARK; }
|
<REQTARG>\? { BEGIN(REQUERY); return QMARK; }
|
||||||
<REQTARG>\/ { return SLASH; }
|
<REQTARG>\/ { return SLASH; }
|
||||||
<REQTARG>[ ] { return SP; }
|
<REQTARG>[ ] { BEGIN(REQHTTP); return SP; }
|
||||||
<REQTARG>HTTP { BEGIN(REQHTTP); return HTTP; }
|
<REQTARG>{pchar}+ { clixon_http1_parselval.string = yytext;
|
||||||
<REQUERY>{pchar}+ { clixon_http1_parselval.string = yytext;
|
|
||||||
return PCHARS; }
|
return PCHARS; }
|
||||||
<REQTARG>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
<REQTARG>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
<REQUERY>\/ { return SLASH; }
|
<REQUERY>\/ { return SLASH; }
|
||||||
<REQUERY>[ ] { return SP; }
|
<REQUERY>[ ] { BEGIN(REQHTTP); return SP; }
|
||||||
<REQUERY>HTTP { BEGIN(REQHTTP); return HTTP; }
|
<REQUERY>{query}+ { clixon_http1_parselval.string = strdup(yytext);
|
||||||
<REQUERY>{query}+ { clixon_http1_parselval.string = yytext;
|
|
||||||
return QUERY; }
|
return QUERY; }
|
||||||
<REQUERY>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
<REQUERY>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
<REQHTTP>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
<REQHTTP>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
||||||
<REQHTTP>\/ { return SLASH; }
|
<REQHTTP>\/ { return SLASH; }
|
||||||
<REQHTTP>\. { return DOT; }
|
<REQHTTP>\. { return DOT; }
|
||||||
|
<REQHTTP>HTTP { BEGIN(REQHTTP); return HTTP; }
|
||||||
<REQHTTP>[0-9] { clixon_http1_parselval.intval = atoi(yytext);
|
<REQHTTP>[0-9] { clixon_http1_parselval.intval = atoi(yytext);
|
||||||
return DIGIT; }
|
return DIGIT; }
|
||||||
<REQHTTP>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
<REQHTTP>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
<FLDNAME>: { BEGIN(FLDVALUE); return COLON; }
|
<FLDNAME>: { BEGIN(FLDVALUE); return COLON; }
|
||||||
<FLDNAME>\r\n { return CRLF; _HY->hy_linenum++; }
|
<FLDNAME>\r\n { BEGIN(BODYM); return CRLF; _HY->hy_linenum++; }
|
||||||
<FLDNAME>[ \t\n]+ { return RWS; }
|
<FLDNAME>[ \t]+ { return RWS; }
|
||||||
<FLDNAME>{token} { clixon_http1_parselval.string = yytext;
|
<FLDNAME>{token} { clixon_http1_parselval.string = strdup(yytext);
|
||||||
return TOKEN; }
|
return TOKEN; }
|
||||||
<FLDNAME>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
<FLDNAME>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
<FLDVALUE>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
<FLDVALUE>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
||||||
<FLDVALUE>[ \t\n]+ { return RWS; }
|
<FLDVALUE>[ \t]+ { return RWS; }
|
||||||
<FLDVALUE>. { clixon_http1_parselval.string = yytext;
|
<FLDVALUE>[^ \t\n\r]+ { clixon_http1_parselval.string = strdup(yytext);
|
||||||
return VCHAR; }
|
return VCHARS; }
|
||||||
|
<FLDVALUE>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||||
|
|
||||||
|
<BODYM>.+ { clixon_http1_parselval.string = strdup(yytext); /* note \n not . */
|
||||||
|
return BODY; }
|
||||||
|
<BODYM>\n { clixon_http1_parselval.string = strdup(yytext);
|
||||||
|
_HY->hy_linenum++;
|
||||||
|
return BODY; }
|
||||||
|
<BODYM><<EOF>> { return X_EOF; }
|
||||||
|
<BODYM>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return ERROR; }
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
/*! Initialize scanner.
|
/*! Initialize scanner.
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
***** END LICENSE BLOCK *****
|
***** END LICENSE BLOCK *****
|
||||||
|
|
||||||
* HTTP/1.1 parser according to RFC 7230 Appendix B
|
* HTTP/1.1 parser according to RFC 7230 Appendix B
|
||||||
|
* XXX field_values : field_values field_vchars : Only handle one field
|
||||||
*/
|
*/
|
||||||
|
|
||||||
%start http_message
|
%start http_message
|
||||||
|
|
@ -51,15 +52,20 @@
|
||||||
%token HTTP
|
%token HTTP
|
||||||
%token COLON
|
%token COLON
|
||||||
%token X_EOF
|
%token X_EOF
|
||||||
|
%token ERROR
|
||||||
|
|
||||||
%token <string> PCHARS
|
%token <string> PCHARS
|
||||||
%token <string> QUERY
|
%token <string> QUERY
|
||||||
%token <string> TOKEN
|
%token <string> TOKEN
|
||||||
%token <string> VCHAR
|
%token <string> VCHARS
|
||||||
|
%token <string> BODY
|
||||||
%token <intval> DIGIT
|
%token <intval> DIGIT
|
||||||
|
|
||||||
|
%type <string> body
|
||||||
%type <string> absolute_paths
|
%type <string> absolute_paths
|
||||||
%type <string> absolute_path
|
%type <string> absolute_path
|
||||||
|
%type <string> field_vchars
|
||||||
|
%type <string> field_values
|
||||||
|
|
||||||
%lex-param {void *_hy} /* Add this argument to parse() and lex() function */
|
%lex-param {void *_hy} /* Add this argument to parse() and lex() function */
|
||||||
%parse-param {void *_hy}
|
%parse-param {void *_hy}
|
||||||
|
|
@ -81,6 +87,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
|
@ -142,11 +149,50 @@ http1_parse_query(clixon_http1_yacc *hy,
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_stream_data *sd = NULL;
|
restconf_stream_data *sd = NULL;
|
||||||
|
|
||||||
if ((sd = restconf_stream_find(hy->hy_rc, 0)) == NULL)
|
clicon_debug(1, "%s: ?%s ", __FUNCTION__, query);
|
||||||
goto ok;
|
if ((sd = restconf_stream_find(hy->hy_rc, 0)) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, 0, "stream 0 not found");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
if (uri_str2cvec(query, '&', '=', 1, &sd->sd_qvec) < 0)
|
if (uri_str2cvec(query, '&', '=', 1, &sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
ok:
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http1_body(clixon_http1_yacc *hy,
|
||||||
|
char *body)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
restconf_stream_data *sd = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s: %s ", __FUNCTION__, body);
|
||||||
|
if ((sd = restconf_stream_find(hy->hy_rc, 0)) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, 0, "stream 0 not found");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (cbuf_append_buf(sd->sd_indata, body, strlen(body)) < 0){
|
||||||
|
clicon_err(OE_RESTCONF, errno, "cbuf_append_buf");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
http1_parse_header_field(clixon_http1_yacc *hy,
|
||||||
|
char *name,
|
||||||
|
char *field)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
|
||||||
|
if (restconf_convert_hdr(hy->hy_h, name, field) < 0)
|
||||||
|
goto done;
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -159,12 +205,32 @@ http1_parse_query(clixon_http1_yacc *hy,
|
||||||
/* start-line *( header-field CRLF ) CRLF [ message-body ]
|
/* start-line *( header-field CRLF ) CRLF [ message-body ]
|
||||||
* start-line = request-line / status-line (only request-line here, ignore status-line)
|
* start-line = request-line / status-line (only request-line here, ignore status-line)
|
||||||
*/
|
*/
|
||||||
http_message : request_line header_fields CRLF
|
http_message : request_line header_fields CRLF body
|
||||||
{ _HY->hy_top=NULL; _PARSE_DEBUG("http-message -> request-line header-fields ACCEPT"); YYACCEPT; }
|
{
|
||||||
|
if ($4) {
|
||||||
|
if (http1_body(_HY, $4) < 0) YYABORT;
|
||||||
|
free($4);
|
||||||
|
}
|
||||||
|
_PARSE_DEBUG("http-message -> request-line header-fields body");
|
||||||
|
YYACCEPT;
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* request-line = method SP request-target SP HTTP-version CRLF */
|
body : body BODY
|
||||||
request_line : method SP request_target SP HTTP_version CRLF
|
{
|
||||||
|
if (($$ = clixon_string_del_join($1, "", $2)) == NULL) {
|
||||||
|
free($2);
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
free($2);
|
||||||
|
_PARSE_DEBUG("body -> body BODY");
|
||||||
|
}
|
||||||
|
| ERROR { _PARSE_DEBUG("body -> ERROR"); YYABORT; /* shouldnt happen */ }
|
||||||
|
| { _PARSE_DEBUG("body -> "); $$ = NULL; }
|
||||||
|
;
|
||||||
|
|
||||||
|
/* request-line = method SP request-target SP HTTP-version CRLF */request_line : method SP request_target SP HTTP_version CRLF
|
||||||
{
|
{
|
||||||
_PARSE_DEBUG("request-line -> method request-target HTTP_version CRLF");
|
_PARSE_DEBUG("request-line -> method request-target HTTP_version CRLF");
|
||||||
}
|
}
|
||||||
|
|
@ -179,50 +245,46 @@ method : TOKEN
|
||||||
{
|
{
|
||||||
if (restconf_param_set(_HY->hy_h, "REQUEST_METHOD", $1) < 0)
|
if (restconf_param_set(_HY->hy_h, "REQUEST_METHOD", $1) < 0)
|
||||||
YYABORT;
|
YYABORT;
|
||||||
|
free($1);
|
||||||
_PARSE_DEBUG("method -> TOKEN");
|
_PARSE_DEBUG("method -> TOKEN");
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* request-target = origin-form / absolute-form / authority-form / asterisk-form *
|
/* request-target = origin-form / absolute-form / authority-form / asterisk-form *
|
||||||
* origin-form = absolute-path [ "?" query ] */
|
* origin-form = absolute-path [ "?" query ]
|
||||||
|
* query = <query, see [RFC3986], Section 3.4>
|
||||||
|
* query = *( pchar / "/" / "?" )
|
||||||
|
*/
|
||||||
request_target : absolute_paths
|
request_target : absolute_paths
|
||||||
{
|
{
|
||||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||||
YYABORT;
|
YYABORT;
|
||||||
|
free($1);
|
||||||
_PARSE_DEBUG("request-target -> absolute-paths");
|
_PARSE_DEBUG("request-target -> absolute-paths");
|
||||||
}
|
}
|
||||||
| absolute_paths QMARK QUERY
|
| absolute_paths QMARK QUERY
|
||||||
{
|
{
|
||||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||||
YYABORT;
|
YYABORT;
|
||||||
if (http1_parse_query(_HY->hy_h, $3) < 0)
|
free($1);
|
||||||
|
if (http1_parse_query(_HY, $3) < 0)
|
||||||
YYABORT;
|
YYABORT;
|
||||||
|
free($3);
|
||||||
_PARSE_DEBUG("request-target -> absolute-paths ? query");
|
_PARSE_DEBUG("request-target -> absolute-paths ? query");
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* query = <query, see [RFC3986], Section 3.4>
|
|
||||||
* query = *( pchar / "/" / "?" )
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
query : query query1 { _PARSE_DEBUG("query -> query1"); }
|
|
||||||
| { _PARSE_DEBUG("query -> "); }
|
|
||||||
;
|
|
||||||
|
|
||||||
query1 : PCHARS { _PARSE_DEBUG("query1 -> PCHARS"); }
|
|
||||||
| SLASH { _PARSE_DEBUG("query1 -> /"); }
|
|
||||||
| QMARK { _PARSE_DEBUG("query1 -> ?"); }
|
|
||||||
;
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* absolute-path = 1*( "/" segment ) */
|
/* absolute-path = 1*( "/" segment ) */
|
||||||
absolute_paths : absolute_paths absolute_path
|
absolute_paths : absolute_paths absolute_path
|
||||||
{
|
{
|
||||||
if (($$ = clixon_string_del_join($1, "/", $2)) == NULL) YYABORT;
|
if (($$ = clixon_string_del_join($1, "/", $2)) == NULL) { free($2); YYABORT;}
|
||||||
|
free($2);
|
||||||
_PARSE_DEBUG("absolute-paths -> absolute-paths absolute -path");
|
_PARSE_DEBUG("absolute-paths -> absolute-paths absolute -path");
|
||||||
}
|
}
|
||||||
| absolute_path
|
| absolute_path
|
||||||
{ $$ = strdup($1);
|
{
|
||||||
|
if (($$ = clixon_string_del_join(NULL, "/", $1)) == NULL) { free($1); YYABORT;}
|
||||||
|
free($1);
|
||||||
_PARSE_DEBUG("absolute-paths -> absolute -path");
|
_PARSE_DEBUG("absolute-paths -> absolute -path");
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
@ -236,11 +298,21 @@ absolute_paths : absolute_paths absolute_path
|
||||||
* / "*" / "+" / "," / ";" / "="
|
* / "*" / "+" / "," / ";" / "="
|
||||||
*/
|
*/
|
||||||
absolute_path : SLASH PCHARS
|
absolute_path : SLASH PCHARS
|
||||||
{ $$=$2; _PARSE_DEBUG("absolute-path -> PCHARS"); }
|
{
|
||||||
|
if (($$=strdup($2)) == NULL) YYABORT;
|
||||||
|
_PARSE_DEBUG("absolute-path -> PCHARS");
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
/* HTTP-version = HTTP-name "/" DIGIT "." DIGIT */
|
||||||
HTTP_version : HTTP SLASH DIGIT DOT DIGIT
|
HTTP_version : HTTP SLASH DIGIT DOT DIGIT
|
||||||
{ _PARSE_DEBUG("HTTP-version -> HTTP / DIGIT . DIGIT"); }
|
{
|
||||||
|
/* make sanity check later */
|
||||||
|
_HY->hy_rc->rc_proto_d1 = $3;
|
||||||
|
_HY->hy_rc->rc_proto_d2 = $5;
|
||||||
|
clicon_debug(1, "clixon_http1_parse: http/%d.%d", $3, $5);
|
||||||
|
_PARSE_DEBUG("HTTP-version -> HTTP / DIGIT . DIGIT");
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/*------------------------------------------ hdr fields
|
/*------------------------------------------ hdr fields
|
||||||
|
|
@ -250,29 +322,41 @@ header_fields : header_fields header_field CRLF
|
||||||
| { _PARSE_DEBUG("header-fields -> "); }
|
| { _PARSE_DEBUG("header-fields -> "); }
|
||||||
;
|
;
|
||||||
|
|
||||||
/* header-field = field-name ":" OWS field-value OWS */
|
/* header-field = field-name ":" OWS field-value OWS
|
||||||
header_field : field_name COLON ows field_values ows
|
field-name = token */
|
||||||
{ _PARSE_DEBUG("header-field -> field-name : field-values"); }
|
header_field : TOKEN COLON ows field_values ows
|
||||||
|
{
|
||||||
|
if (http1_parse_header_field(_HY, $1, $4) < 0)
|
||||||
|
YYABORT;
|
||||||
|
free($1);
|
||||||
|
free($4);
|
||||||
|
_PARSE_DEBUG("header-field -> field-name : field-values");
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* field-name = token */
|
/* field-value = *( field-content / obs-fold )
|
||||||
field_name : TOKEN { _PARSE_DEBUG("field-name -> TOKEN"); }
|
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||||
;
|
field-vchar = VCHAR / obs-text */
|
||||||
|
field_values : field_vchars
|
||||||
/* field-value = *( field-content / obs-fold ) */
|
{
|
||||||
field_values : field_values field_content
|
$$ = $1; // XXX is there more than one??
|
||||||
{ _PARSE_DEBUG("field-values -> field-values field-content"); }
|
_PARSE_DEBUG("field-values -> field-values field-vchars");
|
||||||
|
}
|
||||||
| { _PARSE_DEBUG("field-values -> "); }
|
| { _PARSE_DEBUG("field-values -> "); }
|
||||||
;
|
;
|
||||||
|
|
||||||
/* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] */
|
|
||||||
field_content : field_vchars { _PARSE_DEBUG("field-content -> field-vchars"); }
|
|
||||||
;
|
|
||||||
|
|
||||||
/* field-vchar = VCHAR / obs-text */
|
field_vchars : field_vchars RWS VCHARS
|
||||||
field_vchars : field_vchars RWS VCHAR
|
{
|
||||||
{ _PARSE_DEBUG("field-vchars -> field-vchars VCHAR"); }
|
if (($$ = clixon_string_del_join($1, " ", $3)) == NULL) YYABORT;
|
||||||
| VCHAR { _PARSE_DEBUG("field-vchars -> VCHAR"); }
|
free($3);
|
||||||
|
_PARSE_DEBUG("field-vchars -> field-vchars VCHARS");
|
||||||
|
}
|
||||||
|
| VCHARS
|
||||||
|
{
|
||||||
|
$$ = $1;
|
||||||
|
_PARSE_DEBUG("field-vchars -> VCHARS");
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
/* The OWS rule is used where zero or more linear whitespace octets
|
/* The OWS rule is used where zero or more linear whitespace octets
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ restconf_param_set(clicon_handle h,
|
||||||
{
|
{
|
||||||
struct restconf_handle *rh = handle(h);
|
struct restconf_handle *rh = handle(h);
|
||||||
|
|
||||||
clicon_debug(1, "%s=%s", param, val);
|
clicon_debug(1, "%s: %s=%s", __FUNCTION__, param, val);
|
||||||
if (rh->rh_params == NULL)
|
if (rh->rh_params == NULL)
|
||||||
if ((rh->rh_params = clicon_hash_init()) == NULL)
|
if ((rh->rh_params = clicon_hash_init()) == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
||||||
492
apps/restconf/restconf_http1.c
Normal file
492
apps/restconf/restconf_http1.c
Normal file
|
|
@ -0,0 +1,492 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright (C) 2020-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 *****
|
||||||
|
|
||||||
|
* HTTP/1.1 parser according to RFC 7230
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "clixon_config.h" /* generated by config & autoconf */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* cligen */
|
||||||
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
|
/* clixon */
|
||||||
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
|
#include "restconf_handle.h"
|
||||||
|
#include "restconf_lib.h"
|
||||||
|
#include "restconf_root.h"
|
||||||
|
#include "restconf_native.h"
|
||||||
|
#include "restconf_api.h"
|
||||||
|
#include "restconf_err.h"
|
||||||
|
#include "clixon_http1_parse.h"
|
||||||
|
#include "restconf_http1.h"
|
||||||
|
|
||||||
|
/* Size of xml read buffer */
|
||||||
|
#define BUFLEN 1024
|
||||||
|
|
||||||
|
/*! HTTP/1 parsing function. Input is string and side-effect is populating connection structs
|
||||||
|
*
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Restconf connection
|
||||||
|
* @param[in] str Pointer to string containing HTTP/1
|
||||||
|
* @param[in] filename Debug string identifying file or connection
|
||||||
|
* @retval 0 Parse OK
|
||||||
|
* @retval -1 Error with clicon_err called.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
_http1_parse(clicon_handle h,
|
||||||
|
restconf_conn *rc,
|
||||||
|
char *str,
|
||||||
|
const char *filename)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
clixon_http1_yacc hy = {0,};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s:\n%s", __FUNCTION__, str);
|
||||||
|
if (strlen(str) == 0)
|
||||||
|
goto ok;
|
||||||
|
hy.hy_parse_string = str;
|
||||||
|
hy.hy_name = filename;
|
||||||
|
hy.hy_h = h;
|
||||||
|
hy.hy_rc = rc;
|
||||||
|
hy.hy_linenum = 1;
|
||||||
|
if (http1_scan_init(&hy) < 0)
|
||||||
|
goto done;
|
||||||
|
if (http1_parse_init(&hy) < 0)
|
||||||
|
goto done;
|
||||||
|
ret = clixon_http1_parseparse(&hy); /* yacc returns 1 on error */
|
||||||
|
/* yacc/lex terminates parsing after headers.
|
||||||
|
* Look for body after headers assuming str terminating with \n\n\0 and then <body> */
|
||||||
|
http1_parse_exit(&hy);
|
||||||
|
http1_scan_exit(&hy);
|
||||||
|
if (ret != 0){
|
||||||
|
if (filename)
|
||||||
|
clicon_log(LOG_NOTICE, "HTTP1 error: on line %d in %s", hy.hy_linenum, filename);
|
||||||
|
else
|
||||||
|
clicon_log(LOG_NOTICE, "HTTP1 error: on line %d", hy.hy_linenum);
|
||||||
|
if (clicon_errno == 0)
|
||||||
|
clicon_err(OE_RESTCONF, 0, "HTTP1 parser error with no error code (should not happen)");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! HTTP/1 parsing function from file
|
||||||
|
*
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Restconf connection
|
||||||
|
* @param[in] f A file descriptor containing HTTP/1 (as ASCII characters)
|
||||||
|
* @param[in] filename Debug string identifying file or connection
|
||||||
|
* @retval 0 Parse OK
|
||||||
|
* @retval -1 Error with clicon_err called.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
clixon_http1_parse_file(clicon_handle h,
|
||||||
|
restconf_conn *rc,
|
||||||
|
FILE *f,
|
||||||
|
const char *filename)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
int ret;
|
||||||
|
char ch;
|
||||||
|
char *buf = NULL;
|
||||||
|
char *ptr;
|
||||||
|
int buflen = BUFLEN; /* start size */
|
||||||
|
int len = 0;
|
||||||
|
int oldbuflen;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s %s", __FUNCTION__, filename);
|
||||||
|
if (f == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, EINVAL, "f is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((buf = malloc(buflen)) == NULL){
|
||||||
|
clicon_err(OE_XML, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(buf, 0, buflen);
|
||||||
|
ptr = buf;
|
||||||
|
while (1){
|
||||||
|
if ((ret = fread(&ch, 1, 1, f)) < 0){
|
||||||
|
clicon_err(OE_XML, errno, "read");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ret != 0){
|
||||||
|
buf[len++] = ch;
|
||||||
|
}
|
||||||
|
if (ret == 0) { /* buffer read */
|
||||||
|
if (_http1_parse(h, rc, ptr, filename) < 0)
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (len >= buflen-1){ /* Space: one for the null character */
|
||||||
|
oldbuflen = buflen;
|
||||||
|
buflen *= 2;
|
||||||
|
if ((buf = realloc(buf, buflen)) == NULL){
|
||||||
|
clicon_err(OE_XML, errno, "realloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(buf+oldbuflen, 0, buflen-oldbuflen);
|
||||||
|
ptr = buf;
|
||||||
|
}
|
||||||
|
} /* while */
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (buf)
|
||||||
|
free(buf);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! HTTP/1 parsing function from string
|
||||||
|
*
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Restconf connection
|
||||||
|
* @param[in] str HTTP/1 string
|
||||||
|
* @retval 0 Parse OK
|
||||||
|
* @retval -1 Error with clicon_err called.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
clixon_http1_parse_string(clicon_handle h,
|
||||||
|
restconf_conn *rc,
|
||||||
|
char *str)
|
||||||
|
{
|
||||||
|
return _http1_parse(h, rc, str, "http1-parse");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! HTTP/1 parsing function from buffer (non-null terminated)
|
||||||
|
*
|
||||||
|
* Convert buffer to null-terminated string
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Restconf connection
|
||||||
|
* @param[in] buf HTTP/1 buffer
|
||||||
|
* @param[in] n Length of buffer
|
||||||
|
* @retval 0 Parse OK
|
||||||
|
* @retval -1 Error with clicon_err called.
|
||||||
|
* @note Had preferred to do this without copying, OR
|
||||||
|
* input flex with a non-null terminated string
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
clixon_http1_parse_buf(clicon_handle h,
|
||||||
|
restconf_conn *rc,
|
||||||
|
char *buf,
|
||||||
|
size_t n)
|
||||||
|
{
|
||||||
|
char *str = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if ((str = malloc(n+1)) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, errno, "malloc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(str, buf, n);
|
||||||
|
str[n] = '\0';
|
||||||
|
ret = _http1_parse(h, rc, str, "http1-parse");
|
||||||
|
free(str);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
/*! Check http/1 UPGRADE to http/2
|
||||||
|
* If upgrade headers are encountered AND http/2 is configured, then
|
||||||
|
* - add upgrade headers or signal error
|
||||||
|
* - set http2 flag get settings to and signal to upper layer to do the actual transition.
|
||||||
|
* @retval -1 Error
|
||||||
|
* @retval 0 Yes, upgrade dont proceed with request
|
||||||
|
* @retval 1 No upgrade, proceed with request
|
||||||
|
* @note currently upgrade header is checked always if nghttp2 is configured but may be a
|
||||||
|
* runtime config option
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
http1_upgrade_http2(clicon_handle h,
|
||||||
|
restconf_stream_data *sd)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *str;
|
||||||
|
char *settings;
|
||||||
|
cxobj *xerr = NULL;
|
||||||
|
|
||||||
|
if ((str = restconf_param_get(h, "HTTP_UPGRADE")) != NULL &&
|
||||||
|
clicon_option_bool(h, "CLICON_RESTCONF_HTTP2_PLAIN") == 1){
|
||||||
|
/* Only accept "h2c" */
|
||||||
|
if (strcmp(str, "h2c") != 0){
|
||||||
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid upgrade token") < 0)
|
||||||
|
goto done;
|
||||||
|
if (api_return_err0(h, sd, xerr, 1, YANG_DATA_JSON, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
if (xerr)
|
||||||
|
xml_free(xerr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (restconf_reply_header(sd, "Connection", "Upgrade") < 0)
|
||||||
|
goto done;
|
||||||
|
if (restconf_reply_header(sd, "Upgrade", "h2c") < 0)
|
||||||
|
goto done;
|
||||||
|
if (restconf_reply_send(sd, 101, NULL, 0) < 0) /* Switch protocol */
|
||||||
|
goto done;
|
||||||
|
/* Signal http/2 upgrade to http/2 to upper restconf_connection handling */
|
||||||
|
sd->sd_upgrade2 = 1;
|
||||||
|
if ((settings = restconf_param_get(h, "HTTP_HTTP2_Settings")) != NULL &&
|
||||||
|
(sd->sd_settings2 = (uint8_t*)strdup(settings)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "strdup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retval = 0; /* Yes, upgrade or error */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
retval = 1; /* No upgrade, proceed with request */
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
#endif /* HAVE_LIBNGHTTP2 */
|
||||||
|
|
||||||
|
/*! Construct an HTTP/1 reply (dont actually send it)
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
restconf_http1_reply(restconf_conn *rc,
|
||||||
|
restconf_stream_data *sd)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
cg_var *cv;
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
* server MUST NOT send a Content-Length header field in any 2xx
|
||||||
|
* (Successful) response to a CONNECT request (Section 4.3.6 of
|
||||||
|
* [RFC7231]).
|
||||||
|
*/
|
||||||
|
if (sd->sd_code != 204 && sd->sd_code > 199)
|
||||||
|
if (restconf_reply_header(sd, "Content-Length", "%zu", sd->sd_body_len) < 0)
|
||||||
|
goto done;
|
||||||
|
/* Create reply and write headers */
|
||||||
|
#if 0 /* XXX need some keep-alive logic here */
|
||||||
|
/* protocol is HTTP/1.0 and clients wants to keep established */
|
||||||
|
if (restconf_reply_header(sd, "Connection", "keep-alive") < 0)
|
||||||
|
goto done;
|
||||||
|
#endif
|
||||||
|
cprintf(sd->sd_outp_buf, "HTTP/%u.%u %u %s\r\n",
|
||||||
|
rc->rc_proto_d1,
|
||||||
|
rc->rc_proto_d2,
|
||||||
|
sd->sd_code,
|
||||||
|
restconf_code2reason(sd->sd_code));
|
||||||
|
/* Loop over headers */
|
||||||
|
cv = NULL;
|
||||||
|
while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL)
|
||||||
|
cprintf(sd->sd_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv));
|
||||||
|
cprintf(sd->sd_outp_buf, "\r\n");
|
||||||
|
/* Write a body */
|
||||||
|
if (sd->sd_body){
|
||||||
|
cbuf_append_str(sd->sd_outp_buf, cbuf_get(sd->sd_body));
|
||||||
|
}
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Clixon request connect pointer
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_http1_path_root(clicon_handle h,
|
||||||
|
restconf_conn *rc)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
restconf_stream_data *sd;
|
||||||
|
cvec *cvv = NULL;
|
||||||
|
char *cn;
|
||||||
|
char *subject = NULL;
|
||||||
|
cxobj *xerr = NULL;
|
||||||
|
int pretty;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
clicon_debug(1, "------------");
|
||||||
|
pretty = restconf_pretty_get(h);
|
||||||
|
if ((sd = restconf_stream_find(rc, 0)) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Sanity check */
|
||||||
|
if (restconf_param_get(h, "REQUEST_URI") == NULL){
|
||||||
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Missing REQUEST_URI ") < 0)
|
||||||
|
goto done;
|
||||||
|
/* Select json as default since content-type header may not be accessible yet */
|
||||||
|
if (api_return_err0(h, sd, xerr, pretty, YANG_DATA_JSON, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if ((rc->rc_proto != HTTP_10 && rc->rc_proto != HTTP_11) ||
|
||||||
|
rc->rc_proto_d1 != 1 ||
|
||||||
|
(rc->rc_proto_d2 != 0 && rc->rc_proto_d2 != 1)){
|
||||||
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid HTTP version number") < 0)
|
||||||
|
goto done;
|
||||||
|
/* Select json as default since content-type header may not be accessible yet */
|
||||||
|
if (api_return_err0(h, sd, xerr, pretty, YANG_DATA_JSON, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||||
|
goto done; // XXX SHOULDNT EXIT if no REQUEST_URI
|
||||||
|
if (rc->rc_proto_d2 == 0 && rc->rc_proto == HTTP_11)
|
||||||
|
rc->rc_proto = HTTP_10;
|
||||||
|
else if (rc->rc_proto_d2 == 1 && rc->rc_proto != HTTP_10)
|
||||||
|
rc->rc_proto = HTTP_11;
|
||||||
|
if (rc->rc_ssl != NULL){
|
||||||
|
/* Slightly awkward way of taking SSL cert subject and CN and add it to restconf parameters
|
||||||
|
* instead of accessing it directly
|
||||||
|
* SSL subject fields, eg CN (Common Name) , can add more here? */
|
||||||
|
if (ssl_x509_name_oneline(rc->rc_ssl, &subject) < 0)
|
||||||
|
goto done;
|
||||||
|
if (subject != NULL) {
|
||||||
|
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
|
||||||
|
goto done;
|
||||||
|
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
|
||||||
|
if (restconf_param_set(h, "SSL_CN", cn) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Check sanity of session, eg ssl client cert validation, may set rc_exit */
|
||||||
|
if (restconf_connection_sanity(h, rc, sd) < 0)
|
||||||
|
goto done;
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
if ((ret = http1_upgrade_http2(h, sd)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0) /* upgrade */
|
||||||
|
goto upgrade;
|
||||||
|
#endif
|
||||||
|
/* call generic function */
|
||||||
|
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
||||||
|
if (api_well_known(h, sd) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||||
|
goto done;
|
||||||
|
fail:
|
||||||
|
if (restconf_param_del_all(h) < 0)
|
||||||
|
goto done;
|
||||||
|
upgrade:
|
||||||
|
if (sd->sd_code)
|
||||||
|
if (restconf_http1_reply(rc, sd) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
|
if (xerr)
|
||||||
|
xml_free(xerr);
|
||||||
|
if (cvv)
|
||||||
|
cvec_free(cvv);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Check expect header, if found generate a Continue reply
|
||||||
|
*
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] rc Restconf connection
|
||||||
|
* @param[in] sd Restconf stream data (for http1 only stream 0)
|
||||||
|
* @retval 1 OK, Send continue
|
||||||
|
* @retval 0 OK, Dont send continue
|
||||||
|
* @retval -1 Error
|
||||||
|
* @see rfc7231 Sec 5.1.1
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
http1_check_expect(clicon_handle h,
|
||||||
|
restconf_conn *rc,
|
||||||
|
restconf_stream_data *sd)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *val;
|
||||||
|
|
||||||
|
if ((val = restconf_param_get(h, "HTTP_EXPECT")) != NULL &&
|
||||||
|
strcmp(val, "100-continue") == 0){ /* just drop if not well-formed */
|
||||||
|
sd->sd_code = 100;
|
||||||
|
if (restconf_http1_reply(rc, sd) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 1; /* send continue by flushing stream buffer after the call */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Is there more data to be read?
|
||||||
|
*
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] sd Restconf stream data (for http1 only stream 0)
|
||||||
|
* @retval 1 OK, message is fully read, proceed to processing
|
||||||
|
* @retval 0 OK, but message is partially read, need more data before processing
|
||||||
|
* @retval -1 Error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
http1_check_readmore(clicon_handle h,
|
||||||
|
restconf_stream_data *sd)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *val;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if ((val = restconf_param_get(h, "HTTP_CONTENT_LENGTH")) != NULL &&
|
||||||
|
(len = atoi(val)) != 0){
|
||||||
|
if (cbuf_len(sd->sd_indata) < len)
|
||||||
|
goto readmore;
|
||||||
|
}
|
||||||
|
retval = 1;
|
||||||
|
done:
|
||||||
|
return retval;
|
||||||
|
readmore:
|
||||||
|
retval = 0;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
@ -33,8 +33,8 @@
|
||||||
|
|
||||||
* HTTP/1.1 parser according to RFC 7230
|
* HTTP/1.1 parser according to RFC 7230
|
||||||
*/
|
*/
|
||||||
#ifndef _CLIXON_HTTP1_H_
|
#ifndef _RESTCONF_HTTP1_H_
|
||||||
#define _CLIXON_HTTP1_H_
|
#define _RESTCONF_HTTP1_H_
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
|
|
@ -43,5 +43,7 @@ int clixon_http1_parse_file(clicon_handle h, restconf_conn *rc, FILE *f, const c
|
||||||
int clixon_http1_parse_string(clicon_handle h, restconf_conn *rc, char *str);
|
int clixon_http1_parse_string(clicon_handle h, restconf_conn *rc, char *str);
|
||||||
int clixon_http1_parse_buf(clicon_handle h, restconf_conn *rc, char *buf, size_t n);
|
int clixon_http1_parse_buf(clicon_handle h, restconf_conn *rc, char *buf, size_t n);
|
||||||
int restconf_http1_path_root(clicon_handle h, restconf_conn *rc);
|
int restconf_http1_path_root(clicon_handle h, restconf_conn *rc);
|
||||||
|
int http1_check_expect(clicon_handle h, restconf_conn *rc, restconf_stream_data *sd);
|
||||||
|
int http1_check_readmore(clicon_handle h, restconf_stream_data *sd);
|
||||||
|
|
||||||
#endif /* _CLIXON_HTTP1_H_ */
|
#endif /* _RESTCONF_HTTP1_H_ */
|
||||||
|
|
@ -127,17 +127,16 @@
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* cligen */
|
/* cligen */
|
||||||
#include <cligen/cligen.h>
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
/* clicon */
|
/* libclixon */
|
||||||
#include <clixon/clixon.h>
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
/* nghttp2 */
|
|
||||||
#include <nghttp2/nghttp2.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* restconf */
|
/* restconf */
|
||||||
#include "restconf_lib.h" /* generic shared with plugins */
|
#include "restconf_lib.h" /* generic shared with plugins */
|
||||||
#include "restconf_handle.h"
|
#include "restconf_handle.h"
|
||||||
|
|
@ -149,7 +148,7 @@
|
||||||
#include "restconf_nghttp2.h" /* http/2 */
|
#include "restconf_nghttp2.h" /* http/2 */
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_HTTP1
|
#ifdef HAVE_HTTP1
|
||||||
#include "clixon_http1.h"
|
#include "restconf_http1.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Command line options to be passed to getopt(3) */
|
/* Command line options to be passed to getopt(3) */
|
||||||
|
|
@ -170,9 +169,6 @@
|
||||||
/* Cert verify depth: dont know what to set here? */
|
/* Cert verify depth: dont know what to set here? */
|
||||||
#define VERIFY_DEPTH 5
|
#define VERIFY_DEPTH 5
|
||||||
|
|
||||||
/* Forward */
|
|
||||||
static int restconf_connection(int s, void* arg);
|
|
||||||
|
|
||||||
static int session_id_context = 1;
|
static int session_id_context = 1;
|
||||||
|
|
||||||
/*! Get restconf native handle
|
/*! Get restconf native handle
|
||||||
|
|
@ -209,98 +205,6 @@ restconf_native_handle_set(clicon_handle h,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write evbuf to socket
|
|
||||||
* see also this function in restcont_api_openssl.c
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
buf_write(char *buf,
|
|
||||||
size_t buflen,
|
|
||||||
int s,
|
|
||||||
SSL *ssl)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
ssize_t len;
|
|
||||||
ssize_t totlen = 0;
|
|
||||||
int er;
|
|
||||||
|
|
||||||
/* Two problems with debugging buffers from libevent that this fixes:
|
|
||||||
* 1. they are not "strings" in the sense they are not NULL-terminated
|
|
||||||
* 2. they are often very long
|
|
||||||
*/
|
|
||||||
if (clicon_debug_get()) {
|
|
||||||
char *dbgstr = NULL;
|
|
||||||
size_t sz;
|
|
||||||
sz = buflen>256?256:buflen; /* Truncate to 256 */
|
|
||||||
if ((dbgstr = malloc(sz+1)) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
memcpy(dbgstr, buf, sz);
|
|
||||||
dbgstr[sz] = '\0';
|
|
||||||
clicon_debug(1, "%s buflen:%zu buf:%s", __FUNCTION__, buflen, dbgstr);
|
|
||||||
free(dbgstr);
|
|
||||||
}
|
|
||||||
while (totlen < buflen){
|
|
||||||
if (ssl){
|
|
||||||
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
|
|
||||||
er = errno;
|
|
||||||
switch (SSL_get_error(ssl, len)){
|
|
||||||
case SSL_ERROR_SYSCALL: /* 5 */
|
|
||||||
if (er == ECONNRESET) {/* Connection reset by peer */
|
|
||||||
if (ssl)
|
|
||||||
SSL_free(ssl);
|
|
||||||
close(s);
|
|
||||||
clixon_event_unreg_fd(s, restconf_connection);
|
|
||||||
goto ok; /* Close socket and ssl */
|
|
||||||
}
|
|
||||||
else if (er == EAGAIN){
|
|
||||||
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
|
|
||||||
usleep(10000);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
clicon_err(OE_RESTCONF, er, "SSL_write %d", er);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
clicon_err(OE_SSL, 0, "SSL_write");
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if ((len = write(s, buf+totlen, buflen-totlen)) < 0){
|
|
||||||
if (errno == EAGAIN){
|
|
||||||
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
|
|
||||||
usleep(10000);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#if 1
|
|
||||||
else if (errno == ECONNRESET) {/* Connection reset by peer */
|
|
||||||
close(s);
|
|
||||||
clixon_event_unreg_fd(s, restconf_connection);
|
|
||||||
goto ok; /* Close socket and ssl */
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else{
|
|
||||||
clicon_err(OE_UNIX, errno, "write");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert(len != 0);
|
|
||||||
}
|
|
||||||
totlen += len;
|
|
||||||
} /* while */
|
|
||||||
ok:
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* util function to append log string
|
/* util function to append log string
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
|
|
@ -596,324 +500,6 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Send early handcoded bad request reply before actual packet received, just after accept
|
|
||||||
* @param[in] h Clixon handle
|
|
||||||
* @param[in] s Socket
|
|
||||||
* @param[in] ssl If set, it will be freed
|
|
||||||
* @param[in] body If given add message body using media
|
|
||||||
* @see restconf_badrequest which can only be called in a request context
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
send_badrequest(clicon_handle h,
|
|
||||||
int s,
|
|
||||||
SSL *ssl,
|
|
||||||
char *media,
|
|
||||||
char *body)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
cbuf *cb = NULL;
|
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
|
||||||
if ((cb = cbuf_new()) == NULL){
|
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cprintf(cb, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n");
|
|
||||||
if (body){
|
|
||||||
cprintf(cb, "Content-Type: %s\r\n", media);
|
|
||||||
cprintf(cb, "Content-Length: %zu\r\n", strlen(body)+2); /* for \r\n */
|
|
||||||
}
|
|
||||||
else
|
|
||||||
cprintf(cb, "Content-Length: 0\r\n");
|
|
||||||
cprintf(cb, "\r\n");
|
|
||||||
if (body)
|
|
||||||
cprintf(cb, "%s\r\n", body);
|
|
||||||
if (buf_write(cbuf_get(cb), cbuf_len(cb), s, ssl) < 0)
|
|
||||||
goto done;
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
if (cb)
|
|
||||||
cbuf_free(cb);
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
#define IFILE "/var/tmp/clixon-mirror/ifile"
|
|
||||||
#define FMTDIR "/var/tmp/clixon-mirror/"
|
|
||||||
|
|
||||||
static FILE *myf = NULL;
|
|
||||||
|
|
||||||
static int
|
|
||||||
mirror_pkt(const char *buf,
|
|
||||||
ssize_t n)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
|
|
||||||
if (fwrite(buf, 1, n, myf) != n){
|
|
||||||
perror("fopen");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mirror_new(void)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
static uint64_t u64 = 0;
|
|
||||||
cbuf *cb = cbuf_new();
|
|
||||||
FILE *ifile;
|
|
||||||
|
|
||||||
if ((ifile = fopen(IFILE, "r+")) == NULL){
|
|
||||||
perror("fopen r+ ifile");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (fscanf(ifile, "%" PRIu64, &u64) < 0){
|
|
||||||
perror("fscanf ifile");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
fclose(ifile);
|
|
||||||
}
|
|
||||||
if (myf != NULL)
|
|
||||||
fclose(myf);
|
|
||||||
cprintf(cb, FMTDIR "%" PRIu64 ".dump", u64);
|
|
||||||
if ((myf = fopen(cbuf_get(cb), "w")) == NULL){
|
|
||||||
perror("fopen");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
cbuf_free(cb);
|
|
||||||
u64++;
|
|
||||||
if ((ifile = fopen(IFILE, "w")) == NULL){
|
|
||||||
perror("fopen w+ ifile");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
fprintf(ifile, "%" PRIu64, u64);
|
|
||||||
fclose(ifile);
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*! New data connection after accept, receive and reply on data socket
|
|
||||||
*
|
|
||||||
* @param[in] s Socket where message arrived. read from this.
|
|
||||||
* @param[in] arg Client entry (from).
|
|
||||||
* @retval 0 OK
|
|
||||||
* @retval -1 Error Terminates backend and is never called). Instead errors are
|
|
||||||
* propagated back to client.
|
|
||||||
* @see restconf_accept_client where this callback is registered
|
|
||||||
* @note read buffer is limited. More data can be read in two ways: returns a buffer
|
|
||||||
* with 100 Continue, in which case that is replied and the function returns and the client sends
|
|
||||||
* more data.
|
|
||||||
* OR returns 0 with no reply, then this is assumed to mean read more data from the socket.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
restconf_connection(int s,
|
|
||||||
void *arg)
|
|
||||||
{
|
|
||||||
int retval = -1;
|
|
||||||
restconf_conn *rc = NULL;
|
|
||||||
ssize_t n;
|
|
||||||
char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */
|
|
||||||
int readmore = 1;
|
|
||||||
int sslerr;
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
int ret;
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_HTTP1
|
|
||||||
clicon_handle h;
|
|
||||||
restconf_stream_data *sd;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, s);
|
|
||||||
if ((rc = (restconf_conn*)arg) == NULL){
|
|
||||||
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
assert(s == rc->rc_s);
|
|
||||||
while (readmore) {
|
|
||||||
clicon_debug(1, "%s readmore", __FUNCTION__);
|
|
||||||
readmore = 0;
|
|
||||||
/* Example: curl -Ssik -u wilma:bar -X GET https://localhost/restconf/data/example:x */
|
|
||||||
if (rc->rc_ssl){
|
|
||||||
/* Non-ssl gets n == 0 here!
|
|
||||||
curl -Ssik --key /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.key --cert /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.crt -X GET https://localhost/restconf/data/example:x
|
|
||||||
*/
|
|
||||||
if ((n = SSL_read(rc->rc_ssl, buf, sizeof(buf))) < 0){
|
|
||||||
sslerr = SSL_get_error(rc->rc_ssl, n);
|
|
||||||
clicon_debug(1, "%s SSL_read() n:%zd errno:%d sslerr:%d", __FUNCTION__, n, errno, sslerr);
|
|
||||||
switch (sslerr){
|
|
||||||
case SSL_ERROR_WANT_READ: /* 2 */
|
|
||||||
/* SSL_ERROR_WANT_READ is returned when the last operation was a read operation
|
|
||||||
* from a nonblocking BIO.
|
|
||||||
* That is, it can happen if restconf_socket_init() below is called
|
|
||||||
* with SOCK_NONBLOCK
|
|
||||||
*/
|
|
||||||
clicon_debug(1, "%s SSL_read SSL_ERROR_WANT_READ", __FUNCTION__);
|
|
||||||
usleep(1000);
|
|
||||||
readmore = 1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
clicon_err(OE_XML, errno, "SSL_read");
|
|
||||||
goto done;
|
|
||||||
} /* switch */
|
|
||||||
continue; /* readmore */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if ((n = read(rc->rc_s, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */
|
|
||||||
switch(errno){
|
|
||||||
case ECONNRESET:/* Connection reset by peer */
|
|
||||||
clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s);
|
|
||||||
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
|
|
||||||
close(rc->rc_s);
|
|
||||||
restconf_conn_free(rc);
|
|
||||||
goto ok; /* Close socket and ssl */
|
|
||||||
break;
|
|
||||||
case EAGAIN:
|
|
||||||
clicon_debug(1, "%s read EAGAIN", __FUNCTION__);
|
|
||||||
usleep(1000);
|
|
||||||
readmore = 1;
|
|
||||||
break;
|
|
||||||
default:;
|
|
||||||
clicon_err(OE_XML, errno, "read");
|
|
||||||
goto done;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s read:%zd", __FUNCTION__, n);
|
|
||||||
if (n == 0){
|
|
||||||
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
|
|
||||||
if (restconf_close_ssl_socket(rc, 0) < 0)
|
|
||||||
goto done;
|
|
||||||
restconf_conn_free(rc);
|
|
||||||
rc = NULL;
|
|
||||||
goto ok;
|
|
||||||
}
|
|
||||||
#if 0
|
|
||||||
if (mirror_pkt(buf, n) < 0)
|
|
||||||
goto done;
|
|
||||||
#endif
|
|
||||||
switch (rc->rc_proto){
|
|
||||||
#ifdef HAVE_HTTP1
|
|
||||||
case HTTP_10:
|
|
||||||
case HTTP_11:
|
|
||||||
h = rc->rc_h;
|
|
||||||
if (clixon_http1_parse_buf(h, rc, buf, n) < 0){
|
|
||||||
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
|
|
||||||
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message></error></errors>") < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
if (restconf_http1_path_root(h, rc) < 0)
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
clicon_debug(1, "%s connection_parse OK", __FUNCTION__);
|
|
||||||
/* default stream */
|
|
||||||
if ((sd = restconf_stream_find(rc, 0)) == NULL){
|
|
||||||
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
|
|
||||||
rc->rc_s, rc->rc_ssl) < 0)
|
|
||||||
goto done;
|
|
||||||
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
|
|
||||||
cbuf_reset(sd->sd_outp_buf);
|
|
||||||
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
|
|
||||||
SSL_free(rc->rc_ssl);
|
|
||||||
rc->rc_ssl = NULL;
|
|
||||||
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);
|
|
||||||
goto ok;
|
|
||||||
}
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
if (sd->sd_upgrade2){
|
|
||||||
nghttp2_error ngerr;
|
|
||||||
|
|
||||||
/* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */
|
|
||||||
rc->rc_proto = HTTP_2;
|
|
||||||
if (http2_session_init(rc) < 0){
|
|
||||||
restconf_close_ssl_socket(rc, 1);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* The HTTP/1.1 request that is sent prior to upgrade is assigned a
|
|
||||||
* stream identifier of 1 (see Section 5.1.1) with default priority
|
|
||||||
*/
|
|
||||||
sd->sd_stream_id = 1;
|
|
||||||
/* The first HTTP/2 frame sent by the server MUST be a server connection
|
|
||||||
* preface (Section 3.5) consisting of a SETTINGS frame (Section 6.5).
|
|
||||||
*/
|
|
||||||
if ((ngerr = nghttp2_session_upgrade2(rc->rc_ngsession,
|
|
||||||
sd->sd_settings2,
|
|
||||||
sd->sd_settings2?strlen((const char*)sd->sd_settings2):0,
|
|
||||||
0, /* XXX: 1 if HEAD */
|
|
||||||
NULL)) < 0){
|
|
||||||
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_upgrade2");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
if (http2_send_server_connection(rc) < 0){
|
|
||||||
restconf_close_ssl_socket(rc, 1);
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
/* Use params from original http/1 session to http/2 stream */
|
|
||||||
if (http2_exec(rc, sd, rc->rc_ngsession, 1) < 0)
|
|
||||||
goto done;
|
|
||||||
/*
|
|
||||||
* Very special case for http/1->http/2 upgrade and restconf "restart"
|
|
||||||
* That is, the restconf daemon is restarted under the hood, and the session
|
|
||||||
* is closed in mid-step: it needs a couple of extra rounds to complete the http/2
|
|
||||||
* settings before it completes.
|
|
||||||
* Maybe a more precise way would be to encode that semantics using recieved http/2
|
|
||||||
* frames instead of just postponing nrof events?
|
|
||||||
*/
|
|
||||||
if (clixon_exit_get() == 1){
|
|
||||||
clixon_exit_set(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
#endif /* HAVE_HTTP1 */
|
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
|
||||||
case HTTP_2:
|
|
||||||
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
|
|
||||||
nghttp2_error ngerr;
|
|
||||||
if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, 0)) < 0)
|
|
||||||
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0)
|
|
||||||
goto done;
|
|
||||||
if (ret == 0){
|
|
||||||
restconf_close_ssl_socket(rc, 1);
|
|
||||||
if (restconf_conn_free(rc) < 0)
|
|
||||||
goto done;
|
|
||||||
goto ok;
|
|
||||||
}
|
|
||||||
/* There may be more data frames */
|
|
||||||
readmore++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
#endif /* HAVE_LIBNGHTTP2 */
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
} /* switch rc_proto */
|
|
||||||
} /* while readmore */
|
|
||||||
ok:
|
|
||||||
retval = 0;
|
|
||||||
done:
|
|
||||||
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
|
||||||
return retval;
|
|
||||||
} /* restconf_connection */
|
|
||||||
|
|
||||||
#if 0 /* debug */
|
#if 0 /* debug */
|
||||||
/*! Debug print all loaded certs
|
/*! Debug print all loaded certs
|
||||||
*/
|
*/
|
||||||
|
|
@ -1019,7 +605,7 @@ ssl_alpn_check(clicon_handle h,
|
||||||
if (alpn != NULL){
|
if (alpn != NULL){
|
||||||
cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>ALPN: protocol not recognized: %s</error-message></error></errors>", alpn);
|
cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>ALPN: protocol not recognized: %s</error-message></error></errors>", alpn);
|
||||||
clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr));
|
clicon_log(LOG_INFO, "%s Warning: %s", __FUNCTION__, cbuf_get(cberr));
|
||||||
if (send_badrequest(h, rc->rc_s, rc->rc_ssl,
|
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl,
|
||||||
"application/yang-data+xml",
|
"application/yang-data+xml",
|
||||||
cbuf_get(cberr)) < 0)
|
cbuf_get(cberr)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -1160,7 +746,7 @@ restconf_accept_client(int fd,
|
||||||
case SSL_ERROR_SSL: /* 1 */
|
case SSL_ERROR_SSL: /* 1 */
|
||||||
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
|
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
|
||||||
#if 1
|
#if 1
|
||||||
if (send_badrequest(h, rc->rc_s, NULL, "application/yang-data+xml",
|
if (native_send_badrequest(h, rc->rc_s, NULL, "application/yang-data+xml",
|
||||||
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The plain HTTP request was sent to HTTPS port</error-message></error></errors>") < 0)
|
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The plain HTTP request was sent to HTTPS port</error-message></error></errors>") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1243,7 +829,7 @@ restconf_accept_client(int fd,
|
||||||
}
|
}
|
||||||
else { /* Get certificates (if available) */
|
else { /* Get certificates (if available) */
|
||||||
if (proto != HTTP_2 &&
|
if (proto != HTTP_2 &&
|
||||||
send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
|
native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
|
||||||
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>") < 0)
|
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
restconf_conn_free(rc);
|
restconf_conn_free(rc);
|
||||||
|
|
@ -1315,10 +901,6 @@ restconf_accept_client(int fd,
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
} /* switch proto */
|
} /* switch proto */
|
||||||
#if 0
|
|
||||||
if (mirror_new() < 0)
|
|
||||||
goto done;
|
|
||||||
#endif
|
|
||||||
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
|
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
ok:
|
ok:
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ api_data_post(clicon_handle h,
|
||||||
* expected data resource. (tested again below)
|
* expected data resource. (tested again below)
|
||||||
*/
|
*/
|
||||||
if (data == NULL || strlen(data) == 0){
|
if (data == NULL || strlen(data) == 0){
|
||||||
if (netconf_malformed_message_xml(&xerr, "The message-body MUST contain exactly one instance of the expected data resource") < 0)
|
if (netconf_malformed_message_xml(&xerr, "The message-body of POST MUST contain exactly one instance of the expected data resource") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
|
|
@ -55,23 +55,32 @@
|
||||||
#include <openssl/rand.h>
|
#include <openssl/rand.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
#include <nghttp2/nghttp2.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* cligen */
|
/* cligen */
|
||||||
#include <cligen/cligen.h>
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
/* clicon */
|
/* libclixon */
|
||||||
#include <clixon/clixon.h>
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
/* restconf */
|
/* restconf */
|
||||||
#include "restconf_lib.h" /* generic shared with plugins */
|
#include "restconf_lib.h" /* generic shared with plugins */
|
||||||
#include "restconf_handle.h"
|
#include "restconf_handle.h"
|
||||||
#include "restconf_err.h"
|
#include "restconf_err.h"
|
||||||
|
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
#include "restconf_nghttp2.h" /* http/2 */
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_HTTP1
|
||||||
|
#include "restconf_http1.h"
|
||||||
#endif
|
#endif
|
||||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* @see restconf_stream_free
|
||||||
|
*/
|
||||||
restconf_stream_data *
|
restconf_stream_data *
|
||||||
restconf_stream_data_new(restconf_conn *rc,
|
restconf_stream_data_new(restconf_conn *rc,
|
||||||
int32_t stream_id)
|
int32_t stream_id)
|
||||||
|
|
@ -297,3 +306,400 @@ restconf_connection_sanity(clicon_handle h,
|
||||||
xml_free(xerr);
|
xml_free(xerr);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Write buf to socket
|
||||||
|
* see also this function in restcont_api_openssl.c
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
native_buf_write(char *buf,
|
||||||
|
size_t buflen,
|
||||||
|
int s,
|
||||||
|
SSL *ssl)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
ssize_t len;
|
||||||
|
ssize_t totlen = 0;
|
||||||
|
int er;
|
||||||
|
|
||||||
|
/* Two problems with debugging buffers that this fixes:
|
||||||
|
* 1. they are not "strings" in the sense they are not NULL-terminated
|
||||||
|
* 2. they are often very long
|
||||||
|
*/
|
||||||
|
if (clicon_debug_get()) {
|
||||||
|
char *dbgstr = NULL;
|
||||||
|
size_t sz;
|
||||||
|
sz = buflen>256?256:buflen; /* Truncate to 256 */
|
||||||
|
if ((dbgstr = malloc(sz+1)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memcpy(dbgstr, buf, sz);
|
||||||
|
dbgstr[sz] = '\0';
|
||||||
|
clicon_debug(1, "%s buflen:%zu buf:\n%s", __FUNCTION__, buflen, dbgstr);
|
||||||
|
free(dbgstr);
|
||||||
|
}
|
||||||
|
while (totlen < buflen){
|
||||||
|
if (ssl){
|
||||||
|
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
|
||||||
|
er = errno;
|
||||||
|
switch (SSL_get_error(ssl, len)){
|
||||||
|
case SSL_ERROR_SYSCALL: /* 5 */
|
||||||
|
if (er == ECONNRESET) {/* Connection reset by peer */
|
||||||
|
if (ssl)
|
||||||
|
SSL_free(ssl);
|
||||||
|
close(s);
|
||||||
|
clixon_event_unreg_fd(s, restconf_connection);
|
||||||
|
goto ok; /* Close socket and ssl */
|
||||||
|
}
|
||||||
|
else if (er == EAGAIN){
|
||||||
|
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
|
||||||
|
usleep(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
clicon_err(OE_RESTCONF, er, "SSL_write %d", er);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clicon_err(OE_SSL, 0, "SSL_write");
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if ((len = write(s, buf+totlen, buflen-totlen)) < 0){
|
||||||
|
switch (errno){
|
||||||
|
case EAGAIN: /* Operation would block */
|
||||||
|
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
|
||||||
|
usleep(10000);
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
case ECONNRESET: /* Connection reset by peer */
|
||||||
|
case EPIPE: /* Broken pipe */
|
||||||
|
close(s);
|
||||||
|
clixon_event_unreg_fd(s, restconf_connection);
|
||||||
|
goto ok; /* Close socket and ssl */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clicon_err(OE_UNIX, errno, "write %d", errno);
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(len != 0);
|
||||||
|
}
|
||||||
|
totlen += len;
|
||||||
|
} /* while */
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Send early handcoded bad request reply before actual packet received, just after accept
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] s Socket
|
||||||
|
* @param[in] ssl If set, it will be freed
|
||||||
|
* @param[in] body If given add message body using media
|
||||||
|
* @see restconf_badrequest which can only be called in a request context
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
native_send_badrequest(clicon_handle h,
|
||||||
|
int s,
|
||||||
|
SSL *ssl,
|
||||||
|
char *media,
|
||||||
|
char *body)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
cbuf *cb = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
cprintf(cb, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n");
|
||||||
|
if (body){
|
||||||
|
cprintf(cb, "Content-Type: %s\r\n", media);
|
||||||
|
cprintf(cb, "Content-Length: %zu\r\n", strlen(body)+2); /* for \r\n */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cprintf(cb, "Content-Length: 0\r\n");
|
||||||
|
cprintf(cb, "\r\n");
|
||||||
|
if (body)
|
||||||
|
cprintf(cb, "%s\r\n", body);
|
||||||
|
if (native_buf_write(cbuf_get(cb), cbuf_len(cb), s, ssl) < 0)
|
||||||
|
goto done;
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! New data connection after accept, receive and reply on data socket
|
||||||
|
*
|
||||||
|
* @param[in] s Socket where message arrived. read from this.
|
||||||
|
* @param[in] arg Client entry (from).
|
||||||
|
* @retval 0 OK
|
||||||
|
* @retval -1 Error Terminates backend and is never called). Instead errors are
|
||||||
|
* propagated back to client.
|
||||||
|
* @see restconf_accept_client where this callback is registered
|
||||||
|
* @note read buffer is limited. More data can be read in two ways: returns a buffer
|
||||||
|
* with 100 Continue, in which case that is replied and the function returns and the client sends
|
||||||
|
* more data.
|
||||||
|
* OR returns 0 with no reply, then this is assumed to mean read more data from the socket.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
restconf_connection(int s,
|
||||||
|
void *arg)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
restconf_conn *rc = NULL;
|
||||||
|
ssize_t n;
|
||||||
|
char buf[BUFSIZ]; /* from stdio.h, typically 8K. 256 fails some tests*/
|
||||||
|
char *totbuf = NULL;
|
||||||
|
size_t totlen = 0;
|
||||||
|
int readmore = 1;
|
||||||
|
int sslerr;
|
||||||
|
int contnr = 0; /* Continue sent */
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
int ret;
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_HTTP1
|
||||||
|
clicon_handle h;
|
||||||
|
restconf_stream_data *sd;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, s);
|
||||||
|
if ((rc = (restconf_conn*)arg) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
assert(s == rc->rc_s);
|
||||||
|
while (readmore) {
|
||||||
|
clicon_debug(1, "%s readmore", __FUNCTION__);
|
||||||
|
readmore = 0;
|
||||||
|
/* Example: curl -Ssik -u wilma:bar -X GET https://localhost/restconf/data/example:x */
|
||||||
|
if (rc->rc_ssl){
|
||||||
|
/* Non-ssl gets n == 0 here!
|
||||||
|
curl -Ssik --key /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.key --cert /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.crt -X GET https://localhost/restconf/data/example:x
|
||||||
|
*/
|
||||||
|
if ((n = SSL_read(rc->rc_ssl, buf, sizeof(buf))) < 0){
|
||||||
|
sslerr = SSL_get_error(rc->rc_ssl, n);
|
||||||
|
clicon_debug(1, "%s SSL_read() n:%zd errno:%d sslerr:%d", __FUNCTION__, n, errno, sslerr);
|
||||||
|
switch (sslerr){
|
||||||
|
case SSL_ERROR_WANT_READ: /* 2 */
|
||||||
|
/* SSL_ERROR_WANT_READ is returned when the last operation was a read operation
|
||||||
|
* from a nonblocking BIO.
|
||||||
|
* That is, it can happen if restconf_socket_init() below is called
|
||||||
|
* with SOCK_NONBLOCK
|
||||||
|
*/
|
||||||
|
clicon_debug(1, "%s SSL_read SSL_ERROR_WANT_READ", __FUNCTION__);
|
||||||
|
usleep(1000);
|
||||||
|
readmore = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clicon_err(OE_XML, errno, "SSL_read");
|
||||||
|
goto done;
|
||||||
|
} /* switch */
|
||||||
|
continue; /* readmore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if ((n = read(rc->rc_s, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */
|
||||||
|
switch(errno){
|
||||||
|
case ECONNRESET:/* Connection reset by peer */
|
||||||
|
clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s);
|
||||||
|
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
|
||||||
|
close(rc->rc_s);
|
||||||
|
restconf_conn_free(rc);
|
||||||
|
goto ok; /* Close socket and ssl */
|
||||||
|
break;
|
||||||
|
case EAGAIN:
|
||||||
|
clicon_debug(1, "%s read EAGAIN", __FUNCTION__);
|
||||||
|
usleep(1000);
|
||||||
|
readmore = 1;
|
||||||
|
break;
|
||||||
|
default:;
|
||||||
|
clicon_err(OE_XML, errno, "read");
|
||||||
|
goto done;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clicon_debug(1, "%s read:%zd", __FUNCTION__, n);
|
||||||
|
if (n == 0){
|
||||||
|
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
|
||||||
|
if (restconf_close_ssl_socket(rc, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
restconf_conn_free(rc);
|
||||||
|
rc = NULL;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
switch (rc->rc_proto){
|
||||||
|
#ifdef HAVE_HTTP1
|
||||||
|
case HTTP_10:
|
||||||
|
case HTTP_11:
|
||||||
|
h = rc->rc_h;
|
||||||
|
/* default stream */
|
||||||
|
if ((sd = restconf_stream_find(rc, 0)) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* multi-buffer for multiple reads */
|
||||||
|
totlen += n;
|
||||||
|
if ((totbuf = realloc(totbuf, totlen+1)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "realloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memcpy(&totbuf[totlen-n], buf, n);
|
||||||
|
totbuf[totlen] = '\0';
|
||||||
|
if (clixon_http1_parse_string(h, rc, totbuf) < 0){
|
||||||
|
if (native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
|
||||||
|
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message></error></errors>") < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
/* Check for Continue and if so reply with 100 Continue
|
||||||
|
* ret == 1: send reply
|
||||||
|
*/
|
||||||
|
if (!contnr){
|
||||||
|
if ((ret = http1_check_expect(h, rc, sd)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 1){
|
||||||
|
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;
|
||||||
|
cvec_reset(sd->sd_outp_hdrs);
|
||||||
|
cbuf_reset(sd->sd_outp_buf);
|
||||||
|
contnr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Check whole message is read.
|
||||||
|
* ret == 0: need more bytes
|
||||||
|
*/
|
||||||
|
if ((ret = http1_check_readmore(h, sd)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0){
|
||||||
|
readmore++;
|
||||||
|
#if 1
|
||||||
|
/* Clear all stream data if reading more
|
||||||
|
* Alternative would be to not adding new data to totbuf ^
|
||||||
|
* and just append to sd->sd_indata but that would assume
|
||||||
|
* all headers read on first round. But that cant be done withut
|
||||||
|
* some probing on the socket if there is more data since it
|
||||||
|
* would hang on read otherwise
|
||||||
|
*/
|
||||||
|
cbuf_reset(sd->sd_indata);
|
||||||
|
if (sd->sd_qvec)
|
||||||
|
cvec_free(sd->sd_qvec);
|
||||||
|
if (restconf_param_del_all(h) < 0)
|
||||||
|
goto done;
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (restconf_http1_path_root(h, rc) < 0)
|
||||||
|
goto done;
|
||||||
|
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;
|
||||||
|
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
|
||||||
|
cbuf_reset(sd->sd_outp_buf);
|
||||||
|
}
|
||||||
|
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
|
||||||
|
SSL_free(rc->rc_ssl);
|
||||||
|
rc->rc_ssl = NULL;
|
||||||
|
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);
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
if (sd->sd_upgrade2){
|
||||||
|
nghttp2_error ngerr;
|
||||||
|
|
||||||
|
/* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */
|
||||||
|
rc->rc_proto = HTTP_2;
|
||||||
|
if (http2_session_init(rc) < 0){
|
||||||
|
restconf_close_ssl_socket(rc, 1);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* The HTTP/1.1 request that is sent prior to upgrade is assigned a
|
||||||
|
* stream identifier of 1 (see Section 5.1.1) with default priority
|
||||||
|
*/
|
||||||
|
sd->sd_stream_id = 1;
|
||||||
|
/* The first HTTP/2 frame sent by the server MUST be a server connection
|
||||||
|
* preface (Section 3.5) consisting of a SETTINGS frame (Section 6.5).
|
||||||
|
*/
|
||||||
|
if ((ngerr = nghttp2_session_upgrade2(rc->rc_ngsession,
|
||||||
|
sd->sd_settings2,
|
||||||
|
sd->sd_settings2?strlen((const char*)sd->sd_settings2):0,
|
||||||
|
0, /* XXX: 1 if HEAD */
|
||||||
|
NULL)) < 0){
|
||||||
|
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_upgrade2");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (http2_send_server_connection(rc) < 0){
|
||||||
|
restconf_close_ssl_socket(rc, 1);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Use params from original http/1 session to http/2 stream */
|
||||||
|
if (http2_exec(rc, sd, rc->rc_ngsession, 1) < 0)
|
||||||
|
goto done;
|
||||||
|
/*
|
||||||
|
* Very special case for http/1->http/2 upgrade and restconf "restart"
|
||||||
|
* That is, the restconf daemon is restarted under the hood, and the session
|
||||||
|
* is closed in mid-step: it needs a couple of extra rounds to complete the http/2
|
||||||
|
* settings before it completes.
|
||||||
|
* Maybe a more precise way would be to encode that semantics using recieved http/2
|
||||||
|
* frames instead of just postponing nrof events?
|
||||||
|
*/
|
||||||
|
if (clixon_exit_get() == 1){
|
||||||
|
clixon_exit_set(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
#endif /* HAVE_HTTP1 */
|
||||||
|
#ifdef HAVE_LIBNGHTTP2
|
||||||
|
case HTTP_2:
|
||||||
|
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
|
||||||
|
nghttp2_error ngerr;
|
||||||
|
if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, 0)) < 0)
|
||||||
|
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0){
|
||||||
|
restconf_close_ssl_socket(rc, 1);
|
||||||
|
if (restconf_conn_free(rc) < 0)
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* There may be more data frames */
|
||||||
|
readmore++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif /* HAVE_LIBNGHTTP2 */
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
} /* switch rc_proto */
|
||||||
|
} /* while readmore */
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (totbuf)
|
||||||
|
free(totbuf);
|
||||||
|
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
|
||||||
|
return retval;
|
||||||
|
} /* restconf_connection */
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,12 @@ typedef struct {
|
||||||
* Per connection request
|
* Per connection request
|
||||||
*/
|
*/
|
||||||
typedef struct restconf_conn {
|
typedef struct restconf_conn {
|
||||||
// qelem_t rs_qelem; /* List header */
|
/* XXX rc_proto and rc_proto_d1/d2 may not both be necessary.
|
||||||
size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */
|
* remove rc_proto?
|
||||||
|
*/
|
||||||
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
|
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
|
||||||
|
int rc_proto_d1; /* parsed version digit 1 */
|
||||||
|
int rc_proto_d2; /* parsed version digit 2 */
|
||||||
int rc_s; /* Connection socket */
|
int rc_s; /* Connection socket */
|
||||||
clicon_handle rc_h; /* Clixon handle */
|
clicon_handle rc_h; /* Clixon handle */
|
||||||
SSL *rc_ssl; /* Structure for SSL connection */
|
SSL *rc_ssl; /* Structure for SSL connection */
|
||||||
|
|
@ -137,7 +140,8 @@ int ssl_x509_name_oneline(SSL *ssl, char **oneline);
|
||||||
|
|
||||||
int restconf_close_ssl_socket(restconf_conn *rc, int shutdown); /* XXX in restconf_main_native.c */
|
int restconf_close_ssl_socket(restconf_conn *rc, int shutdown); /* XXX in restconf_main_native.c */
|
||||||
int restconf_connection_sanity(clicon_handle h, restconf_conn *rc, restconf_stream_data *sd);
|
int restconf_connection_sanity(clicon_handle h, restconf_conn *rc, restconf_stream_data *sd);
|
||||||
|
int native_send_badrequest(clicon_handle h, int s, SSL *ssl, char *media, char *body);
|
||||||
|
int restconf_connection(int s, void *arg);
|
||||||
|
|
||||||
#endif /* _RESTCONF_NATIVE_H_ */
|
#endif /* _RESTCONF_NATIVE_H_ */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -451,6 +451,10 @@ http2_exec(restconf_conn *rc,
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if (sd->sd_path){
|
||||||
|
free(sd->sd_path);
|
||||||
|
sd->sd_path = NULL;
|
||||||
|
}
|
||||||
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
||||||
|
|
|
||||||
1
configure
vendored
1
configure
vendored
|
|
@ -5184,6 +5184,7 @@ $as_echo "checking http1 is enabled: $ac_enable_http1" >&6; }
|
||||||
if test "$ac_enable_http1" = "yes"; then
|
if test "$ac_enable_http1" = "yes"; then
|
||||||
$as_echo "#define HAVE_HTTP1 true" >>confdefs.h
|
$as_echo "#define HAVE_HTTP1 true" >>confdefs.h
|
||||||
# Must be tree/false (not 0/1) used in shells
|
# Must be tree/false (not 0/1) used in shells
|
||||||
|
HAVE_HTTP1=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if nghttp2 is enabled for http/2
|
# Check if nghttp2 is enabled for http/2
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ elif test "x${with_restconf}" == xnative; then
|
||||||
AC_MSG_RESULT(checking http1 is enabled: $ac_enable_http1)
|
AC_MSG_RESULT(checking http1 is enabled: $ac_enable_http1)
|
||||||
if test "$ac_enable_http1" = "yes"; then
|
if test "$ac_enable_http1" = "yes"; then
|
||||||
AC_DEFINE(HAVE_HTTP1,true) # Must be tree/false (not 0/1) used in shells
|
AC_DEFINE(HAVE_HTTP1,true) # Must be tree/false (not 0/1) used in shells
|
||||||
|
HAVE_HTTP1=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if nghttp2 is enabled for http/2
|
# Check if nghttp2 is enabled for http/2
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ RUN apk add --update flex bison
|
||||||
|
|
||||||
# need to add www user manually
|
# need to add www user manually
|
||||||
RUN adduser -D -H -G www-data www-data
|
RUN adduser -D -H -G www-data www-data
|
||||||
# for libevtp
|
|
||||||
RUN apk add --update openssl libevent
|
RUN apk add --update openssl
|
||||||
|
|
||||||
# nghttp2 dependencies
|
# nghttp2 dependencies
|
||||||
RUN apk add --update nghttp2
|
RUN apk add --update nghttp2
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ RUN apk add --update flex bison
|
||||||
|
|
||||||
# need to add www user manually
|
# need to add www user manually
|
||||||
RUN adduser -D -H -G www-data www-data
|
RUN adduser -D -H -G www-data www-data
|
||||||
# for libevtp
|
|
||||||
RUN apk add --update openssl libevent
|
RUN apk add --update openssl
|
||||||
|
|
||||||
# nghttp2 dependencies
|
# nghttp2 dependencies
|
||||||
RUN apk add --update nghttp2
|
RUN apk add --update nghttp2
|
||||||
|
|
|
||||||
|
|
@ -152,8 +152,9 @@ clicon_strjoin(int argc,
|
||||||
/*! Join two string with delimiter.
|
/*! Join two string with delimiter.
|
||||||
* @param[in] str1 string 1 (will be freed) (optional)
|
* @param[in] str1 string 1 (will be freed) (optional)
|
||||||
* @param[in] del delimiter string (not freed) cannot be NULL (but "")
|
* @param[in] del delimiter string (not freed) cannot be NULL (but "")
|
||||||
* @param[in] str2 string 2 (will be freed)
|
* @param[in] str2 string 2 (not freed) mandatory
|
||||||
* @see clicon_strjoin
|
* @see clicon_strjoin
|
||||||
|
* This is somewhat of a special case.
|
||||||
*/
|
*/
|
||||||
char*
|
char*
|
||||||
clixon_string_del_join(char *str1,
|
clixon_string_del_join(char *str1,
|
||||||
|
|
@ -163,8 +164,11 @@ clixon_string_del_join(char *str1,
|
||||||
char *str;
|
char *str;
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
|
if (str2 == NULL){
|
||||||
|
clicon_err(OE_UNIX, EINVAL, "str2 is NULL");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
len = strlen(str2) + 1;
|
len = strlen(str2) + 1;
|
||||||
|
|
||||||
if (str1)
|
if (str1)
|
||||||
len += strlen(str1);
|
len += strlen(str1);
|
||||||
len += strlen(del);
|
len += strlen(del);
|
||||||
|
|
@ -178,7 +182,6 @@ clixon_string_del_join(char *str1,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
snprintf(str, len, "%s%s", del, str2);
|
snprintf(str, len, "%s%s", del, str2);
|
||||||
free(str2);
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1608,23 +1608,35 @@ deviate_replace_substmt : type_stmt { _PARSE_DEBUG("deviate-replace-subs
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
unknown_stmt : ustring ':' ustring optsep ';'
|
unknown_stmt : ustring ':' ustring optsep ';'
|
||||||
{ char *id; if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
{
|
||||||
|
char *id;
|
||||||
|
if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||||
|
free($3);
|
||||||
if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt");
|
if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt");
|
||||||
_PARSE_DEBUG("unknown-stmt -> ustring : ustring ;");
|
_PARSE_DEBUG("unknown-stmt -> ustring : ustring ;");
|
||||||
}
|
}
|
||||||
| ustring ':' ustring sep string optsep ';'
|
| ustring ':' ustring sep string optsep ';'
|
||||||
{ char *id; if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
{
|
||||||
|
char *id;
|
||||||
|
if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||||
|
free($3);
|
||||||
if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("unknown_stmt"); }
|
if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL){ _YYERROR("unknown_stmt"); }
|
||||||
_PARSE_DEBUG("unknown-stmt -> ustring : ustring sep string ;");
|
_PARSE_DEBUG("unknown-stmt -> ustring : ustring sep string ;");
|
||||||
}
|
}
|
||||||
| ustring ':' ustring optsep
|
| ustring ':' ustring optsep
|
||||||
{ char *id; if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
{
|
||||||
|
char *id;
|
||||||
|
if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||||
|
free($3);
|
||||||
if (ysp_add_push(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt"); }
|
if (ysp_add_push(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("unknown_stmt"); }
|
||||||
'{' yang_stmts '}'
|
'{' yang_stmts '}'
|
||||||
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
||||||
_PARSE_DEBUG("unknown-stmt -> ustring : ustring { yang-stmts }"); }
|
_PARSE_DEBUG("unknown-stmt -> ustring : ustring { yang-stmts }"); }
|
||||||
| ustring ':' ustring sep string optsep
|
| ustring ':' ustring sep string optsep
|
||||||
{ char *id; if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
{
|
||||||
|
char *id;
|
||||||
|
if ((id=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("unknown_stmt");
|
||||||
|
free($3);
|
||||||
if (ysp_add_push(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("unknown_stmt"); }
|
if (ysp_add_push(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("unknown_stmt"); }
|
||||||
'{' yang_stmts '}'
|
'{' yang_stmts '}'
|
||||||
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
{ if (ystack_pop(_yy) < 0) _YYERROR("unknown_stmt");
|
||||||
|
|
@ -1818,10 +1830,14 @@ ustring : ustring CHARS
|
||||||
;
|
;
|
||||||
|
|
||||||
abs_schema_nodeid : abs_schema_nodeid '/' node_identifier
|
abs_schema_nodeid : abs_schema_nodeid '/' node_identifier
|
||||||
{ if (($$=clixon_string_del_join($1, "/", $3)) == NULL) _YYERROR("abs_schema_nodeid");
|
{
|
||||||
|
if (($$=clixon_string_del_join($1, "/", $3)) == NULL) _YYERROR("abs_schema_nodeid");
|
||||||
|
free($3);
|
||||||
_PARSE_DEBUG("absolute-schema-nodeid -> absolute-schema-nodeid / node-identifier"); }
|
_PARSE_DEBUG("absolute-schema-nodeid -> absolute-schema-nodeid / node-identifier"); }
|
||||||
| '/' node_identifier
|
| '/' node_identifier
|
||||||
{ if (($$=clixon_string_del_join(NULL, "/", $2)) == NULL) _YYERROR("abs_schema_nodeid");
|
{
|
||||||
|
if (($$=clixon_string_del_join(NULL, "/", $2)) == NULL) _YYERROR("abs_schema_nodeid");
|
||||||
|
free($2);
|
||||||
_PARSE_DEBUG("absolute-schema-nodeid -> / node-identifier"); }
|
_PARSE_DEBUG("absolute-schema-nodeid -> / node-identifier"); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -1855,7 +1871,9 @@ desc_schema_nodeid : node_identifier
|
||||||
{ $$= $1;
|
{ $$= $1;
|
||||||
_PARSE_DEBUG("descendant-schema-nodeid -> node_identifier"); }
|
_PARSE_DEBUG("descendant-schema-nodeid -> node_identifier"); }
|
||||||
| node_identifier abs_schema_nodeid
|
| node_identifier abs_schema_nodeid
|
||||||
{ if (($$=clixon_string_del_join($1, "", $2)) == NULL) _YYERROR("desc_schema_nodeid");
|
{
|
||||||
|
if (($$=clixon_string_del_join($1, "", $2)) == NULL) _YYERROR("desc_schema_nodeid");
|
||||||
|
free($2);
|
||||||
_PARSE_DEBUG("descendant-schema-nodeid -> node_identifier abs_schema_nodeid"); }
|
_PARSE_DEBUG("descendant-schema-nodeid -> node_identifier abs_schema_nodeid"); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -1894,7 +1912,9 @@ node_identifier : IDENTIFIER
|
||||||
{ $$=$1;
|
{ $$=$1;
|
||||||
_PARSE_DEBUG("identifier-ref-arg-str -> string"); }
|
_PARSE_DEBUG("identifier-ref-arg-str -> string"); }
|
||||||
| IDENTIFIER ':' IDENTIFIER
|
| IDENTIFIER ':' IDENTIFIER
|
||||||
{ if (($$=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("node_identifier");
|
{
|
||||||
|
if (($$=clixon_string_del_join($1, ":", $3)) == NULL) _YYERROR("node_identifier");
|
||||||
|
free($3);
|
||||||
_PARSE_DEBUG("identifier-ref-arg-str -> prefix : string"); }
|
_PARSE_DEBUG("identifier-ref-arg-str -> prefix : string"); }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,11 @@
|
||||||
# --with-restconf=native Integration with embedded web server
|
# --with-restconf=native Integration with embedded web server
|
||||||
WITH_RESTCONF=@with_restconf@ # native, fcgi or ""
|
WITH_RESTCONF=@with_restconf@ # native, fcgi or ""
|
||||||
|
|
||||||
HAVE_LIBNGHTTP2=@HAVE_LIBNGHTTP2@
|
# HTTP/2?
|
||||||
|
# If set, curl options are set to use --http2 which may not be what you want, ie
|
||||||
|
# you may want to force it to http/1 for example
|
||||||
|
# If so, override before test
|
||||||
|
: ${HAVE_LIBNGHTTP2:=@HAVE_LIBNGHTTP2@}o
|
||||||
HAVE_HTTP1=@HAVE_HTTP1@
|
HAVE_HTTP1=@HAVE_HTTP1@
|
||||||
|
|
||||||
# This is for libxml2 XSD regex engine
|
# This is for libxml2 XSD regex engine
|
||||||
|
|
@ -85,4 +89,6 @@ LIBSTATIC_SUFFIX=@LIBSTATIC_SUFFIX@
|
||||||
LIBS="@LIBS@"
|
LIBS="@LIBS@"
|
||||||
CLIXON_YANG_PATCH=@CLIXON_YANG_PATCH@
|
CLIXON_YANG_PATCH=@CLIXON_YANG_PATCH@
|
||||||
YANG_STANDARD_DIR=@YANG_STANDARD_DIR@
|
YANG_STANDARD_DIR=@YANG_STANDARD_DIR@
|
||||||
|
|
||||||
YANG_INSTALLDIR=@YANG_INSTALLDIR@
|
YANG_INSTALLDIR=@YANG_INSTALLDIR@
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ if ${HAVE_LIBNGHTTP2}; then
|
||||||
CURLOPTS="${CURLOPTS} --http2-prior-knowledge"
|
CURLOPTS="${CURLOPTS} --http2-prior-knowledge"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
CURLOPTS="${CURLOPTS} --http1.1"
|
||||||
HVER=1.1
|
HVER=1.1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -454,7 +454,7 @@ function testrun()
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 201"
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:description":"The-first-interface"}' $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 201"
|
||||||
|
|
||||||
new "Add nothing using POST (expect fail)"
|
new "Add nothing using POST (expect fail)"
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body MUST contain exactly one instance of the expected data resource"}}}'
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $proto://$addr/restconf/data/ietf-interfaces:interfaces/interface=eth%2f0%2f0)" 0 "HTTP/$HVER 400" '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"The message-body of POST MUST contain exactly one instance of the expected data resource"}}}'
|
||||||
|
|
||||||
new "restconf Check description added"
|
new "restconf Check description added"
|
||||||
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
expectpart "$(curl $CURLOPTS -X GET $proto://$addr/restconf/data/ietf-interfaces:interfaces)" 0 "HTTP/$HVER 200" '{"ietf-interfaces:interfaces":{"interface":\[{"name":"eth/0/0","description":"The-first-interface","type":"clixon-example:eth","enabled":true,"oper-status":"up","clixon-example:my-status":{"int":42,"str":"foo"}}\]}}'
|
||||||
|
|
|
||||||
131
test/test_restconf_continue.sh
Executable file
131
test/test_restconf_continue.sh
Executable file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Restconf HTTP/1.1 Expect/Continue functionality
|
||||||
|
# Trigger Expect by curl -H. Some curls seem to trigger one on large PUTs but not all
|
||||||
|
|
||||||
|
# Override default to use http/1.1
|
||||||
|
HAVE_LIBNGHTTP2=false
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
cfg=$dir/conf.xml
|
||||||
|
fyang=$dir/restconf.yang
|
||||||
|
fjson=$dir/large.json
|
||||||
|
|
||||||
|
# Define default restconfig config: RESTCONFIG
|
||||||
|
RESTCONFIG=$(restconf_config none false)
|
||||||
|
|
||||||
|
# <CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
|
||||||
|
cat <<EOF > $cfg
|
||||||
|
<clixon-config xmlns="http://clicon.org/config">
|
||||||
|
<CLICON_FEATURE>clixon-restconf:allow-auth-none</CLICON_FEATURE> <!-- Use auth-type=none -->
|
||||||
|
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
||||||
|
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||||
|
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
|
||||||
|
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
||||||
|
<CLICON_BACKEND_PIDFILE>$dir/restconf.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
|
$RESTCONFIG
|
||||||
|
</clixon-config>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<EOF > $fyang
|
||||||
|
module 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 "generate large request"
|
||||||
|
# Add large put, curl seems to create a Expect:100-continue after 1024 bytes
|
||||||
|
# Alt: add in file if nr=5000 reacts with "Argument list too long"
|
||||||
|
echo -n '{"example:table":{"parameter":[' > $fjson
|
||||||
|
|
||||||
|
nr=10000
|
||||||
|
for (( i=0; i<$nr; i++ )); do
|
||||||
|
if [ $i -ne 0 ]; then
|
||||||
|
echo -n ",
|
||||||
|
" >> $fjson
|
||||||
|
fi
|
||||||
|
echo -n "{\"name\":\"A$i\",\"value\":\"$i\"}" >> $fjson
|
||||||
|
done
|
||||||
|
echo -n "]}}" >> $fjson
|
||||||
|
|
||||||
|
new "restconf large PUT"
|
||||||
|
expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -H "Expect: 100-continue" -d @$fjson $RCPROTO://localhost/restconf/data)" 0 "HTTP/$HVER 100 Continue" "HTTP/$HVER 201"
|
||||||
|
|
||||||
|
new "restconf PUT with expect"
|
||||||
|
expectpart "$(curl $CURLOPTS -H "Expect: 100-continue" -X POST -H "Content-Type: application/yang-data+json" -d '{"example:parameter":[{"name":"A","value":"42"}]}' $RCPROTO://localhost/restconf/data/example:table)" 0 "HTTP/$HVER 100 Continue" "HTTP/$HVER 201"
|
||||||
|
|
||||||
|
new "restconf GET"
|
||||||
|
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/example:table/parameter=A)" 0 "HTTP/$HVER 200" '{"example:parameter":\[{"name":"A","value":"42"}\]}'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Set by restconf_config
|
||||||
|
unset RESTCONFIG
|
||||||
|
unset nr
|
||||||
|
unset HAVE_LIBNGHTTP2
|
||||||
|
|
||||||
|
rm -rf $dir
|
||||||
|
|
||||||
|
new "endtest"
|
||||||
|
endtest
|
||||||
|
|
@ -20,6 +20,11 @@
|
||||||
# plugin should be visible in the error message.
|
# plugin should be visible in the error message.
|
||||||
# XXX does not test rpc-error from backend in api_return_err?
|
# XXX does not test rpc-error from backend in api_return_err?
|
||||||
|
|
||||||
|
# Override default to use http/1.1
|
||||||
|
# Force to HTTP 1.1 no SSL due to netcat
|
||||||
|
RCPROTO=http
|
||||||
|
HAVE_LIBNGHTTP2=false
|
||||||
|
|
||||||
# Magic line must be first in script (see README.md)
|
# Magic line must be first in script (see README.md)
|
||||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||||
|
|
||||||
|
|
@ -37,9 +42,6 @@ fyang2=$dir/augment.yang
|
||||||
fxml=$dir/initial.xml
|
fxml=$dir/initial.xml
|
||||||
fstate=$dir/state.xml
|
fstate=$dir/state.xml
|
||||||
|
|
||||||
RCPROTO=http # Force to http due to netcat
|
|
||||||
HVER=1.1
|
|
||||||
|
|
||||||
# Define default restconfig config: RESTCONFIG
|
# Define default restconfig config: RESTCONFIG
|
||||||
RESTCONFIG=$(restconf_config none false)
|
RESTCONFIG=$(restconf_config none false)
|
||||||
|
|
||||||
|
|
@ -203,15 +205,15 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR
|
||||||
# But leave it here for debugging where netcat works properly
|
# But leave it here for debugging where netcat works properly
|
||||||
# Alt try something like:
|
# Alt try something like:
|
||||||
# printf "Hello World!" | (exec 3<>/dev/tcp/127.0.0.1/80; cat >&3; cat <&3; exec 3<&-)
|
# printf "Hello World!" | (exec 3<>/dev/tcp/127.0.0.1/80; cat >&3; cat <&3; exec 3<&-)
|
||||||
if [ false -a ! ${HAVE_LIBNGHTTP2} ] ; then
|
|
||||||
# Look for netcat or nc for direct socket http calls
|
# Look for netcat or nc for direct socket http calls
|
||||||
if [ -n "$(type netcat 2> /dev/null)" ]; then
|
if [ -n "$(type netcat 2> /dev/null)" ]; then
|
||||||
netcat="netcat -w 1" # -N does not work on fcgi
|
netcat="netcat -w 1" # -N does not work on fcgi
|
||||||
elif [ -n "$(type nc 2> /dev/null)" ]; then
|
elif [ -n "$(type nc 2> /dev/null)" ]; then
|
||||||
netcat=nc
|
netcat=nc
|
||||||
else
|
else
|
||||||
err1 "netcat/nc not found"
|
netcat=
|
||||||
fi
|
fi
|
||||||
|
if [ -n "$netcat" ]; then
|
||||||
|
|
||||||
# new "restconf try fuzz crash"
|
# new "restconf try fuzz crash"
|
||||||
# expectpart "$(${netcat} 127.0.0.1 80 < ~/tmp/crashes/id:000000,sig:06,src:000493+000365,op:splice,rep:8)" 0 "HTTP/$HVER 400"
|
# expectpart "$(${netcat} 127.0.0.1 80 < ~/tmp/crashes/id:000000,sig:06,src:000493+000365,op:splice,rep:8)" 0 "HTTP/$HVER 400"
|
||||||
|
|
@ -236,12 +238,12 @@ EOF
|
||||||
|
|
||||||
new "restconf PUT not allowed"
|
new "restconf PUT not allowed"
|
||||||
expectpart "$(${netcat} 127.0.0.1 80 <<EOF
|
expectpart "$(${netcat} 127.0.0.1 80 <<EOF
|
||||||
PUT /restconf/.well-known/host-meta HTTP/$HVER
|
PUT /.well-known/host-meta HTTP/$HVER
|
||||||
Host: localhost
|
Host: localhost
|
||||||
Accept: application/yang-data+xml
|
Accept: application/yang-data+xml
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
)" 0 "HTTP/$HVER 405" kalle # nginx uses "method not allowed"
|
)" 0 "HTTP/$HVER 405" # nginx uses "method not allowed"
|
||||||
|
|
||||||
new "restconf GET wrong http version raw"
|
new "restconf GET wrong http version raw"
|
||||||
expectpart "$(${netcat} 127.0.0.1 80 <<EOF
|
expectpart "$(${netcat} 127.0.0.1 80 <<EOF
|
||||||
|
|
@ -252,7 +254,7 @@ Accept: application/yang-data+xml
|
||||||
EOF
|
EOF
|
||||||
)" 0 "HTTP/$HVER 400" # native: '<error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message>'
|
)" 0 "HTTP/$HVER 400" # native: '<error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message>'
|
||||||
|
|
||||||
fi # Http/1.1 and netcat Cannot get to work on all platforms
|
fi # netcat Cannot get to work on all platforms
|
||||||
|
|
||||||
new "restconf XYZ not found"
|
new "restconf XYZ not found"
|
||||||
expectpart "$(curl $CURLOPTS -X XYS -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 404"
|
expectpart "$(curl $CURLOPTS -X XYS -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 404"
|
||||||
|
|
@ -335,6 +337,7 @@ fi
|
||||||
unset RESTCONFIG
|
unset RESTCONFIG
|
||||||
unset HVER
|
unset HVER
|
||||||
unset RCPROTO
|
unset RCPROTO
|
||||||
|
unset HAVE_LIBNGHTTP2
|
||||||
|
|
||||||
rm -rf $dir
|
rm -rf $dir
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ function rpcoperation()
|
||||||
}
|
}
|
||||||
|
|
||||||
# This test is confusing:
|
# This test is confusing:
|
||||||
# The whole restconf config is in clixon-config wich binds 0.0.0.0:80 which will be the only
|
# The whole restconf config is in clixon-config which binds 0.0.0.0:80 which will be the only
|
||||||
# config the restconf daemon ever reads.
|
# config the restconf daemon ever reads.
|
||||||
# However, enable (and debug) flag is stored in running db but only backend will ever read that.
|
# However, enable (and debug) flag is stored in running db but only backend will ever read that.
|
||||||
# It just controls how restconf is started, but thereafter the restconf daemon reads the static db in clixon-config file
|
# It just controls how restconf is started, but thereafter the restconf daemon reads the static db in clixon-config file
|
||||||
|
|
|
||||||
|
|
@ -601,9 +601,9 @@ module clixon-config {
|
||||||
type boolean;
|
type boolean;
|
||||||
default false;
|
default false;
|
||||||
description
|
description
|
||||||
"Applies to plan (non-tls) http/2 ie when clixon is configured with --enable-nghttp2
|
"Applies to plain (non-tls) http/2 ie when clixon is configured with --enable-nghttp2
|
||||||
If false, disable direct and upgrade for plain(non-tls) HTTP/2.
|
If false, disable direct and upgrade for plain(non-tls) HTTP/2.
|
||||||
If true, allows direct and upgrade for plain(non-tls) HTTP/2.
|
If true, allow direct and upgrade for plain(non-tls) HTTP/2.
|
||||||
It may especially useful to disable in http/1 + http/2 mode to avoid the complex
|
It may especially useful to disable in http/1 + http/2 mode to avoid the complex
|
||||||
upgrade/switch from http/1 to http/2.
|
upgrade/switch from http/1 to http/2.
|
||||||
Note this also disables plain http/2 in prior-knowledge, that is, in http/2-only mode.
|
Note this also disables plain http/2 in prior-knowledge, that is, in http/2-only mode.
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,8 @@ module clixon-restconf {
|
||||||
}
|
}
|
||||||
list socket {
|
list socket {
|
||||||
description
|
description
|
||||||
"List of server sockets that the restconf daemon listens to";
|
"List of server sockets that the restconf daemon listens to.
|
||||||
|
Not fcgi";
|
||||||
key "namespace address port";
|
key "namespace address port";
|
||||||
leaf namespace {
|
leaf namespace {
|
||||||
type string;
|
type string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue