* 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_patch.c
|
||||
APPSRC += restconf_root.c
|
||||
APPSRC += clixon_http1.c
|
||||
APPSRC += restconf_main_$(with_restconf).c
|
||||
ifeq ($(with_restconf),native)
|
||||
APPSRC += restconf_http1.c
|
||||
APPSRC += restconf_native.c
|
||||
APPSRC += restconf_nghttp2.c # HTTP/2
|
||||
endif
|
||||
|
|
@ -128,8 +128,6 @@ LIBSRC += restconf_api_$(with_restconf).c
|
|||
|
||||
LIBOBJ = $(LIBSRC:.c=.o)
|
||||
|
||||
|
||||
|
||||
# This lib is very small but used for clixon restconf applications to access clixon restconf lib
|
||||
# functions. Mostly for future use
|
||||
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 */
|
||||
char *hy_parse_string; /* original (copy of) parse string */
|
||||
void *hy_lexbuf; /* internal parse buffer from lex */
|
||||
void *hy_top;
|
||||
};
|
||||
typedef struct clixon_http1_yacc clixon_http1_yacc;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
|
|
@ -93,46 +94,57 @@ query [A-Za-z0-9\-\._~!$&'()*+,;=:@?/]|%[0-9a-fA-F][0-9a-fA-F]
|
|||
%x REQHTTP
|
||||
%x FLDNAME
|
||||
%x FLDVALUE
|
||||
%x BODYM
|
||||
|
||||
%%
|
||||
<REQLINE,REQTARG,REQUERY,REQHTTP,FLDNAME,FLDVALUE><<EOF>> { return X_EOF; }
|
||||
<REQLINE>[ ] { BEGIN(REQTARG); return SP; }
|
||||
<REQLINE>{token} { clixon_http1_parselval.string = yytext;
|
||||
<REQLINE>{token} { clixon_http1_parselval.string = strdup(yytext);
|
||||
return TOKEN; }
|
||||
<REQLINE>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||
|
||||
<REQTARG>\? { BEGIN(REQUERY); return QMARK; }
|
||||
<REQTARG>\/ { return SLASH; }
|
||||
<REQTARG>[ ] { return SP; }
|
||||
<REQTARG>HTTP { BEGIN(REQHTTP); return HTTP; }
|
||||
<REQUERY>{pchar}+ { clixon_http1_parselval.string = yytext;
|
||||
<REQTARG>[ ] { BEGIN(REQHTTP); return SP; }
|
||||
<REQTARG>{pchar}+ { clixon_http1_parselval.string = yytext;
|
||||
return PCHARS; }
|
||||
<REQTARG>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||
|
||||
<REQUERY>\/ { return SLASH; }
|
||||
<REQUERY>[ ] { return SP; }
|
||||
<REQUERY>HTTP { BEGIN(REQHTTP); return HTTP; }
|
||||
<REQUERY>{query}+ { clixon_http1_parselval.string = yytext;
|
||||
<REQUERY>[ ] { BEGIN(REQHTTP); return SP; }
|
||||
<REQUERY>{query}+ { clixon_http1_parselval.string = strdup(yytext);
|
||||
return QUERY; }
|
||||
<REQUERY>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||
|
||||
<REQHTTP>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
||||
<REQHTTP>\/ { return SLASH; }
|
||||
<REQHTTP>\. { return DOT; }
|
||||
<REQHTTP>HTTP { BEGIN(REQHTTP); return HTTP; }
|
||||
<REQHTTP>[0-9] { clixon_http1_parselval.intval = atoi(yytext);
|
||||
return DIGIT; }
|
||||
<REQHTTP>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||
|
||||
<FLDNAME>: { BEGIN(FLDVALUE); return COLON; }
|
||||
<FLDNAME>\r\n { return CRLF; _HY->hy_linenum++; }
|
||||
<FLDNAME>[ \t\n]+ { return RWS; }
|
||||
<FLDNAME>{token} { clixon_http1_parselval.string = yytext;
|
||||
<FLDNAME>\r\n { BEGIN(BODYM); return CRLF; _HY->hy_linenum++; }
|
||||
<FLDNAME>[ \t]+ { return RWS; }
|
||||
<FLDNAME>{token} { clixon_http1_parselval.string = strdup(yytext);
|
||||
return TOKEN; }
|
||||
<FLDNAME>. { clixon_http1_parseerror(_HY, "LEXICAL ERROR\n"); return -1; }
|
||||
|
||||
<FLDVALUE>\r\n { BEGIN(FLDNAME); return CRLF; _HY->hy_linenum++; }
|
||||
<FLDVALUE>[ \t\n]+ { return RWS; }
|
||||
<FLDVALUE>. { clixon_http1_parselval.string = yytext;
|
||||
return VCHAR; }
|
||||
<FLDVALUE>[ \t]+ { return RWS; }
|
||||
<FLDVALUE>[^ \t\n\r]+ { clixon_http1_parselval.string = strdup(yytext);
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
|
||||
* HTTP/1.1 parser according to RFC 7230 Appendix B
|
||||
* XXX field_values : field_values field_vchars : Only handle one field
|
||||
*/
|
||||
|
||||
%start http_message
|
||||
|
|
@ -51,15 +52,20 @@
|
|||
%token HTTP
|
||||
%token COLON
|
||||
%token X_EOF
|
||||
%token ERROR
|
||||
|
||||
%token <string> PCHARS
|
||||
%token <string> QUERY
|
||||
%token <string> TOKEN
|
||||
%token <string> VCHAR
|
||||
%token <string> VCHARS
|
||||
%token <string> BODY
|
||||
%token <intval> DIGIT
|
||||
|
||||
%type <string> body
|
||||
%type <string> absolute_paths
|
||||
%type <string> absolute_path
|
||||
%type <string> field_vchars
|
||||
%type <string> field_values
|
||||
|
||||
%lex-param {void *_hy} /* Add this argument to parse() and lex() function */
|
||||
%parse-param {void *_hy}
|
||||
|
|
@ -81,6 +87,7 @@
|
|||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
|
|
@ -142,11 +149,50 @@ http1_parse_query(clixon_http1_yacc *hy,
|
|||
int retval = -1;
|
||||
restconf_stream_data *sd = NULL;
|
||||
|
||||
if ((sd = restconf_stream_find(hy->hy_rc, 0)) == NULL)
|
||||
goto ok;
|
||||
clicon_debug(1, "%s: ?%s ", __FUNCTION__, query);
|
||||
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)
|
||||
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;
|
||||
done:
|
||||
return retval;
|
||||
|
|
@ -159,14 +205,34 @@ http1_parse_query(clixon_http1_yacc *hy,
|
|||
/* start-line *( header-field CRLF ) CRLF [ message-body ]
|
||||
* start-line = request-line / status-line (only request-line here, ignore status-line)
|
||||
*/
|
||||
http_message : request_line header_fields CRLF
|
||||
{ _HY->hy_top=NULL; _PARSE_DEBUG("http-message -> request-line header-fields ACCEPT"); YYACCEPT; }
|
||||
http_message : request_line header_fields CRLF body
|
||||
{
|
||||
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 */
|
||||
request_line : method SP request_target SP HTTP_version CRLF
|
||||
body : body BODY
|
||||
{
|
||||
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)
|
||||
YYABORT;
|
||||
free($1);
|
||||
_PARSE_DEBUG("method -> TOKEN");
|
||||
}
|
||||
;
|
||||
|
||||
/* 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
|
||||
{
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||
YYABORT;
|
||||
free($1);
|
||||
_PARSE_DEBUG("request-target -> absolute-paths");
|
||||
}
|
||||
| absolute_paths QMARK QUERY
|
||||
{
|
||||
if (restconf_param_set(_HY->hy_h, "REQUEST_URI", $1) < 0)
|
||||
YYABORT;
|
||||
if (http1_parse_query(_HY->hy_h, $3) < 0)
|
||||
free($1);
|
||||
if (http1_parse_query(_HY, $3) < 0)
|
||||
YYABORT;
|
||||
free($3);
|
||||
_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_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");
|
||||
}
|
||||
| absolute_path
|
||||
{ $$ = strdup($1);
|
||||
{
|
||||
if (($$ = clixon_string_del_join(NULL, "/", $1)) == NULL) { free($1); YYABORT;}
|
||||
free($1);
|
||||
_PARSE_DEBUG("absolute-paths -> absolute -path");
|
||||
}
|
||||
;
|
||||
|
|
@ -236,11 +298,21 @@ absolute_paths : absolute_paths absolute_path
|
|||
* / "*" / "+" / "," / ";" / "="
|
||||
*/
|
||||
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
|
||||
{ _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
|
||||
|
|
@ -250,29 +322,41 @@ header_fields : header_fields header_field CRLF
|
|||
| { _PARSE_DEBUG("header-fields -> "); }
|
||||
;
|
||||
|
||||
/* header-field = field-name ":" OWS field-value OWS */
|
||||
header_field : field_name COLON ows field_values ows
|
||||
{ _PARSE_DEBUG("header-field -> field-name : field-values"); }
|
||||
/* header-field = field-name ":" OWS field-value OWS
|
||||
field-name = token */
|
||||
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_name : TOKEN { _PARSE_DEBUG("field-name -> TOKEN"); }
|
||||
;
|
||||
|
||||
/* field-value = *( field-content / obs-fold ) */
|
||||
field_values : field_values field_content
|
||||
{ _PARSE_DEBUG("field-values -> field-values field-content"); }
|
||||
/* field-value = *( field-content / obs-fold )
|
||||
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
|
||||
field-vchar = VCHAR / obs-text */
|
||||
field_values : field_vchars
|
||||
{
|
||||
$$ = $1; // XXX is there more than one??
|
||||
_PARSE_DEBUG("field-values -> field-values field-vchars");
|
||||
}
|
||||
| { _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 VCHAR
|
||||
{ _PARSE_DEBUG("field-vchars -> field-vchars VCHAR"); }
|
||||
| VCHAR { _PARSE_DEBUG("field-vchars -> VCHAR"); }
|
||||
field_vchars : field_vchars RWS VCHARS
|
||||
{
|
||||
if (($$ = clixon_string_del_join($1, " ", $3)) == NULL) YYABORT;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ restconf_param_set(clicon_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 = clicon_hash_init()) == NULL)
|
||||
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
|
||||
*/
|
||||
#ifndef _CLIXON_HTTP1_H_
|
||||
#define _CLIXON_HTTP1_H_
|
||||
#ifndef _RESTCONF_HTTP1_H_
|
||||
#define _RESTCONF_HTTP1_H_
|
||||
|
||||
/*
|
||||
* 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_buf(clicon_handle h, restconf_conn *rc, char *buf, size_t n);
|
||||
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/x509v3.h>
|
||||
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#endif
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
/* libclixon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
/* nghttp2 */
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#endif
|
||||
|
||||
/* restconf */
|
||||
#include "restconf_lib.h" /* generic shared with plugins */
|
||||
#include "restconf_handle.h"
|
||||
|
|
@ -149,7 +148,7 @@
|
|||
#include "restconf_nghttp2.h" /* http/2 */
|
||||
#endif
|
||||
#ifdef HAVE_HTTP1
|
||||
#include "clixon_http1.h"
|
||||
#include "restconf_http1.h"
|
||||
#endif
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
|
|
@ -170,9 +169,6 @@
|
|||
/* Cert verify depth: dont know what to set here? */
|
||||
#define VERIFY_DEPTH 5
|
||||
|
||||
/* Forward */
|
||||
static int restconf_connection(int s, void* arg);
|
||||
|
||||
static int session_id_context = 1;
|
||||
|
||||
/*! Get restconf native handle
|
||||
|
|
@ -209,98 +205,6 @@ restconf_native_handle_set(clicon_handle h,
|
|||
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
|
||||
*/
|
||||
static int
|
||||
|
|
@ -596,324 +500,6 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that
|
|||
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 */
|
||||
/*! Debug print all loaded certs
|
||||
*/
|
||||
|
|
@ -1019,7 +605,7 @@ ssl_alpn_check(clicon_handle h,
|
|||
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);
|
||||
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",
|
||||
cbuf_get(cberr)) < 0)
|
||||
goto done;
|
||||
|
|
@ -1160,7 +746,7 @@ restconf_accept_client(int fd,
|
|||
case SSL_ERROR_SSL: /* 1 */
|
||||
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
|
||||
#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)
|
||||
goto done;
|
||||
#endif
|
||||
|
|
@ -1243,7 +829,7 @@ restconf_accept_client(int fd,
|
|||
}
|
||||
else { /* Get certificates (if available) */
|
||||
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)
|
||||
goto done;
|
||||
restconf_conn_free(rc);
|
||||
|
|
@ -1315,10 +901,6 @@ restconf_accept_client(int fd,
|
|||
default:
|
||||
break;
|
||||
} /* 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)
|
||||
goto done;
|
||||
ok:
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ api_data_post(clicon_handle h,
|
|||
* expected data resource. (tested again below)
|
||||
*/
|
||||
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;
|
||||
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -55,23 +55,32 @@
|
|||
#include <openssl/rand.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#endif
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clicon */
|
||||
/* libclixon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
/* restconf */
|
||||
#include "restconf_lib.h" /* generic shared with plugins */
|
||||
#include "restconf_handle.h"
|
||||
#include "restconf_err.h"
|
||||
|
||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||
#ifdef HAVE_LIBNGHTTP2
|
||||
#include <nghttp2/nghttp2.h>
|
||||
#include "restconf_nghttp2.h" /* http/2 */
|
||||
#endif
|
||||
#ifdef HAVE_HTTP1
|
||||
#include "restconf_http1.h"
|
||||
#endif
|
||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||
|
||||
/*!
|
||||
* @see restconf_stream_free
|
||||
*/
|
||||
restconf_stream_data *
|
||||
restconf_stream_data_new(restconf_conn *rc,
|
||||
int32_t stream_id)
|
||||
|
|
@ -297,3 +306,400 @@ restconf_connection_sanity(clicon_handle h,
|
|||
xml_free(xerr);
|
||||
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
|
||||
*/
|
||||
typedef struct restconf_conn {
|
||||
// qelem_t rs_qelem; /* List header */
|
||||
size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */
|
||||
/* XXX rc_proto and rc_proto_d1/d2 may not both be necessary.
|
||||
* remove rc_proto?
|
||||
*/
|
||||
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 */
|
||||
clicon_handle rc_h; /* Clixon handle */
|
||||
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_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_ */
|
||||
|
||||
|
|
|
|||
|
|
@ -451,6 +451,10 @@ http2_exec(restconf_conn *rc,
|
|||
int retval = -1;
|
||||
|
||||
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)
|
||||
goto done;
|
||||
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue