296 lines
9.7 KiB
C
296 lines
9.7 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2020-2024 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 *****
|
|
|
|
* nghttp2 callback mechanism
|
|
*
|
|
* nghttp2_session_mem_recv()
|
|
* on_begin_headers_callback()
|
|
* create sd
|
|
* on_header_callback() NGHTTP2_HEADERS
|
|
* translate all headers
|
|
* on_data_chunk_recv_callback
|
|
* get indata
|
|
* on_frame_recv_callback NGHTTP2_FLAG_END_STREAM
|
|
* get method and call handler
|
|
* create rr
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <pwd.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/x509v3.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clixon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#ifdef HAVE_LIBNGHTTP2
|
|
#include <nghttp2/nghttp2.h>
|
|
#endif
|
|
|
|
/* restconf */
|
|
#include "restconf_lib.h" /* generic shared with plugins */
|
|
#include "restconf_handle.h"
|
|
#include "restconf_api.h" /* generic not shared with plugins */
|
|
#include "restconf_err.h"
|
|
#include "restconf_root.h"
|
|
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
|
#include "restconf_stream.h"
|
|
|
|
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
|
|
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
|
|
|
/*! Callback when stream notifications arrive from backend
|
|
*
|
|
* @param[in] s Socket
|
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see netconf_notification_cb
|
|
*/
|
|
static int
|
|
restconf_native_stream_cb(int s,
|
|
void *arg)
|
|
{
|
|
int retval = -1;
|
|
int eof;
|
|
cxobj *xtop = NULL; /* top xml */
|
|
cxobj *xn; /* notification xml */
|
|
cbuf *cb = NULL;
|
|
cbuf *cbmsg = NULL;
|
|
int pretty = 0; /* XXX should be via arg */
|
|
int ret;
|
|
|
|
clixon_debug(CLIXON_DBG_STREAM, "");
|
|
if (clixon_msg_rcv11(s, NULL, 0, &cbmsg, &eof) < 0)
|
|
goto done;
|
|
clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg));
|
|
/* handle close from remote end: this will exit the client */
|
|
if (eof){
|
|
clixon_debug(CLIXON_DBG_STREAM, "eof");
|
|
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
|
|
errno = ESHUTDOWN;
|
|
clixon_exit_set(1);
|
|
goto done;
|
|
}
|
|
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_err(OE_XML, EFAULT, "Invalid notification");
|
|
goto done;
|
|
}
|
|
/* create event */
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_PLUGIN, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if ((xn = xpath_first(xtop, NULL, "notification")) == NULL)
|
|
goto ok;
|
|
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
|
|
goto done;
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_STREAM, "retval: %d", retval);
|
|
if (xtop != NULL)
|
|
xml_free(xtop);
|
|
if (cbmsg)
|
|
cbuf_free(cbmsg);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! Process a stream request, native variant
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
|
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
|
* @param[out] finish Not used in native?
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see api_stream fcgi implementation
|
|
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
|
|
* is XML according to RFC5277. But what is error return? assume XML here
|
|
*/
|
|
int
|
|
api_stream(clixon_handle h,
|
|
void *req,
|
|
cvec *qvec,
|
|
int *finish)
|
|
{
|
|
int retval = -1;
|
|
char *path = NULL;
|
|
char *request_method = NULL; /* GET,.. */
|
|
char *streampath;
|
|
int pretty;
|
|
char **pvec = NULL;
|
|
int pn;
|
|
cvec *pcvec = NULL; /* for rest api */
|
|
cxobj *xerr = NULL;
|
|
char *media_str = NULL;
|
|
char *stream_name;
|
|
restconf_media media_stream = TEXT_EVENT_STREAM; /* text/event-stream, see RFC8040 sec 6 */
|
|
restconf_media media_out = YANG_DATA_XML;
|
|
int ret;
|
|
int backend_socket = -1;
|
|
|
|
fprintf(stderr, "%s\n", __FUNCTION__);
|
|
clixon_debug(CLIXON_DBG_STREAM, "");
|
|
if (req == NULL){
|
|
errno = EINVAL;
|
|
goto done;
|
|
}
|
|
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
|
if ((path = restconf_uripath(h)) == NULL)
|
|
goto done;
|
|
clixon_debug(CLIXON_DBG_STREAM, "path:%s", path);
|
|
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
|
clixon_debug(CLIXON_DBG_STREAM, "method:%s", request_method);
|
|
pretty = restconf_pretty_get(h);
|
|
clixon_debug(CLIXON_DBG_STREAM, "pretty:%d", pretty);
|
|
/* Get media for output (proactive negotiation) RFC7231 by using
|
|
* Accept:. This is for methods that have output, such as GET,
|
|
* operation POST, etc
|
|
* If accept is * default is yang-json
|
|
*/
|
|
media_str = restconf_param_get(h, "HTTP_ACCEPT");
|
|
clixon_debug(CLIXON_DBG_STREAM, "accept(media):%s", media_str);
|
|
if (media_str == NULL){
|
|
if (restconf_not_acceptable(h, req, pretty, media_out) < 0)
|
|
goto done;
|
|
goto ok;
|
|
}
|
|
media_stream = restconf_media_str2int(media_str);
|
|
clixon_debug(CLIXON_DBG_STREAM, "media_out:%s", restconf_media_int2str(media_stream));
|
|
switch ((int)media_stream){
|
|
case -1:
|
|
if (strcmp(media_str, "*/*") == 0){ /* catch-all */
|
|
media_out = TEXT_EVENT_STREAM;
|
|
}
|
|
else{
|
|
if (restconf_not_acceptable(h, req, pretty, media_out) < 0)
|
|
goto done;
|
|
goto ok;
|
|
}
|
|
break;
|
|
case TEXT_EVENT_STREAM:
|
|
break;
|
|
default:
|
|
if (restconf_not_acceptable(h, req, pretty, media_out) < 0)
|
|
goto done;
|
|
goto ok;
|
|
break;
|
|
}
|
|
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
|
|
goto done;
|
|
if (strlen(pvec[0]) != 0){
|
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
|
goto done;
|
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
|
goto done;
|
|
goto ok;
|
|
}
|
|
else if (strcmp(pvec[1], streampath)){
|
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
|
goto done;
|
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
|
goto done;
|
|
goto ok;
|
|
}
|
|
else if ((stream_name = pvec[2]) == NULL ||
|
|
strlen(stream_name) == 0){
|
|
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
|
|
goto done;
|
|
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
|
|
goto done;
|
|
goto ok;
|
|
}
|
|
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
|
|
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
|
|
goto done;
|
|
/* If present, check credentials. See "plugin_credentials" in plugin
|
|
* See RFC 8040 section 2.5
|
|
*/
|
|
if ((ret = restconf_authentication_cb(h, req, pretty, media_out)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto ok;
|
|
clixon_debug(CLIXON_DBG_STREAM, "passed auth");
|
|
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_out, &backend_socket) < 0)
|
|
goto done;
|
|
if (backend_socket != -1){
|
|
// XXX Could add forking here eventurally
|
|
/* Listen to backend socket */
|
|
if (clixon_event_reg_fd(backend_socket,
|
|
restconf_native_stream_cb,
|
|
req,
|
|
"stream socket") < 0)
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
fprintf(stderr, "%s retval %d\n", __FUNCTION__, retval);
|
|
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
|
if (xerr)
|
|
xml_free(xerr);
|
|
if (path)
|
|
free(path);
|
|
if (pvec)
|
|
free(pvec);
|
|
if (pcvec)
|
|
cvec_free(pcvec);
|
|
return retval;
|
|
}
|
|
|
|
#endif /* HAVE_LIBNGHTTP2 */
|