All clixon test utilities in util/ moved to separate repo: clicon/clixon-util

This commit is contained in:
Olof hagsand 2023-12-01 14:30:14 +01:00
parent 631ebaa759
commit 80a10b694c
29 changed files with 166 additions and 17293 deletions

View file

@ -48,6 +48,8 @@ Expected: December 2023
### API changes on existing protocol/config features ### API changes on existing protocol/config features
Users may have to change how they access the system Users may have to change how they access the system
* All clixon test utilities in util/ have been moved to a separate repo: clicon/clixon-util
* To run tests you need to clone, build and install them separately
* Moved and split install of main example config file * Moved and split install of main example config file
* From `/usr/local/etc/example.xml` to `/usr/local/etc/clixon/example.xml` * From `/usr/local/etc/example.xml` to `/usr/local/etc/clixon/example.xml`
* Added `/usr/local/etc/clixon/example/autocli.xml` and `/usr/local/etc/clixon/example/restconf.xml` * Added `/usr/local/etc/clixon/example/autocli.xml` and `/usr/local/etc/clixon/example/restconf.xml`
@ -2128,7 +2130,6 @@ xpath_first_nsc` are removed).
* Mandatory variables can no longer be deleted. * Mandatory variables can no longer be deleted.
* [Add missing includes](https://github.com/clicon/clixon/pulls) * [Add missing includes](https://github.com/clicon/clixon/pulls)
## 4.3.1 ## 4.3.1
2 February 2020 2 February 2020

View file

@ -59,11 +59,11 @@ SUBDIRS2 = apps etc yang # without include lib for circular dependency
SUBDIRS= $(SUBDIRS1) $(SUBDIRS2) SUBDIRS= $(SUBDIRS1) $(SUBDIRS2)
.PHONY: doc example install-example clean-example all clean depend $(SUBDIRS) \ .PHONY: doc example install-example clean-example all clean depend $(SUBDIRS) \
install loc TAGS .config.status docker test util checkroot mrproper \ install loc TAGS .config.status docker test checkroot mrproper \
checkinstall warnroot install-util clean-util checkinstall warnroot
all: $(SUBDIRS2) warnroot all: $(SUBDIRS2) warnroot
@echo "\e[32mAfter 'make install' as euid root, build example app and test utils: 'make example'\e[0m" @echo "\e[32mAfter 'make install' as euid root, build example app: 'make example'\e[0m"
checkroot: checkroot:
@if command -v id &> /dev/null; then \ @if command -v id &> /dev/null; then \
@ -84,8 +84,6 @@ checkinstall:
echo "\e[31mclixon must be installed first to build this target. "\ echo "\e[31mclixon must be installed first to build this target. "\
"Run 'make'. Then run 'make install' as root.\e[0m"; exit 1; fi; "Run 'make'. Then run 'make install' as root.\e[0m"; exit 1; fi;
util: apps
# May cause circular include->include,lib # May cause circular include->include,lib
$(SUBDIRS2): $(SUBDIRS1) # Cannot build app before lib (for parallel make -j) $(SUBDIRS2): $(SUBDIRS1) # Cannot build app before lib (for parallel make -j)
(cd $@ && $(MAKE) $(MFLAGS) all) (cd $@ && $(MAKE) $(MFLAGS) all)
@ -106,37 +104,25 @@ install: checkroot
install-include: install-include:
for i in $(SUBDIRS) doc; \ for i in $(SUBDIRS) doc; \
do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done; do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done;
@echo "\e[32mTo install example app and test utils: make install-example\e[0m" @echo "\e[32mTo install example app: make install-example\e[0m"
uninstall: checkroot uninstall: checkroot
for i in $(SUBDIRS) doc example util docker; \ for i in $(SUBDIRS) doc example docker; \
do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done; do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done;
doc: warnroot doc: warnroot
cd $@; $(MAKE) $(MFLAGS) $@ cd $@; $(MAKE) $(MFLAGS) $@
util:
cd $@; $(MAKE) $(MFLAGS)
clean-util:
cd util; $(MAKE) $(MFLAGS) clean
install-util: checkroot install-include
cd util; $(MAKE) $(MFLAGS) install
uninstall-util:
cd util; $(MAKE) $(MFLAGS) uninstall
clean-example: clean-example:
for i in example util; \ for i in example; \
do (cd $$i && $(MAKE) $(MFLAGS) clean) || exit 1; done; do (cd $$i && $(MAKE) $(MFLAGS) clean) || exit 1; done;
install-example: checkroot install-example: checkroot
for i in example util; \ for i in example; \
do (cd $$i && $(MAKE) $(MFLAGS) install) || exit 1; done; do (cd $$i && $(MAKE) $(MFLAGS) install) || exit 1; done;
uninstall-example: checkroot uninstall-example: checkroot
for i in example util; \ for i in example; \
do (cd $$i && $(MAKE) $(MFLAGS) uninstall) || exit 1; done; do (cd $$i && $(MAKE) $(MFLAGS) uninstall) || exit 1; done;
config.status: configure config.status: configure
@ -146,23 +132,23 @@ configure: configure.ac
cd $(srcdir) && autoconf cd $(srcdir) && autoconf
clean: clean:
for i in $(SUBDIRS) doc example util docker; \ for i in $(SUBDIRS) doc example docker; \
do (cd $$i && $(MAKE) $(MFLAGS) $@); done; do (cd $$i && $(MAKE) $(MFLAGS) $@); done;
rm -f *.gcov test/*.gcov rm -f *.gcov test/*.gcov
# Uninstall and clean all the targets used for testing, but without cloning or # Uninstall and clean all the targets used for testing, but without cloning or
# checking-out from git. Provides a reliabily clean slate for testing changes # checking-out from git. Provides a reliabily clean slate for testing changes
# before commit. # before commit.
mrproper: uninstall uninstall-example uninstall-util clean clean-example clean-util mrproper: uninstall uninstall-example clean clean-example
distclean: distclean:
rm -f Makefile TAGS config.status config.log *~ .depend rm -f Makefile TAGS config.status config.log *~ .depend
rm -rf autom4te.cache rm -rf autom4te.cache
for i in $(SUBDIRS) doc example util docker; \ for i in $(SUBDIRS) doc example docker; \
do (cd $$i && $(MAKE) $(MFLAGS) $@); done do (cd $$i && $(MAKE) $(MFLAGS) $@); done
# To make the example you need to run the "install-include" target first # To make the example you need to run the "install-include" target first
example: checkinstall util warnroot example: checkinstall warnroot
(cd $@ && $(MAKE) $(MFLAGS) all) (cd $@ && $(MAKE) $(MFLAGS) all)
@echo "\e[36mRemember to run 'make install-example' as euid root\e[0m" @echo "\e[36mRemember to run 'make install-example' as euid root\e[0m"

File diff suppressed because it is too large Load diff

1
config-aux/ltmain.sh Symbolic link
View file

@ -0,0 +1 @@
/usr/share/libtool/build-aux/ltmain.sh

54
configure vendored
View file

@ -654,7 +654,6 @@ CLIGEN_DIR
WC_BIN WC_BIN
TAIL_BIN TAIL_BIN
GREP GREP
SSHD_BIN
SSH_BIN SSH_BIN
LEXLIB LEXLIB
LEX_OUTPUT_ROOT LEX_OUTPUT_ROOT
@ -5736,56 +5735,6 @@ fi
printf "%s\n" "#define SSH_BIN \"${SSH_BIN}\"" >>confdefs.h printf "%s\n" "#define SSH_BIN \"${SSH_BIN}\"" >>confdefs.h
# SSHD binary path for test
# Extract the first word of "sshd", so it can be a program name with args.
set dummy sshd; ac_word=$2
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
printf %s "checking for $ac_word... " >&6; }
if test ${ac_cv_path_SSHD_BIN+y}
then :
printf %s "(cached) " >&6
else $as_nop
case $SSHD_BIN in
[\\/]* | ?:[\\/]*)
ac_cv_path_SSHD_BIN="$SSHD_BIN" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
case $as_dir in #(((
'') as_dir=./ ;;
*/) ;;
*) as_dir=$as_dir/ ;;
esac
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then
ac_cv_path_SSHD_BIN="$as_dir$ac_word$ac_exec_ext"
printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
SSHD_BIN=$ac_cv_path_SSHD_BIN
if test -n "$SSHD_BIN"; then
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SSHD_BIN" >&5
printf "%s\n" "$SSHD_BIN" >&6; }
else
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5
printf "%s\n" "no" >&6; }
fi
printf "%s\n" "#define SSHD_BIN \"${SSHD_BIN}\"" >>confdefs.h
# For cli pipe output functions # For cli pipe output functions
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
printf %s "checking for grep that handles long lines and -e... " >&6; } printf %s "checking for grep that handles long lines and -e... " >&6; }
@ -7012,7 +6961,7 @@ fi
test "x$prefix" = xNONE && prefix=$ac_default_prefix test "x$prefix" = xNONE && prefix=$ac_default_prefix
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/snmp/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile example/main/example.xml docker/Makefile docker/clixon-dev/Makefile docker/example/Makefile docker/test/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile doc/Makefile test/Makefile test/config.sh test/cicd/Makefile test/vagrant/Makefile" ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/snmp/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile example/main/example.xml docker/Makefile docker/clixon-dev/Makefile docker/example/Makefile docker/test/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile doc/Makefile test/Makefile test/config.sh test/cicd/Makefile test/vagrant/Makefile"
cat >confcache <<\_ACEOF cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # This file is a shell script that caches the results of configure
@ -7721,7 +7670,6 @@ do
"docker/clixon-dev/Makefile") CONFIG_FILES="$CONFIG_FILES docker/clixon-dev/Makefile" ;; "docker/clixon-dev/Makefile") CONFIG_FILES="$CONFIG_FILES docker/clixon-dev/Makefile" ;;
"docker/example/Makefile") CONFIG_FILES="$CONFIG_FILES docker/example/Makefile" ;; "docker/example/Makefile") CONFIG_FILES="$CONFIG_FILES docker/example/Makefile" ;;
"docker/test/Makefile") CONFIG_FILES="$CONFIG_FILES docker/test/Makefile" ;; "docker/test/Makefile") CONFIG_FILES="$CONFIG_FILES docker/test/Makefile" ;;
"util/Makefile") CONFIG_FILES="$CONFIG_FILES util/Makefile" ;;
"yang/Makefile") CONFIG_FILES="$CONFIG_FILES yang/Makefile" ;; "yang/Makefile") CONFIG_FILES="$CONFIG_FILES yang/Makefile" ;;
"yang/clixon/Makefile") CONFIG_FILES="$CONFIG_FILES yang/clixon/Makefile" ;; "yang/clixon/Makefile") CONFIG_FILES="$CONFIG_FILES yang/clixon/Makefile" ;;
"yang/mandatory/Makefile") CONFIG_FILES="$CONFIG_FILES yang/mandatory/Makefile" ;; "yang/mandatory/Makefile") CONFIG_FILES="$CONFIG_FILES yang/mandatory/Makefile" ;;

View file

@ -155,10 +155,6 @@ fi
AC_PATH_PROG(SSH_BIN, ssh) AC_PATH_PROG(SSH_BIN, ssh)
AC_DEFINE_UNQUOTED(SSH_BIN, "${SSH_BIN}", [SSH binary]) AC_DEFINE_UNQUOTED(SSH_BIN, "${SSH_BIN}", [SSH binary])
# SSHD binary path for test
AC_PATH_PROG(SSHD_BIN, sshd)
AC_DEFINE_UNQUOTED(SSHD_BIN, "${SSHD_BIN}", [SSHD binary])
# For cli pipe output functions # For cli pipe output functions
AC_PROG_GREP AC_PROG_GREP
AC_DEFINE_UNQUOTED(GREP_BIN, "$GREP", [Grep binary]) AC_DEFINE_UNQUOTED(GREP_BIN, "$GREP", [Grep binary])
@ -482,7 +478,6 @@ AC_CONFIG_FILES([Makefile
docker/clixon-dev/Makefile docker/clixon-dev/Makefile
docker/example/Makefile docker/example/Makefile
docker/test/Makefile docker/test/Makefile
util/Makefile
yang/Makefile yang/Makefile
yang/clixon/Makefile yang/clixon/Makefile
yang/mandatory/Makefile yang/mandatory/Makefile

View file

@ -81,8 +81,11 @@ RUN ./configure --prefix=/usr/local --sysconfdir=/etc --with-cligen=/clixon/buil
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install
# Install utils (for tests) # Configure, build and install clixon utilities (for tests)
WORKDIR /clixon/clixon/util WORKDIR /clixon
RUN git clone https://github.com/clicon/clixon-util.git
WORKDIR /clixon/clixon-util
RUN ./configure --prefix=/usr/local --with-cligen=/clixon/build/usr/local --with-clixon=/clixon/build/usr/local
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install

View file

@ -89,8 +89,11 @@ RUN ./configure --prefix=/usr/local --sysconfdir=/etc --with-cligen=/clixon/buil
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install
# Install utils (for tests) # Configure, build and install clixon utilities (for tests)
WORKDIR /clixon/clixon/util WORKDIR /clixon
RUN git clone https://github.com/clicon/clixon-util.git
WORKDIR /clixon/clixon-util
RUN ./configure --prefix=/usr/local --with-cligen=/clixon/build/usr/local --with-clixon=/clixon/build/usr/local
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install

View file

@ -87,8 +87,11 @@ RUN ./configure --prefix=/usr/local --sysconfdir=/etc --with-cligen=/clixon/buil
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install
# Install utils (for tests) # Configure, build and install clixon utilities (for tests)
WORKDIR /clixon/clixon/util WORKDIR /clixon
RUN git clone https://github.com/clicon/clixon-util.git
WORKDIR /clixon/clixon-util
RUN ./configure --prefix=/usr/local --with-cligen=/clixon/build/usr/local --with-clixon=/clixon/build/usr/local
RUN make RUN make
RUN make DESTDIR=/clixon/build install RUN make DESTDIR=/clixon/build install

View file

@ -171,9 +171,6 @@
/* Default restconf network namespace */ /* Default restconf network namespace */
#undef RESTCONF_NETNS_DEFAULT #undef RESTCONF_NETNS_DEFAULT
/* SSHD binary */
#undef SSHD_BIN
/* SSH binary */ /* SSH binary */
#undef SSH_BIN #undef SSH_BIN

View file

@ -55,9 +55,11 @@ There are also [manual cicd scripts here](cicd/README.md)
## Getting started ## Getting started
You need to build and install the clixon utility programs before running the tests as some of the tests rely on them: You need to build and install the clixon utility programs from a separate repo
before running the tests as some of the tests rely on them:
``` ```
cd util git clone https://github.com/clicon/clixon-util.git
./configure
make make
sudo make install sudo make install
``` ```

View file

@ -1,201 +0,0 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
# Copyright (C) 2017-2019 Olof Hagsand
# 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 *****
#
prefix = @prefix@
datarootdir = @datarootdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
exec_prefix = @exec_prefix@
bindir = @bindir@
libdir = @libdir@
dbdir = @prefix@/db
mandir = @mandir@
libexecdir = @libexecdir@
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
HOST_VENDOR = @host_vendor@
with_restconf = @with_restconf@
with_libcurl = @with_libcurl@
LIBXML2_CFLAGS = @LIBXML2_CFLAGS@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_VERSION = @CLIXON_VERSION@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
CLIXON_MINOR = @CLIXON_VERSION_MINOR@
VPATH = @srcdir@
CC = @CC@
CFLAGS = @CFLAGS@
INSTALL = @INSTALL@
INSTALL_LIB = @INSTALL@
ifeq ($(HOST_VENDOR),apple)
INSTALLFLAGS =
else
INSTALLFLAGS = @INSTALLFLAGS@
endif
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
CPPFLAGS = @CPPFLAGS@
LINKAGE = @LINKAGE@
INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib -I$(top_srcdir)/include
CPPFLAGS += $(INCLUDES)
ifeq ($(LINKAGE),dynamic)
CLIXON_LIB = libclixon$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR)
CLIXON_BACKEND_LIB = libclixon_backend$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR)
else
CLIXON_LIB = libclixon.a
CLIXON_BACKEND_LIB = libclixon_backend.a # for util_validate
endif
# For dependency. A little strange that we rely on it being built in the src dir
# even though it may exist in $(libdir). But the new version may not have been installed yet.
LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
BELIBDEPS = $(top_srcdir)/apps/backend/$(CLIXON_BACKEND_LIB)
# Utilities, unit testings.
APPSRC = clixon_util_xml.c
APPSRC += clixon_util_xml_mod.c
APPSRC += clixon_util_json.c
APPSRC += clixon_util_yang.c
APPSRC += clixon_util_xpath.c
APPSRC += clixon_util_path.c
APPSRC += clixon_util_datastore.c
APPSRC += clixon_util_regexp.c
APPSRC += clixon_util_socket.c
APPSRC += clixon_util_validate.c
APPSRC += clixon_util_dispatcher.c
APPSRC += clixon_netconf_ssh_callhome.c
APPSRC += clixon_netconf_ssh_callhome_client.c
ifdef with_restconf
ifdef with_libcurl
APPSRC += clixon_util_stream.c
endif
ifeq ($(with_restconf), native)
APPSRC += clixon_restconf_callhome_client.c
endif
endif
ifdef with_http2
APPSRC += clixon_util_ssl.c # requires http/2
#APPSRC += clixon_util_grpc.c # work in progress
endif
APPS = $(APPSRC:.c=)
all: $(APPS)
# Dependency of clixon library
$(top_srcdir)/lib/src/$(CLIXON_LIB):
(cd $(top_srcdir)/lib/src && $(MAKE) $(MFLAGS) $(CLIXON_LIB))
$(top_srcdir)/lib/src/$(CLIXON_BACKEND_LIB):
(cd $(top_srcdir)/apps/backend && $(MAKE) $(MFLAGS) $(CLIXON_BACKEND_LIB))
clean:
rm -f $(APPS) clixon_util_stream *.core
rm -f *.gcda *.gcno *.gcov # coverage
# APPS
clixon_util_xml: clixon_util_xml.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_json: clixon_util_json.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_yang: clixon_util_yang.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_xpath: clixon_util_xpath.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_path: clixon_util_path.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_datastore: clixon_util_datastore.c $(LIBDEPS)
$(CC) $(CPPFLAGS) $(CFLAGS) -D__PROGRAM__=\"$@\" $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_xml_mod: clixon_util_xml_mod.c $(LIBDEPS)
$(CC) $(CPPFLAGS) $(CFLAGS) -D__PROGRAM__=\"$@\" $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_regexp: clixon_util_regexp.c $(LIBDEPS)
$(CC) $(LIBXML2_CFLAGS) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_socket: clixon_util_socket.c $(LIBDEPS)
$(CC) $(CPPFLAGS) $(CFLAGS) -D__PROGRAM__=\"$@\" $(LDFLAGS) $^ $(LIBS) -o $@
clixon_util_validate: clixon_util_validate.c $(BELIBDEPS) $(LIBDEPS)
$(CC) $(CPPFLAGS) $(CFLAGS) -D__PROGRAM__=\"$@\" $(LDFLAGS) $^ -l clixon_backend -o $@ $(LIBS) $(BELIBS)
clixon_util_dispatcher: clixon_util_dispatcher.c $(BELIBDEPS) $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ -l clixon_backend -o $@ $(LIBS) $(BELIBS)
ifdef with_restconf
clixon_util_stream: clixon_util_stream.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -lcurl -o $@
clixon_restconf_callhome_client: clixon_restconf_callhome_client.c $(LIBDEPS)
$(CC) $(CPPFLAGS) -D__PROGRAM__=\"$@\" $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
endif
#clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS)
# $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@
distclean: clean
rm -f Makefile *~ .depend
install: $(APPS)
install -d -m 0755 $(DESTDIR)$(bindir)
install -m 0755 $(INSTALLFLAGS) $(APPS) $(DESTDIR)$(bindir)
install-include:
install-lib:
uninstall:
rm -f $(DESTDIR)$(bindir)/$(APPS)
TAGS:
find . -name '*.[ch]' -print | etags -
depend:
$(CC) $(DEPENDFLAGS) @DEFS@ $(CPPFLAGS) $(CFLAGS) -MM $(APPSRC) > .depend
#include .depend

View file

@ -1,9 +0,0 @@
# Clixon utils
This directory contains Clixon utility programs, ie, programs that are
good to have for testing, analysis, etc, but not an actual part of
delivered code.
Look inside C-code for documentation
Note, streams utility may need: libcurl4-openssl-dev or corresponding.

View file

@ -1,325 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Create stream socket, connect to remote address, then exec sshd -e that takes over the
* tcp connection.
device/server client
+-----------------+ 2) tcp connect +-----------------+
| callhome | ----------------> | callhome-client |
+-----------------+ +-----------------+
| 3) c ^
v 1) | 4)
+-----------------+ ssh +-----------------+ 5) stdio
| sshd -i | <----------------> | ssh | <------ <rpc>...</rpc>]]>]]>"
+-----------------+ |-----------------+
| stdio
+-----------------+
| clixon_netconf |
+-----------------+
|
+-----------------+
| clixon_backend |
+-----------------+
1) Start ssh client using -o ProxyUseFdpass=yes -o ProxyCommand="callhome-client".
Callhome-client listens on port 4334 for incoming TCP connections.
2) Start callhome on server making tcp connect to client on port 4334 establishing a tcp stream
3) Callhome starts sshd -i using the established stream socket (stdio)
4) Callhome-client returns with an open stream socket to the ssh client establishing an SSH stream
to server
5) Client request sent on stdin to ssh client on established SSH stream using netconf subsystem
to clixon_netconf client
ssh -s -v -o ProxyUseFdpass=yes -o ProxyCommand="clixon_netconf_ssh_callhome_client -a 127.0.0.1" . netconf
sudo clixon_netconf_ssh_callhome -a 127.0.0.1 -c /var/tmp/./test_netconf_ssh_callhome.sh/conf_yang.xml
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define NETCONF_CH_SSH 4334
#define SSHDBIN_DEFAULT SSHD_BIN
#define UTIL_OPTS "hD:f:a:p:s:c:C:"
static int
callhome_connect(struct sockaddr *sa,
size_t sa_len,
int *sp)
{
int retval = -1;
int s;
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto done;
}
if (connect(s, sa, sa_len) < 0){
perror("connect");
close(s);
goto done;
}
*sp = s;
retval = 0;
done:
return retval;
}
/* @see clixon_inet2sin */
static int
inet2sin(const char *addrtype,
const char *addrstr,
uint16_t port,
struct sockaddr *sa,
size_t *sa_len)
{
struct sockaddr_in6 *sin6;
struct sockaddr_in *sin;
if (strcmp(addrtype, "inet:ipv6-address") == 0) {
sin6 = (struct sockaddr_in6 *)sa;
*sa_len = sizeof(struct sockaddr_in6);
sin6->sin6_port = htons(port);
sin6->sin6_family = AF_INET6;
inet_pton(AF_INET6, addrstr, &sin6->sin6_addr);
}
else if (strcmp(addrtype, "inet:ipv4-address") == 0) {
sin = (struct sockaddr_in *)sa;
*sa_len = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
sin->sin_addr.s_addr = inet_addr(addrstr);
}
else{
fprintf(stderr, "Unexpected addrtype: %s\n", addrtype);
return -1;
}
return 0;
}
static int
ssh_server_exec(int s,
char *sshdbin,
char *sshdconfigfile,
char *clixonconfigfile,
int dbg)
{
int retval = -1;
char **argv = NULL;
int i;
int nr;
char *optstr = NULL;
size_t len;
const char *formatstr = "Subsystem netconf " CLIXON_CONFIG_BINDIR "/clixon_netconf -f %s";
if (s < 0){
errno = EINVAL;
perror("socket s");
goto done;
}
if (sshdbin == NULL){
errno = EINVAL;
perror("sshdbin");
goto done;
}
if (sshdconfigfile == NULL){
errno = EINVAL;
perror("sshdconfigfile");
goto done;
}
if (clixonconfigfile == NULL){
errno = EINVAL;
perror("clixonconfigfile");
goto done;
}
/* Construct subsystem string */
len = strlen(formatstr)+strlen(clixonconfigfile)+1;
if ((optstr = malloc(len)) == NULL){
perror("malloc");
goto done;
}
snprintf(optstr, len, formatstr, clixonconfigfile);
nr = 9; /* See below */
if (dbg)
nr++;
if ((argv = calloc(nr, sizeof(char *))) == NULL){
perror("calloc");
goto done;
}
i = 0;
/* Note if you change here, also change in nr = above */
argv[i++] = sshdbin;
argv[i++] = "-i"; /* Specifies that sshd is being run from inetd(8) */
argv[i++] = "-D"; /* Foreground ? */
if (dbg)
argv[i++] = "-d"; /* Debug mode */
argv[i++] = "-e"; /* write debug logs to stderr */
argv[i++] = "-o"; /* option */
argv[i++] = optstr;
argv[i++] = "-f"; /* config file */
argv[i++] = sshdconfigfile;
argv[i++] = NULL;
assert(i==nr);
if (setreuid(0, 0) < 0){
perror("setreuid");
goto done;
}
close(0);
close(1);
if (dup2(s, STDIN_FILENO) < 0){
perror("dup2");
return -1;
}
if (dup2(s, STDOUT_FILENO) < 0){
perror("dup2");
return -1;
}
if (execv(argv[0], argv) < 0) {
perror("execv");
exit(1);
}
/* Should reach here */
retval = 0;
done:
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f ipv4|ipv6 \tSocket address family(inet:ipv4-address default)\n"
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
"\t-p <port> \tPort (default 4334)\n"
"\t-c <file> \tClixon config file - (default " CLIXON_DEFAULT_CONFIG ")\n"
"\t-C <file> \tSSHD config file - (default /dev/null)\n"
"\t-s <sshd> \tPath to sshd binary, default %s\n"
,
argv0, SSHDBIN_DEFAULT);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
int c;
char *family = "inet:ipv4-address";
char *addr = NULL;
struct sockaddr_in6 sin6 = {0, };
struct sockaddr *sa = (struct sockaddr *)&sin6;
size_t sa_len;
int dbg = 0;
uint16_t port = NETCONF_CH_SSH;
int s = -1;
char *sshdbin = SSHDBIN_DEFAULT;
char *sshdconfigfile = "/dev/null";
char *clixonconfigfile = CLIXON_CONFIG_SYSCONFDIR "/clixon.xml";
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
dbg++;
break;
case 'f':
family = optarg;
break;
case 'a':
addr = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'C':
sshdconfigfile = optarg;
break;
case 'c':
clixonconfigfile = optarg;
break;
case 's':
sshdbin = optarg;
break;
default:
usage(argv[0]);
break;
}
if (port == 0){
fprintf(stderr, "-p <port> is invalid\n");
usage(argv[0]);
goto done;
}
if (addr == NULL){
fprintf(stderr, "-a <addr> is NULL\n");
usage(argv[0]);
goto done;
}
if (inet2sin(family, addr, port, sa, &sa_len) < 0)
goto done;
if (callhome_connect(sa, sa_len, &s) < 0)
goto done;
/* For some reason this sshd returns -1 which is unclear why */
if (ssh_server_exec(s, sshdbin, sshdconfigfile, clixonconfigfile, dbg) < 0)
goto done;
/* Should not reach here */
if (s >= 0)
close(s);
retval = 0;
done:
return retval;
}

View file

@ -1,310 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
device/server client
+-----------------+ 2) tcp connect +-----------------+
| callhome | ----------------> | callhome-client |
+-----------------+ +-----------------+
| 3) c ^
v 1) | 4)
+-----------------+ ssh +-----------------+ 5) stdio
| sshd -i | <----------------> | ssh | <------ <rpc>...</rpc>]]>]]>"
+-----------------+ |-----------------+
| stdio
+-----------------+
| clixon_netconf |
+-----------------+
|
+-----------------+
| clixon_backend |
+-----------------+
1) Start ssh client using -o ProxyUseFdpass=yes -o ProxyCommand="callhome-client".
Callhome-client listens on port 4334 for incoming TCP connections.
2) Start callhome on server making tcp connect to client on port 4334 establishing a tcp stream
3) Callhome starts sshd -i using the established stream socket (stdio)
4) Callhome-client returns with an open stream socket to the ssh client establishing an SSH stream
to server
5) Client request sent on stdin to ssh client on established SSH stream using netconf subsystem
to clixon_netconf client
Example sshd-config (-c option):n
ssh -s -v -o ProxyUseFdpass=yes -o ProxyCommand="clixon_netconf_ssh_callhome_client -a 127.0.0.1" . netconf
sudo clixon_netconf_ssh_callhome -a 127.0.0.1
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define NETCONF_CH_SSH 4334
#define UTIL_OPTS "hD:f:a:p:"
/*
* fdpass()
* Pass the connected file descriptor to stdout and exit.
* This is taken from:
* https://github.com/openbsd/src/blob/master/usr.bin/nc/netcat.c
* Copyright (c) 2001 Eric Jackson <ericj@monkey.org>
* Copyright (c) 2015 Bob Beck. All rights reserved.
*/
static int
fdpass(int nfd)
{
struct msghdr mh;
union {
struct cmsghdr hdr;
char buf[CMSG_SPACE(sizeof(int))];
} cmsgbuf;
struct cmsghdr *cmsg;
struct iovec iov;
char c = '\0';
ssize_t r;
struct pollfd pfd;
/* Avoid obvious stupidity */
if (isatty(STDOUT_FILENO)){
perror("Cannot pass file descriptor to tty");
return -1;
}
memset(&mh, 0, sizeof(mh));
memset(&cmsgbuf, 0, sizeof(cmsgbuf));
memset(&iov, 0, sizeof(iov));
mh.msg_control = (char*)&cmsgbuf.buf;
mh.msg_controllen = sizeof(cmsgbuf.buf);
cmsg = CMSG_FIRSTHDR(&mh);
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
*(int *)CMSG_DATA(cmsg) = nfd;
iov.iov_base = &c;
iov.iov_len = 1;
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
memset(&pfd, 0, sizeof(pfd));
pfd.fd = STDOUT_FILENO;
pfd.events = POLLOUT;
for (;;) {
r = sendmsg(STDOUT_FILENO, &mh, 0);
if (r == -1) {
if (errno == EAGAIN || errno == EINTR) {
if (poll(&pfd, 1, -1) == -1){
perror("poll");
return -1;
}
continue;
}
perror("sendmsg");
return -1;
} else if (r != 1){
perror("sendmsg: unexpected return value");
return -1;
}
else
break;
}
// exit(0);
return 0;
}
/*! Create and bind stream socket
*
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queie of pending connections
* @param[out] sock Server socket (bound for accept)
* @retval 0 OK
* @retval -1 Error
*/
int
callhome_bind(struct sockaddr *sa,
size_t sin_len,
int backlog,
int *sock)
{
int retval = -1;
int s = -1;
int on = 1;
if (sock == NULL){
errno = EINVAL;
perror("sock");
goto done;
}
/* create inet socket */
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_KEEPALIVE");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_REUSEADDR");
goto done;
}
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
if (sa->sa_family == AF_INET6 &&
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
perror("setsockopt IPPROTO_IPV6");
goto done;
}
if (bind(s, sa, sin_len) == -1) {
perror("bind");
goto done;
}
if (listen(s, backlog) < 0){
perror("listen");
goto done;
}
if (sock)
*sock = s;
retval = 0;
done:
if (retval != 0 && s != -1)
close(s);
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f ipv4|ipv6 \tSocket address family(ipv4 default)\n"
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
"\t-p <port> \tPort (default 4334)\n"
,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
int c;
char *family = "ipv4";
char *addr = NULL;
struct sockaddr *sa;
struct sockaddr_in6 sin6 = { 0 };
struct sockaddr_in sin = { 0 };
struct sockaddr from = {0,};
socklen_t len;
size_t sin_len;
uint16_t port = NETCONF_CH_SSH;
int ss = -1; /* server socket */
int s = -1; /* accepted session socket */
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'f':
family = optarg;
break;
case 'a':
addr = optarg;
break;
case 'p':
port = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
if (port == 0){
fprintf(stderr, "-p <port> is invalid\n");
usage(argv[0]);
goto done;
}
if (addr == NULL){
fprintf(stderr, "-a <addr> is NULL\n");
usage(argv[0]);
goto done;
}
if (strcmp(family, "ipv6") == 0){
sin_len = sizeof(struct sockaddr_in6);
sin6.sin6_port = htons(port);
sin6.sin6_family = AF_INET6;
inet_pton(AF_INET6, addr, &sin6.sin6_addr);
sa = (struct sockaddr *)&sin6;
}
else if (strcmp(family, "ipv4") == 0){
sin_len = sizeof(struct sockaddr_in);
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(addr);
sa = (struct sockaddr *)&sin;
}
else{
fprintf(stderr, "-f <%s> is invalid family\n", family);
goto done;
}
/* Bind port */
if (callhome_bind(sa, sin_len, 1, &ss) < 0)
goto done;
/* Wait until connect */
len = sizeof(from);
if ((s = accept(ss, &from, &len)) < 0){
perror("accept");
goto done;
}
/* s Pass the first connected socket using sendmsg(2) to stdout and exit. */
if (fdpass(s) < 0)
goto done;
retval = 0;
done:
return retval;
}

View file

@ -1,749 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2022 Olof Hagsand and Rubicon Communications, LLC (Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
See RFC 8071 NETCONF Call Home and RESTCONF Call Home
device/server client
+-----------------+ 1) tcp connect +-----------------+
| clixon_restconf | ----------------> | callhome-client | <------ 3) HTTP
| | 2) tls | |
+-----------------+ <--------------- +-----------------+
The callhome-client listens on accept, when connect comes in, creates data socket and sends
RESTCONF GET to server, then re-waits for new accepts.
When accepting a connection, send HTTP data from input or -f <file> tehn wait for reply
Reply is matched with -e <expectfile> or printed on stdout
Tracing events on stdout using:
Accept:<n> at t=<sec> # where <n> is connection nr, <sec> is time since start of program
Close: <n> <where> <sec> at t=<sec> # where <n> is connection nr, <where> is local or remote, <sec> is time since start of connection
Reply: <n> t=<sec> [\n<msg>\n] # where <n> is nr data reply from start, <sec> is time since start of connection
Exit: <function> # where <reason> is which exit point (for debugging)
Timeline:
w
<-------------->
a0 d0 d1 a1 d0 d1
----------|----|----|------------------|----|----|------------------|
ai Accepted connect from server
di Reply from server
n Number of ai:s, 0 means no limit (_accepts)
D Timeout of di:s (1st request sent at ai, sent back-to-back or with 1sec interval) (_data_timeout_s)
idle? If set do not close after D timeout
t Wait for accept, exit if no accepts (default: 60s), just a safety for deadlocks (_accept_timeout_s)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <signal.h>
#include <netdb.h> /* gethostbyname */
#include <arpa/inet.h> /* inet_pton */
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <openssl/ssl.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
#define UTIL_TLS_OPTS "hD:f:F:a:p:c:C:k:n:N:it:d:e:"
#define RESTCONF_CH_TLS 4336
/* User struct for context / accept */
typedef struct {
int ta_ss; /* Accept socket */
SSL_CTX *ta_ctx; /* SSL context */
struct timeval ta_t0; /* Program start */
} tls_accept_handle;
/* User connection-specific data handle */
typedef struct {
int sd_s; /* data socket */
SSL *sd_ssl; /* SSL connection data */
struct timeval sd_t0; /* Start of connection, eg accept call*/
} tls_session_data;
/* Lots of global variables here, alt pass them between ta and sd structs
*/
/* Input data file for HTTP request data */
static FILE *_input_file = NULL;
/* Expected accepts */
static int _accepts = 1;
/* Number of accepts */
static int _n_accepts = 0;
/* After accepting a socket, a request is sent to the server. The handle the data socket as follows:
* 0: close after first reply
* -1: dont close after reply, (remote side may close)
* s>0: send new requests during <s> seconds after accept, then dont close
*/
static int _idle = 0;
/* Timeout in seconds after each accept, if fired just exit */
static int _accept_timeout_s = 60;
/* Timeout of data requests (1st request sent at accept, sent back-to-back / 1sec interval)
* Note: uses blockling timeout 100ms
*/
static int _data_timeout_s = 0;
/* Event trace, 1: terse (Accept:/Reply:/Close:) 2: full (data payload) */
static int _event_trace = 0;
static FILE *_event_f = NULL; /* set to stdout in main */
/*! Create and bind stream socket
*
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queie of pending connections
* @param[out] sock Server socket (bound for accept)
* @retval 0 OK
* @retval -1 Error
*/
int
callhome_bind(struct sockaddr *sa,
size_t sin_len,
int backlog,
int *sock)
{
int retval = -1;
int s = -1;
int on = 1;
if (sock == NULL){
errno = EINVAL;
perror("sock");
goto done;
}
/* create inet socket */
if ((s = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_KEEPALIVE");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
perror("setsockopt SO_REUSEADDR");
goto done;
}
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
if (sa->sa_family == AF_INET6 &&
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
perror("setsockopt IPPROTO_IPV6");
goto done;
}
if (bind(s, sa, sin_len) == -1) {
perror("bind");
goto done;
}
if (listen(s, backlog) < 0){
perror("listen");
goto done;
}
if (sock)
*sock = s;
retval = 0;
done:
if (retval != 0 && s != -1)
close(s);
return retval;
}
/*! read data from file return a malloced buffer
*
* Note same file is reread multiple times: same request/reply is made each iteration
* Also, the file read is limited to 1024 bytes
*/
static int
read_data_file(FILE *fe,
char **bufp,
size_t *lenp)
{
int retval = -1;
char *buf = NULL;
int buflen = 1024; /* start size */
char ch;
size_t len = 0;
int ret;
if ((buf = malloc(buflen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(buf, 0, buflen);
/* Start file form beginning */
rewind(fe);
while (1){
if ((ret = fread(&ch, 1, 1, fe)) < 0){
clicon_err(OE_JSON, errno, "fread");
goto done;
}
if (ret == 0)
break;
buf[len++] = ch;
// XXX No realloc, can overflow
}
*bufp = buf;
*lenp = len;
retval = 0;
done:
return retval;
}
/*! Read data from file/stdin and write to TLS data socket
*/
static int
tls_write_file(FILE *fp,
SSL *ssl)
{
int retval = -1;
char *buf = NULL;
size_t len = 0;
int ret;
int sslerr;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
if (read_data_file(fp, &buf, &len) < 0)
goto done;
if ((ret = SSL_write(ssl, buf, len)) < 1){
sslerr = SSL_get_error(ssl, ret);
clixon_debug(CLIXON_DBG_DEFAULT, "%s SSL_write() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
}
retval = 0;
done:
if (buf)
free(buf);
return retval;
}
/*! Client data socket, receive reply from server
*
* Print info on stdout
* If keep_open = 0, then close socket directly after 1st reply (client close)
* If keep_open = 1, then keep socket open (server close)
*/
static int
tls_server_reply_cb(int s,
void *arg)
{
int retval = -1;
tls_session_data *sd = (tls_session_data *)arg;
SSL *ssl;
char buf[1024];
int n;
char *expbuf = NULL;
struct timeval now;
struct timeval td;
static int seq = 0; // from start
// clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
ssl = sd->sd_ssl;
/* get reply & decrypt */
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
clixon_debug(CLIXON_DBG_DEFAULT, "%s n:%d", __FUNCTION__, n);
gettimeofday(&now, NULL);
timersub(&now, &sd->sd_t0, &td); /* from start of connection */
if (n == 0){ /* Server closed socket */
SSL_free(ssl);
clixon_event_unreg_fd(s, tls_server_reply_cb);
if (_event_trace)
fprintf(_event_f, "Close: %d remote at t=%lu\n", _n_accepts, td.tv_sec);
close(s);
free(sd);
if (_accepts == 0)
;
else if (_accepts == 1){
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
fprintf(_event_f, "Exit: %s remote\n", __FUNCTION__);
}
else
_accepts--;
goto ok;
}
seq++;
buf[n] = 0;
if (_event_trace){
fprintf(_event_f, "Reply: %d t=%lu\n", seq, td.tv_sec);
if (_event_trace > 1)
fprintf(_event_f, "%s\n", buf);
}
/* See if we should send more requests on this socket */
if (sd->sd_t0.tv_sec + _data_timeout_s > now.tv_sec){
/* Send another packet */
usleep(100000); /* XXX This is a blocking timeout */
/* Write HTTP request on socket */
if (tls_write_file(_input_file, sd->sd_ssl) < 0)
goto done;
}
else if (!_idle){
clixon_debug(CLIXON_DBG_DEFAULT, "%s idle", __FUNCTION__);
SSL_shutdown(ssl);
SSL_free(ssl);
clixon_event_unreg_fd(s, tls_server_reply_cb);
if (_event_trace)
fprintf(_event_f, "Close: %d local at t=%lu\n", _n_accepts, td.tv_sec);
close(s);
if (_accepts == 0)
;
else if (_accepts == 1){
clixon_exit_set(1); /* XXX more elaborate logic: 1) continue request, 2) close and accept new */
fprintf(_event_f, "Exit: %s idle\n", __FUNCTION__);
}
else
_accepts--;
free(sd);
}
ok:
retval = 0;
done:
if (expbuf)
free(expbuf);
clixon_debug(CLIXON_DBG_DEFAULT, "%s ret:%d", __FUNCTION__, retval);
return retval;
}
/*! Create ssl connection, select alpn, connect and verify
*/
static int
tls_ssl_init_connect(SSL_CTX *ctx,
int s,
SSL **sslp)
{
int retval = -1;
SSL *ssl = NULL;
unsigned char protos[10];
int ret;
int verify;
int sslerr;
/* create new SSL connection state */
if ((ssl = SSL_new(ctx)) == NULL){
clicon_err(OE_SSL, 0, "SSL_new.");
goto done;
}
SSL_set_fd(ssl, s); /* attach the socket descriptor */
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
protos[0] = 8;
strncpy((char*)&protos[1], "http/1.1", 9);
if ((retval = SSL_set_alpn_protos(ssl, protos, 9)) != 0){
clicon_err(OE_SSL, retval, "SSL_set_alpn_protos.");
goto done;
}
#if 0
SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
#endif
/* perform the connection
TLSEXT_TYPE_application_layer_protocol_negotiation
int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
unsigned int protos_len);
see
https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_alpn_select_cb.html
*/
if ((ret = SSL_connect(ssl)) < 1){
sslerr = SSL_get_error(ssl, ret);
clixon_debug(CLIXON_DBG_DEFAULT, "%s SSL_read() n:%d errno:%d sslerr:%d", __FUNCTION__, ret, errno, sslerr);
switch (sslerr){
case SSL_ERROR_SSL: /* 1 */
goto done;
break;
default:
clicon_err(OE_XML, errno, "SSL_connect");
goto done;
break;
}
}
/* check certificate verification result */
verify = SSL_get_verify_result(ssl);
switch (verify) {
case X509_V_OK:
break;
default:
clicon_err(OE_SSL, errno, "verify problems: %d", verify);
goto done;
}
*sslp = ssl;
retval = 0;
done:
return retval;
}
static int
tls_timeout_cb(int fd,
void *arg)
{
fprintf(_event_f, "Exit: %s\n", __FUNCTION__);
exit(200);
}
/*! Timeout in seconds after each accept, if fired just exit
*/
static int
tls_client_timeout(void *arg)
{
int retval = -1;
struct timeval now;
struct timeval t;
struct timeval t1 = {0, 0};
/* Unregister existing timeout */
clixon_event_unreg_timeout(tls_timeout_cb, arg);
/* Set timeout */
gettimeofday(&now, NULL);
t1.tv_sec = _accept_timeout_s;
timeradd(&now, &t1, &t);
if (clixon_event_reg_timeout(t,
tls_timeout_cb,
arg,
"tls client timeout") < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Callhome-server accept socket
*/
static int
tls_server_accept_cb(int ss,
void *arg)
{
int retval = -1;
tls_accept_handle *ta = (tls_accept_handle *)arg;
tls_session_data *sd = NULL;
int s;
struct sockaddr from = {0,};
socklen_t len;
SSL *ssl = NULL;
struct timeval td;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
len = sizeof(from);
if ((s = accept(ss, &from, &len)) < 0){
perror("accept");
goto done;
}
clixon_debug(CLIXON_DBG_DEFAULT, "accepted");
if (tls_ssl_init_connect(ta->ta_ctx, s, &ssl) < 0)
goto done;
clixon_debug(CLIXON_DBG_DEFAULT, "connected");
if ((sd = malloc(sizeof(*sd))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(sd, 0, sizeof(*sd));
sd->sd_s = s;
sd->sd_ssl = ssl;
gettimeofday(&sd->sd_t0, NULL);
timersub(&sd->sd_t0, &ta->ta_t0, &td); /* from start of connection */
_n_accepts++;
if (_event_trace)
fprintf(_event_f, "Accept: %d at t=%lu\n", _n_accepts, td.tv_sec);
/* Always write one HTTP request on socket, maybe more if _data_timeout_s > 0 */
if (tls_write_file(_input_file, ssl) < 0)
goto done;
/* register callback for reply */
if (clixon_event_reg_fd(s, tls_server_reply_cb, sd, "tls server reply") < 0)
goto done;
/* Unregister old + register new timeout */
if (tls_client_timeout(ta) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Out must be set to point to the selected protocol (which may be within in).
*/
static int
tls_proto_select_cb(SSL *s,
unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
return 0;
}
/*! Verify tls auth
*
* @see tlsauth_verify_callback
* This code needs a "X509 store", see X509_STORE_new()
* crl_file / crl_dir
*/
static int
tls_auth_verify_callback(int preverify_ok,
X509_STORE_CTX *x509_ctx)
{
return 1; /* success */
}
static SSL_CTX *
tls_ctx_init(const char *cert_path,
const char *key_path,
const char *ca_cert_path)
{
SSL_CTX *ctx = NULL;
if ((ctx = SSL_CTX_new(TLS_client_method())) == NULL) {
clicon_err(OE_SSL, 0, "SSL_CTX_new");
goto done;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, tls_auth_verify_callback);
/* get peer certificate
nc_client_tls_update_opts */
if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_use_certificate_file");
goto done;
}
if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_use_PrivateKey_file");
goto done;
}
if (SSL_CTX_load_verify_locations(ctx, ca_cert_path, NULL) != 1) {
clicon_err(OE_SSL, 0, "SSL_CTX_load_verify_locations");
goto done;
}
(void)SSL_CTX_set_next_proto_select_cb(ctx, tls_proto_select_cb, NULL);
return ctx;
done:
return NULL;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file> \tHTTP input file (overrides stdin)\n"
"\t-F ipv4|ipv6 \tSocket address family(ipv4 default)\n"
"\t-a <addrstr> \tIP address (eg 1.2.3.4) - mandatory\n"
"\t-p <port> \tPort (default %d)\n"
"\t-c <path> \tcert\n"
"\t-C <path> \tcacert\n"
"\t-k <path> \tkey\n"
"\t-n <nr> \tQuit after this many incoming connections, 0 means no limit. Default: 1\n"
"\t-t <sec> \tTimeout in seconds after each accept, if fired just exit. Default: %ds\n"
"\t-d <sec> \tTimeout of data requests on a connection in seconds after each accept, if fired either close or keep idle (see -i). Default: 0s\n"
"\t-i \tIdle after receiving last reply. Otherwise close directly after receiving last reply\n"
"\t-e <nr> \tEvent trace on stdout, 1: terse, 2: full\n"
,
argv0,
RESTCONF_CH_TLS,
_accept_timeout_s);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
clicon_handle h;
int c;
uint16_t port = RESTCONF_CH_TLS;
SSL_CTX *ctx = NULL;
int ss = -1;
int dbg = 0;
tls_accept_handle *ta = NULL;
char *input_filename = NULL;
char *ca_cert_path = NULL;
char *cert_path = NULL;
char *key_path = NULL;
FILE *fp = stdin; /* base file, stdin, can be overridden with -f */
struct sockaddr_in6 sin6 = {0,}; // because its larger than sin and sa
struct sockaddr *sa = (struct sockaddr *)&sin6;
size_t sa_len;
char *addr = "127.0.0.1";
char *family = "inet:ipv4-address";
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
while ((c = getopt(argc, argv, UTIL_TLS_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'f':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
input_filename = optarg;
break;
case 'F':
family = optarg;
break;
case 'a':
addr = optarg;
break;
case 'p':
if (sscanf(optarg, "%hu", &port) != 1)
usage(argv[0]);
break;
case 'c':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
cert_path = optarg;
break;
case 'C':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
ca_cert_path = optarg;
break;
case 'k':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
key_path = optarg;
break;
case 'n':
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
_accepts = atoi(optarg);
break;
case 'i': /* keep open, do not close after first reply */
_idle = 1;
break;
case 't': /* accept timeout */
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
_accept_timeout_s = atoi(optarg);
break;
case 'd': /* data timeout */
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
_data_timeout_s = atoi(optarg);
break;
case 'e': /* Event trace */
if (optarg == NULL || *optarg == '-')
usage(argv[0]);
_event_trace = atoi(optarg);
_event_f = stdout;
break;
default:
usage(argv[0]);
break;
}
if (cert_path == NULL || key_path == NULL || ca_cert_path == NULL){
fprintf(stderr, "-c <cert path> and -k <key path> -C <ca-cert> are mandatory\n");
usage(argv[0]);
}
clixon_debug_init(dbg, NULL);
if (input_filename){
if ((_input_file = fopen(input_filename, "r")) == NULL){
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
goto done;
}
}
if ((ctx = tls_ctx_init(cert_path, key_path, ca_cert_path)) == NULL)
goto done;
if (port == 0){
fprintf(stderr, "-p <port> is invalid\n");
usage(argv[0]);
goto done;
}
if (addr == NULL){
fprintf(stderr, "-a <addr> is NULL\n");
usage(argv[0]);
goto done;
}
if (clixon_inet2sin(family, addr, port, sa, &sa_len) < 0)
goto done;
/* Bind port */
if (callhome_bind(sa, sa_len, 1, &ss) < 0)
goto done;
clixon_debug(CLIXON_DBG_DEFAULT, "callhome_bind %s:%hu", addr, port);
if ((ta = malloc(sizeof(*ta))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(ta, 0, sizeof(*ta));
ta->ta_ctx = ctx;
ta->ta_ss = ss;
gettimeofday(&ta->ta_t0, NULL);
if (clixon_event_reg_fd(ss, tls_server_accept_cb, ta, "tls server accept") < 0)
goto done;
if (tls_client_timeout(ta) < 0)
goto done;
if (clixon_event_loop(h) < 0)
goto done;
retval = 0;
done:
if (ss != -1)
clixon_event_unreg_fd(ss, tls_server_accept_cb);
if (ta)
free(ta);
if (fp)
fclose(fp);
if (ss != -1)
close(ss);
if (ctx)
SSL_CTX_free(ctx); /* release context */
clicon_handle_exit(h); /* frees h and options (and streams) */
clixon_err_exit();
clixon_debug(CLIXON_DBG_DEFAULT, "clixon_restconf_callhome_client pid:%u done", getpid());
clicon_log_exit(); /* Must be after last clixon_debug */
return retval;
}

View file

@ -1,366 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <signal.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* Command line options to be passed to getopt(3) */
#define DATASTORE_OPTS "hDd:b:f:x:y:Y:"
/*! usage
*/
static void
usage(char *argv0)
{
fprintf(stderr, "usage:%s <options>* [<command>]\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D\t\tDebug\n"
"\t-d <db>\t\tDatabase name. Default: running. Alt: candidate,startup\n"
"\t-b <dir>\tDatabase directory. Mandatory\n"
"\t-f <fmt>\tDatabase format: xml or json\n"
"\t-x <xml>\tXML file. Alternative to put <xml> argument\n"
"\t-y <file>\tYang file. Mandatory\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"and command is either:\n"
"\tget [<xpath>]\n"
"\tmget <nr> [<xpath>]\n"
"\tput (merge|replace|create|delete|remove) [<xml>]\n"
"\tcopy <todb>\n"
"\tlock <pid>\n"
"\tunlock\n"
"\tunlock_all <pid>\n"
"\tislocked\n"
"\texists\n"
"\tdelete\n"
"\tinit\n"
,
argv0
);
exit(0);
}
int
main(int argc, char **argv)
{
int retval = -1;
int c;
clicon_handle h;
char *argv0;
char *db = "running";
char *cmd = NULL;
yang_stmt *yspec = NULL;
char *yangfilename = NULL;
char *xmlfilename = NULL;
char *dbdir = NULL;
int ret;
uint32_t id;
enum operation_type op;
cxobj *xt = NULL;
int i;
char *xpath;
cbuf *cbret = NULL;
int dbg = 0;
cxobj *xerr = NULL;
cxobj *xcfg = NULL;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
argv0 = argv[0];
/* Defaults */
if ((h = clicon_handle_init()) == NULL)
goto done;
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
goto done;
if (clicon_conf_xml_set(h, xcfg) < 0)
goto done;
/* getopt in two steps, first find config-file before over-riding options. */
clicon_option_str_set(h, "CLICON_XMLDB_FORMAT", "xml"); /* default */
while ((c = getopt(argc, argv, DATASTORE_OPTS)) != -1)
switch (c) {
case '?' :
case 'h' : /* help */
usage(argv0);
break;
case 'D' : /* debug */
dbg = 1;
break;
case 'd': /* db symbolic: running|candidate|startup */
if (!optarg)
usage(argv0);
db = optarg;
break;
case 'b': /* db directory */
if (!optarg)
usage(argv0);
dbdir = optarg;
break;
case 'f': /* db format */
if (!optarg)
usage(argv0);
clicon_option_str_set(h, "CLICON_XMLDB_FORMAT", optarg);
break;
case 'x': /* XML file */
if (!optarg)
usage(argv0);
xmlfilename = optarg;
break;
case 'y': /* Yang file */
if (!optarg)
usage(argv0);
yangfilename = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
}
/*
* Logs, error and debug to stderr, set debug level
*/
clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
clixon_debug_init(dbg, NULL);
argc -= optind;
argv += optind;
if (argc < 1)
usage(argv0);
cmd = argv[0];
if (dbdir == NULL){
clicon_err(OE_DB, 0, "Missing dbdir -b option");
goto done;
}
if (yangfilename == NULL){
clicon_err(OE_YANG, 0, "Missing yang filename -y option");
goto done;
}
/* Connect to plugin to get a handle */
if (xmldb_connect(h) < 0)
goto done;
/* Create yang spec */
if ((yspec = yspec_new()) == NULL)
goto done;
/* Parse yang spec from given file */
if (yang_spec_parse_file(h, yangfilename, yspec) < 0)
goto done;
clicon_option_str_set(h, "CLICON_XMLDB_DIR", dbdir);
clicon_dbspec_yang_set(h, yspec);
if (strcmp(cmd, "get")==0){
if (argc != 1 && argc != 2)
usage(argv0);
if (argc==2)
xpath = argv[1];
else
xpath = "/";
if (xmldb_get(h, db, NULL, xpath, &xt) < 0)
goto done;
if (clixon_xml2file(stdout, xt, 0, 0, NULL, fprintf, 0, 0) < 0)
goto done;
fprintf(stdout, "\n");
if (xt){
xml_free(xt);
xt = NULL;
}
}
else if (strcmp(cmd, "mget")==0){
int nr;
if (argc != 2 && argc != 3)
usage(argv0);
nr = atoi(argv[1]);
if (argc==3)
xpath = argv[2];
else
xpath = "/";
for (i=0;i<nr;i++){
if (xmldb_get(h, db, NULL, xpath, &xt) < 0)
goto done;
if (xt == NULL){
clicon_err(OE_DB, 0, "xt is NULL");
goto done;
}
if (clixon_xml2file(stdout, xt, 0, 0, NULL, fprintf, 0, 0) < 0)
goto done;
if (xt){
xml_free(xt);
xt = NULL;
}
}
fprintf(stdout, "\n");
}
else if (strcmp(cmd, "put")==0){
if (argc == 2){
if (xmlfilename == NULL){
clicon_err(OE_DB, 0, "XML filename expected");
usage(argv0);
}
}
else if (argc != 3){
clicon_err(OE_DB, 0, "Unexpected nr of args: %d", argc);
usage(argv0);
}
if (xml_operation(argv[1], &op) < 0){
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0);
}
if (argc == 2){
FILE *fp;
if ((fp = fopen(xmlfilename, "r")) == NULL){
clicon_err(OE_UNIX, errno, "fopen(%s)", xmlfilename);
goto done;
}
if (clixon_xml_parse_file(fp, YB_MODULE, yspec, &xt, NULL) < 0)
goto done;
fclose(fp);
}
else{
if ((ret = clixon_xml_parse_string(argv[2], YB_MODULE, yspec, &xt, &xerr)) < 0)
goto done;
if (ret == 0){
xml_print(stderr, xerr);
goto done;
}
}
if (xml_name_set(xt, NETCONF_INPUT_CONFIG) < 0)
goto done;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((ret = xmldb_put(h, db, op, xt, NULL, cbret)) < 0)
goto done;
}
else if (strcmp(cmd, "copy")==0){
if (argc != 2)
usage(argv0);
if (xmldb_copy(h, db, argv[1]) < 0)
goto done;
}
else if (strcmp(cmd, "lock")==0){
if (argc != 2)
usage(argv0);
id = atoi(argv[1]);
if (xmldb_lock(h, db, id) < 0)
goto done;
}
else if (strcmp(cmd, "unlock")==0){
if (argc != 1)
usage(argv0);
if (xmldb_unlock(h, db) < 0)
goto done;
}
else if (strcmp(cmd, "unlock_all")==0){
if (argc != 2)
usage(argv0);
id = atoi(argv[1]);
if (xmldb_unlock_all(h, id) < 0)
goto done;
}
else if (strcmp(cmd, "islocked")==0){
if (argc != 1)
usage(argv0);
if ((ret = xmldb_islocked(h, db)) < 0)
goto done;
fprintf(stdout, "islocked: %d\n", ret);
}
else if (strcmp(cmd, "exists")==0){
if (argc != 1)
usage(argv0);
if ((ret = xmldb_exists(h, db)) < 0)
goto done;
fprintf(stdout, "exists: %d\n", ret);
}
else if (strcmp(cmd, "delete")==0){
if (argc != 1)
usage(argv0);
if (xmldb_delete(h, db) < 0)
goto done;
}
else if (strcmp(cmd, "init")==0){
if (argc != 1)
usage(argv0);
if (xmldb_create(h, db) < 0)
goto done;
}
else{
clicon_err(OE_DB, 0, "Unrecognized command: %s", cmd);
usage(argv0);
}
if (xmldb_disconnect(h) < 0)
goto done;
retval = 0;
done:
if (xcfg)
xml_free(xcfg);
if (cbret)
cbuf_free(cbret);
if (xt)
xml_free(xt);
if (h)
clicon_handle_exit(h);
if (yspec)
ys_free(yspec);
return retval;
}

View file

@ -1,189 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2021-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 *****
* Utility for testing path dispatcher
* Everything is run by options and order is significant which makes it a little special.
* For example:
* clixon_util_dispatcher -r -c / :
* Register cb1 with default path "/" and arg NULL, call with path /
* clixon_util_dispatcher -i 2 -p /foo -a bar -r -c /bar -c /fie
* Register cb2 with path "/foo" and arg bar, call with path /bar then /fie
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
#include "clixon/clixon_backend.h"
/* Command line options to be passed to getopt(3) */
#define DISPATCHER_OPTS "hD:a:i:p:rc:"
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \t Debug - print dispatch tree\n"
"\t-a <string>\t Argument to callback (default: NULL)\n"
"\t-i <int> \t Function index: 1..3 (default: 1)\n"
"\t-p <path> \t Registered path (default: /)\n"
"\t-r \t Register callback (based on -a/-i/-p setting)\n"
"\t-c <path> \t Call dispatcher with path\n",
argv0
);
exit(0);
}
/*! Function to handle a path
*
* @param[in] h Generic handler
* @param[in] xpath Registered XPath using canonical prefixes
* @param[in] userargs Per-call user arguments
* @param[in] arg Per-path user argument
*(
/ * Make a CB() macro to generate simple callbacks that just prints the path and arg
*/
#define CB(i) static int cb##i(void *h0, char *xpath, void *userargs, void *arg) { fprintf(stdout, "%s %s\n", __FUNCTION__, (char*)arg); return 0; }
CB(1)
CB(2)
int
main(int argc,
char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int logdst = CLICON_LOG_STDERR;
int dbg = 0;
int c;
char *arg = NULL;
handler_function fn = cb1;
dispatcher_entry_t *htable = NULL;
int ret;
char *regpath = "/"; /* Register path */
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init("dispatcher", LOG_DEBUG, logdst);
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, DISPATCHER_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0);
break;
case 'a' :
case 'i' :
case 'p' :
case 'r' :
case 'c' :
break;
default:
usage(argv[0]);
break;
}
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
clicon_log_init("xpath", dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
/* Now rest of options */
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, DISPATCHER_OPTS)) != -1){
switch (c) {
case 'D' : /* debug */
break; /* see above */
case 'a' : /* arg string */
arg = optarg;
break;
case 'i' : /* dispatcher function: 1..3 */
switch (atoi(optarg)){
case 1: fn = cb1; break;
case 2: fn = cb2; break;
// case 3: fn = cb3; break;
}
break;
case 'p' : /* register path */
regpath = optarg;
break;
case 'r' :{ /* register callback based on -a/-i/-p*/
dispatcher_definition x = {regpath, fn, arg};
if (dispatcher_register_handler(&htable, &x) < 0)
goto done;
break;
}
case 'c':{ /* Execute a call using path */
char *path = optarg;
if ((ret = dispatcher_call_handlers(htable, NULL, path, NULL)) < 0)
goto done;
fprintf(stderr, "path:%s ret:%d\n", path, ret);
break;
}
default:
usage(argv[0]);
break;
}
}
if (dbg)
dispatcher_print(stderr, 0, htable);
dispatcher_free(htable);
retval = 0;
done:
return retval;
}

View file

@ -1,551 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
*
* HTTP2 + OPENSSL client integrated with clixon events
* Ubuntu package:
* apt install libnghttp2-dev
* Example run: clixon_util_ssl -H nghttp2.org
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h> /* gethostbyname */
#include <arpa/inet.h> /* inet_pton */
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <openssl/ssl.h>
#include <nghttp2/nghttp2.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
#define UTIL_GRPC_OPTS "hD:H:"
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
/* User data handle to nghttp2 lib */
typedef struct {
int sd_s;
SSL *sd_ssl;
nghttp2_session *sd_session;
int32_t sd_stream_id;
} session_data;
#define MAKE_NV(NAME, VALUE, VALUELEN) \
{ \
(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
NGHTTP2_NV_FLAG_NONE \
}
#define MAKE_NV2(NAME, VALUE) \
{ \
(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
NGHTTP2_NV_FLAG_NONE \
}
#if 1 /* DEBUG */
static void
print_header(const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen)
{
clixon_debug(CLIXON_DBG_DEFAULT, "%s %s", name, value);
}
/* Print HTTP headers to |f|. Please note that this function does not
take into account that header name and value are sequence of
octets, therefore they may contain non-printable characters. */
static void
print_headers(nghttp2_nv *nva,
size_t nvlen)
{
size_t i;
for (i = 0; i < nvlen; ++i)
print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
}
#endif /* DEBUG */
/* Transmit the |data|, |length| bytes, to the network.
* type is: nghttp2_on_header_callback
*/
static ssize_t
send_callback(nghttp2_session *session,
const uint8_t *data,
size_t length,
int flags,
void *user_data)
{
session_data *sd = (session_data*)user_data;
int ret;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %zu:", __FUNCTION__, length);
#if 0
{
int i;
for (i=0; i<length; i++)
fprintf(stderr, "%02x", data[i]&255);
fprintf(stderr, "\n");
}
#endif
/* encrypt & send message */
if ((ret = SSL_write(sd->sd_ssl, data, length)) < 0)
return ret;
return ret;
}
/*!
*/
static int
on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
//session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d", __FUNCTION__, frame->hd.stream_id);
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE)
clixon_debug(CLIXON_DBG_DEFAULT, "All headers received %d", frame->hd.stream_id);
return 0;
}
/*!
*/
static int
on_data_chunk_recv_callback(nghttp2_session *session,
uint8_t flags,
int32_t stream_id,
const uint8_t *data,
size_t len,
void *user_data)
{
session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d", __FUNCTION__, stream_id);
if (sd->sd_session == session &&
sd->sd_stream_id == stream_id)
fwrite(data, 1, len, stdout); /* This is where data is printed */
return 0;
}
/*!
*/
static int
on_stream_close_callback(nghttp2_session *session,
int32_t stream_id,
nghttp2_error_code error_code,
void *user_data)
{
//session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen,
uint8_t flags,
void *user_data)
{
// session_data *sd = (session_data*)user_data;
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE){
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d:", __FUNCTION__, frame->hd.stream_id);
print_header(name, namelen, value, valuelen);
}
return 0;
}
/*!
*/
static int
on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// session_data *sd = (session_data*)user_data;
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE)
clixon_debug(CLIXON_DBG_DEFAULT, "%s Response headers %d",
__FUNCTION__, frame->hd.stream_id);
return 0;
}
/*! Initialize callbacks
*/
static int
session_init(nghttp2_session **session,
session_data *sd)
{
nghttp2_session_callbacks *callbacks = NULL;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_client_new(session, callbacks, sd);
nghttp2_session_callbacks_del(callbacks);
return 0;
}
static int
send_client_connection_header(nghttp2_session *session)
{
int retval = -1;
nghttp2_settings_entry iv[1] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
int rv;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
/* client 24 bytes magic string will be sent by nghttp2 library */
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv));
if (rv != 0) {
clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
goto done;
}
retval = 0;
done:
return retval;
}
/*! Sets sd->sd_stream_id
*/
static int
submit_request(session_data *sd,
char *schema,
char *hostname,
char *path)
{
int retval = -1;
nghttp2_nv hdrs[] = {
MAKE_NV2(":method", "GET"),
MAKE_NV(":scheme", schema, strlen(schema)),
MAKE_NV(":authority", hostname, strlen(hostname)),
MAKE_NV(":path", path, strlen(path))
};
clixon_debug(CLIXON_DBG_DEFAULT, "%s Request headers:", __FUNCTION__);
print_headers(hdrs, ARRLEN(hdrs));
if ((sd->sd_stream_id = nghttp2_submit_request(sd->sd_session,
NULL,
hdrs,
ARRLEN(hdrs),
NULL,
NULL)) < 0){
clicon_err(OE_XML, 0, "Could not submit HTTP request: %s",
nghttp2_strerror(sd->sd_stream_id));
goto done;
}
retval = 0;
done:
return retval;
}
static int
socket_connect_inet(char *hostname,
uint16_t port,
int *sock0)
{
int retval = -1;
int s = -1;
struct sockaddr_in addr;
int ret;
struct hostent *host;
int one = 1;
clixon_debug(CLIXON_DBG_DEFAULT, "%s to %s:%hu", __FUNCTION__, hostname, port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if ((ret = inet_pton(addr.sin_family, hostname, &addr.sin_addr)) < 0){
clicon_err(OE_UNIX, errno, "inet_pton");
goto done;
}
if (ret == 0){ /* Try DNS NOTE OBSOLETE */
if ((host = gethostbyname(hostname)) == NULL){
clicon_err(OE_UNIX, errno, "gethostbyname");
goto done;
}
addr.sin_addr.s_addr = *(long*)(host->h_addr); /* XXX Just to get it to work */
}
/* special error handling to get understandable messages (otherwise ENOENT) */
if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_CFG, errno, "socket");
return -1;
}
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){
clicon_err(OE_CFG, errno, "connecting socket inet4");
close(s);
goto done;
}
/* libev requires this */
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
if (sock0 != NULL)
*sock0 = s;
retval = 0;
done:
if (sock0 == NULL && s >= 0)
close(s);
return retval;
}
/* NPN TLS extension client callback. We check that server advertised
the HTTP/2 protocol the nghttp2 library supports. If not, exit
the program. */
static int
select_next_proto_cb(SSL *ssl,
unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0)
return -1;
clixon_debug(CLIXON_DBG_DEFAULT, "%s out: %s in:%s", __FUNCTION__, *out, in);
return SSL_TLSEXT_ERR_OK;
}
static SSL_CTX*
InitCTX(void)
{
const SSL_METHOD *method;
SSL_CTX *ctx;
#if 1
method = SSLv23_client_method();
#else
OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */
SSL_load_error_strings(); /* Bring in and register error messages */
method = TLSv1_2_client_method(); /* Create new client-method instance */
#endif
/* Create new context */
if ((ctx = SSL_CTX_new(method)) == NULL){
clicon_err(OE_XML, errno, "SSL_CTX_new");
goto done;
}
SSL_CTX_set_options(ctx,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3);
#endif
done:
return ctx;
}
static int
ssl_input_cb(int s,
void *arg)
{
int retval = -1;
session_data *sd = (session_data *)arg;
SSL *ssl;
char buf[1024];
int n;
nghttp2_session *session;
int readlen;
ssl = sd->sd_ssl;
session = sd->sd_session;
/* get reply & decrypt */
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
if (n == 0){
fprintf(stdout, "%s closed\n", __FUNCTION__);
goto done;
}
if ((readlen = nghttp2_session_mem_recv(session, (unsigned char*)buf, n)) < 0){
clicon_err(OE_XML, errno, "nghttp2_session_mem_recv");
goto done;
}
nghttp2_session_send(session);
#if 0
buf[n] = 0;
fprintf(stdout, "%s\n", buf);
#endif
retval = 0;
done:
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] with xml on stdin\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-H <hostname> \tURI hostname\n"
,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
clicon_handle h;
int c;
char *hostname = NULL;
uint16_t port = 443;
SSL_CTX *ctx = NULL;
int ss = -1;
SSL *ssl;
int ret;
nghttp2_session *session = NULL;
session_data *sd = NULL;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
while ((c = getopt(argc, argv, UTIL_GRPC_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'H': /* hostname */
hostname = optarg;
break;
default:
usage(argv[0]);
break;
}
if (hostname == NULL){
fprintf(stderr, "-H <hostname> is mandatory\n");
usage(argv[0]);
}
clixon_debug_init(dbg, NULL);
SSL_library_init();
if ((ctx = InitCTX()) == NULL)
goto done;
if (socket_connect_inet(hostname, port, &ss) < 0)
goto done;
ssl = SSL_new(ctx); /* create new SSL connection state */
SSL_set_fd(ssl, ss); /* attach the socket descriptor */
/* perform the connection */
if ((ret = SSL_connect(ssl)) < 0){
clicon_err(OE_XML, errno, "SSL_connect");
goto done;
}
/* In the nghttp2 code, there is an asynchronous step for
* a connected socket, here I just assume it is connected. */
if ((sd = malloc(sizeof(*sd))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(sd, 0, sizeof(*sd));
sd->sd_s = ss;
sd->sd_ssl = ssl;
if (session_init(&session, sd) < 0)
goto done;
sd->sd_session = session;
if (send_client_connection_header(session) < 0)
goto done;
if (submit_request(sd, "https", hostname, "/") < 0)
goto done;
if (nghttp2_session_send(session) != 0){
clicon_err(OE_XML, errno, "nghttp2_session_send");
goto done;
}
if (clixon_event_reg_fd(ss, ssl_input_cb, sd, "ssl socket") < 0)
goto done;
if (clixon_event_loop(h) < 0)
goto done;
retval = 0;
done:
if (ss != -1)
close(ss);
if (ctx)
SSL_CTX_free(ctx); /* release context */
return retval;
}

View file

@ -1,165 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* JSON utility command
* @see http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
* and RFC 7951 JSON Encoding of Data Modeled with YANG
* and RFC 8259 The JavaScript Object Notation (JSON) Data Interchange Format
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <signal.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/*
* JSON parse and pretty print test program
* Usage: xpath
* read json from input
* Example compile:
gcc -g -o json -I. -I../clixon ./clixon_json.c -lclixon -lcligen
* Example run:
echo '{"foo": -23}' | ./json
*/
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] JSON as input on stdin\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-j \t\tOutput as JSON (default is as XML)\n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
"\t-p \t\tPretty-print output\n"
"\t-y <filename> \tyang filename to parse (must be stand-alone)\n" ,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
cxobj *xt = NULL;
cbuf *cb = cbuf_new();
int c;
int logdst = CLICON_LOG_STDERR;
int json = 0;
char *yang_filename = NULL;
yang_stmt *yspec = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
int ret;
int pretty = 0;
int dbg = 0;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:jl:py:")) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'j':
json++;
break;
case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(argv[0]);
break;
case 'p':
pretty++;
break;
case 'y':
yang_filename = optarg;
break;
default:
usage(argv[0]);
break;
}
clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
if (yang_filename){
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse_filename(NULL, yang_filename, yspec) == NULL){
fprintf(stderr, "yang parse error %s\n", clicon_err_reason);
return -1;
}
}
if ((ret = clixon_json_parse_file(stdin, yspec?1:0, yspec?YB_MODULE:YB_NONE, yspec, &xt, &xerr)) < 0)
goto done;
if (ret == 0){
xml_print(stderr, xerr);
goto done;
}
if (json){
if (clixon_json2cbuf(cb, xt, pretty, 1, 0) < 0)
goto done;
}
else if (clixon_xml2cbuf(cb, xt, 0, pretty, NULL, -1, 1) < 0)
goto done;
fprintf(stdout, "%s", cbuf_get(cb));
fflush(stdout);
retval = 0;
done:
if (yspec)
ys_free(yspec);
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -1,306 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* "Instance-identifier" is a subset of XML Xpaths and defined in Yang, used in NACM for example.
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/* Command line options to be passed to getopt(3) */
#define UTIL_PATH_OPTS "hD:f:ap:y:Y:n:"
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file> \tXML file\n"
"\t-a \t\tUse API-PATH (default INSTANCE-ID)\n"
"\t-p <xpath> \tPATH string\n"
"\t-y <filename> \tYang filename or dir (load all files)\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"\t-n <n> \tRepeat the call n times(for profiling)\n"
"and the following extra rules:\n"
"\tif -f is not given, XML input is expected on stdin\n"
"\tif -p is not given, <path> is expected as the first line on stdin\n"
"This means that with no arguments, <api-path> and XML is expected on stdin.\n",
argv0
);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int i;
cxobj *x = NULL;
cxobj *xc;
cxobj **xvec = NULL;
int xlen = 0;
int c;
int len;
char *buf = NULL;
int ret;
FILE *fp = stdin; /* unless overriden by argv[1] */
char *yang_file_dir = NULL;
yang_stmt *yspec = NULL;
char *path = NULL;
char *filename;
cbuf *cb = NULL;
int api_path_p = 0; /* api-path or instance-id */
clicon_handle h;
struct stat st;
cxobj *xcfg = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
int nr = 1;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init("api-path", LOG_DEBUG, CLICON_LOG_STDERR);
/* Initialize clixon handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
/* Initialize config tree (needed for -Y below) */
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
goto done;
if (clicon_conf_xml_set(h, xcfg) < 0)
goto done;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_PATH_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0);
break;
case 'f': /* XML file */
filename = optarg;
if ((fp = fopen(filename, "r")) == NULL){
clicon_err(OE_UNIX, errno, "fopen(%s)", optarg);
goto done;
}
break;
case 'a': /* API-PATH instead of INSTANCE-ID */
api_path_p++;
break;
case 'p': /* API-PATH string */
path = optarg;
break;
case 'y':
yang_file_dir = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
case 'n':
nr = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
clixon_debug_init(dbg, NULL);
yang_init(h);
/* Parse yang */
if (yang_file_dir){
if ((yspec = yspec_new()) == NULL)
goto done;
if (stat(yang_file_dir, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", yang_file_dir);
goto done;
}
if (S_ISDIR(st.st_mode)){
if (yang_spec_load_dir(h, yang_file_dir, yspec) < 0)
goto done;
}
else{
if (yang_spec_parse_file(h, yang_file_dir, yspec) < 0)
goto done;
}
}
if (path==NULL){
/* First read api-path from file */
len = 1024; /* any number is fine */
if ((buf = malloc(len)) == NULL){
perror("pt_file malloc");
return -1;
}
memset(buf, 0, len);
i = 0;
while (1){
if ((ret = read(0, &c, 1)) < 0){
perror("read");
goto done;
}
if (ret == 0)
break;
if (c == '\n')
break;
if (len==i){
if ((buf = realloc(buf, 2*len)) == NULL){
fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno));
return -1;
}
memset(buf+len, 0, len);
len *= 2;
}
buf[i++] = (char)(c&0xff);
}
path = buf;
}
/*
* If fp=stdin, then continue reading from stdin (after CR)
* XXX Note 0 above, stdin here
*/
if (clixon_xml_parse_file(fp, YB_NONE, NULL, &x, NULL) < 0){
fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason);
return -1;
}
/* Validate XML as well */
if (yang_file_dir){
/* Populate */
if ((ret = xml_bind_yang(h, x, YB_MODULE, yspec, &xerr)) < 0)
goto done;
if (ret == 0){
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (netconf_err2cb(h, xerr, cb) < 0)
goto done;
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cb));
goto done;
}
/* sort */
if (xml_sort_recurse(x) < 0)
goto done;
if (xml_apply0(x, -1, xml_sort_verify, h) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
/* Add default values */
if (xml_default_recurse(x, 0) < 0)
goto done;
if ((ret = xml_yang_validate_all_top(h, x, &xerr)) < 0)
goto done;
if (ret > 0 && (ret = xml_yang_validate_add(h, x, &xerr)) < 0)
goto done;
if (ret == 0){
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (netconf_err2cb(h, xerr, cb) < 0)
goto done;
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cb));
goto done;
}
}
/* Repeat for performance profiling (default is nr = 1) */
xvec = NULL;
for (i=0; i<nr; i++){
if (api_path_p){
if ((ret = clixon_xml_find_api_path(x, yspec, &xvec, &xlen, "%s", path)) < 0)
goto done;
}
else{
if ((ret = clixon_xml_find_instance_id(x, yspec, &xvec, &xlen, "%s", path)) < 0)
goto done;
}
if (ret == 0){
fprintf(stderr, "Fail %d %s\n", clicon_errno, clicon_err_reason);
goto done;
}
}
/* Print results */
for (i = 0; i < xlen; i++){
xc = xvec[i];
fprintf(stdout, "%d: ", i);
clixon_xml2file(stdout, xc, 0, 0, NULL, fprintf, 0, 0);
fputc('\n', stdout);
fflush(stdout);
}
retval = 0;
done:
if (yspec != NULL)
ys_free(yspec);
if (cb)
cbuf_free(cb);
if (xvec)
free(xvec);
if (buf)
free(buf);
if (x)
xml_free(x);
if (xcfg)
xml_free(xcfg);
if (fp)
fclose(fp);
if (h)
clicon_handle_exit(h);
return retval;
}

View file

@ -1,237 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Utility for compiling regexp and checking validity
* gcc -I /usr/include/libxml2 regex.c -o regex -lxml2
* @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <unistd.h> /* unistd */
#include <string.h>
#include <regex.h> /* posix regex */
#include <syslog.h>
#include <stdlib.h>
#include <limits.h>
#include <signal.h>
#ifdef HAVE_LIBXML2 /* Actually it should check for a header file */
#include <libxml/xmlregexp.h>
#endif
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/*! libxml2 regex implementation
*
* @see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028
* @retval 1 Match
* @retval 0 Not match
* @retval -1 Error
*/
static int
regex_libxml2(char *regexp0,
char *content0,
int nr,
int debug)
{
int retval = -1;
#ifdef HAVE_LIBXML2
xmlChar *regexp = (xmlChar*)regexp0;
xmlChar *content = (xmlChar*)content0;
xmlRegexp *xrp = NULL;
int ret;
int i;
if ((xrp = xmlRegexpCompile(regexp)) == NULL)
goto done;
if (nr==0)
return 1;
for (i=0; i<nr; i++)
if ((ret = xmlRegexpExec(xrp, content)) < 0)
goto done;
return ret;
done:
#endif
return retval;
}
static int
regex_posix(char *regexp,
char *content,
int nr,
int debug)
{
int retval = -1;
char *posix = NULL;
char pattern[1024];
int status = 0;
regex_t re;
char errbuf[1024];
int len0;
int i;
if (regexp_xsd2posix(regexp, &posix) < 0)
goto done;
clixon_debug(CLIXON_DBG_DEFAULT, "posix: %s", posix);
len0 = strlen(posix);
if (len0 > sizeof(pattern)-5){
fprintf(stderr, "pattern too long\n");
return -1;
}
/* note following two lines trigger [-Wstringop-truncation] warnings, but see no actual error */
strncpy(pattern, "^(", 3);
strncpy(pattern+2, posix, sizeof(pattern)-3);
strncat(pattern, ")$", sizeof(pattern)-len0-1);
if (regcomp(&re, pattern, REG_NOSUB|REG_EXTENDED) != 0)
return(0); /* report error */
if (nr==0)
return 1;
for (i=0; i<nr; i++)
status = regexec(&re, content, (size_t) 0, NULL, 0);
regfree(&re);
if (status != 0) {
regerror(status, &re, errbuf, sizeof(errbuf)); /* XXX error is ignored */
return(0); /* report error */
}
return(1);
done:
if (posix)
free(posix);
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level>\tDebug\n"
"\t-p \txsd->posix translation regexp (default)\n"
"\t-x \tlibxml2 regexp (alternative to -p)\n"
"\t-n <nr> \tIterate content match (default: 1, 0: no match only compile)\n"
"\t-r <regexp> \tregexp (mandatory)\n"
"\t-c <string> \tValue content string(mandatory if -n > 0)\n",
argv0
);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int c;
char *regexp = NULL;
char *content = NULL;
int ret = 0;
int nr = 1;
int mode = 0; /* 0 is posix, 1 is libxml */
int dbg = 0;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:pxn:r:c:")) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0);
break;
case 'p': /* xsd->posix */
mode = 0;
break;
case 'n': /* Number of iterations */
if ((nr = atoi(optarg)) < 0)
usage(argv0);
break;
case 'x': /* libxml2 */
mode = 1;
break;
case 'r': /* regexp */
regexp = optarg;
break;
case 'c': /* value content string */
content = optarg;
break;
default:
usage(argv[0]);
break;
}
clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, CLICON_LOG_STDERR);
clixon_debug_init(dbg, NULL);
if (regexp == NULL){
fprintf(stderr, "-r mandatory\n");
usage(argv0);
}
if (nr > 0 && content == NULL){
fprintf(stderr, "-c mandatory (if -n > 0)\n");
usage(argv0);
}
if (mode != 0 && mode != 1){
fprintf(stderr, "Neither posix or libxml2 set\n");
usage(argv0);
}
clixon_debug(CLIXON_DBG_DEFAULT, "regexp:%s", regexp);
clixon_debug(CLIXON_DBG_DEFAULT, "content:%s", content);
if (mode == 0){
if ((ret = regex_posix(regexp, content, nr, dbg)) < 0)
goto done;
}
else if (mode == 1){
if ((ret = regex_libxml2(regexp, content, nr, dbg)) < 0)
goto done;
}
else
usage(argv0);
fprintf(stdout, "%d\n", ret);
exit(ret);
retval = 0;
done:
return retval;
}

View file

@ -1,198 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Unit test for testting the backend socket, ie simulating a client by
* directly sending XML to the backend.
* Precondition:
* The backend must have been started using socket path goven as -s
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] with xml on stdin (unless -f)\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-a <family>\tSocket address family (default UNIX)\n"
"\t-s <sockpath> \tPath to unix domain socket (or IP addr)\n"
"\t-f <file>\tXML input file (overrides stdin)\n"
"\t-J \t\tInput as JSON (instead of XML)\n"
,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
int c;
int logdst = CLICON_LOG_STDERR;
struct clicon_msg *msg = NULL;
char *sockpath = NULL;
char *retdata = NULL;
int jsonin = 0;
char *input_filename = NULL;
FILE *fp = stdin;
cxobj *xt = NULL;
cxobj *xc;
cxobj *xerr = NULL;
char *family = "UNIX";
int ret;
cbuf *cb = cbuf_new();
clicon_handle h;
int dbg = 0;
int s;
int eof = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:s:f:Ja:")) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 's':
sockpath = optarg;
break;
case 'f':
input_filename = optarg;
break;
case 'J':
jsonin++;
break;
case 'a':
family = optarg;
break;
default:
usage(argv[0]);
break;
}
clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
if (sockpath == NULL){
fprintf(stderr, "Mandatory option missing: -s <sockpath>\n");
usage(argv[0]);
}
if (input_filename){
if ((fp = fopen(input_filename, "r")) == NULL){
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
goto done;
}
}
/* 2. Parse data (xml/json) */
if (jsonin){
if ((ret = clixon_json_parse_file(fp, 0, YB_NONE, NULL, &xt, &xerr)) < 0)
goto done;
if (ret == 0){
fprintf(stderr, "Invalid JSON\n");
goto done;
}
}
else{
if (clixon_xml_parse_file(fp, YB_NONE, NULL, &xt, NULL) < 0){
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
goto done;
}
}
if ((xc = xml_child_i(xt, 0)) == NULL){
fprintf(stderr, "No xml\n");
goto done;
}
if (clixon_xml2cbuf(cb, xc, 0, 0, NULL, -1, 0) < 0)
goto done;
if ((msg = clicon_msg_encode(getpid(), "%s", cbuf_get(cb))) < 0)
goto done;
if (strcmp(family, "UNIX")==0){
if (clicon_rpc_connect_unix(h, sockpath, &s) < 0)
goto done;
}
else
if (clicon_rpc_connect_inet(h, sockpath, 4535, &s) < 0)
goto done;
if (clicon_rpc(s, NULL, msg, &retdata, &eof) < 0)
goto done;
close(s);
fprintf(stdout, "%s\n", retdata);
retval = 0;
done:
if (fp)
fclose(fp);
if (xerr)
xml_free(xerr);
if (xt)
xml_free(xt);
if (msg)
free(msg);
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -1,551 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Actually HTTP2 + OPENSSL client integrated with clixon events
* Ubuntu package:
* apt install libnghttp2-dev
* Example run: clixon_util_ssl -H nghttp2.org
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <signal.h>
#include <netdb.h> /* gethostbyname */
#include <arpa/inet.h> /* inet_pton */
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <openssl/ssl.h>
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
#define UTIL_SSL_OPTS "hD:H:"
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
/* User data handle to nghttp2 lib */
typedef struct {
int sd_s;
SSL *sd_ssl;
nghttp2_session *sd_session;
int32_t sd_stream_id;
} session_data;
#define MAKE_NV(NAME, VALUE, VALUELEN) \
{ \
(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN, \
NGHTTP2_NV_FLAG_NONE \
}
#define MAKE_NV2(NAME, VALUE) \
{ \
(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \
NGHTTP2_NV_FLAG_NONE \
}
#if 1 /* DEBUG */
static void
print_header(const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen)
{
clixon_debug(CLIXON_DBG_DEFAULT, "%s %s", name, value);
}
/* Print HTTP headers to |f|. Please note that this function does not
take into account that header name and value are sequence of
octets, therefore they may contain non-printable characters. */
static void
print_headers(nghttp2_nv *nva,
size_t nvlen)
{
size_t i;
for (i = 0; i < nvlen; ++i)
print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
}
#endif /* DEBUG */
/* Transmit the |data|, |length| bytes, to the network.
* type is: nghttp2_on_header_callback
*/
static ssize_t
send_callback(nghttp2_session *session,
const uint8_t *data,
size_t length,
int flags,
void *user_data)
{
session_data *sd = (session_data*)user_data;
int ret;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %zu:", __FUNCTION__, length);
#if 0
{
int i;
for (i=0; i<length; i++)
fprintf(stderr, "%02x", data[i]&255);
fprintf(stderr, "\n");
}
#endif
/* encrypt & send message */
if ((ret = SSL_write(sd->sd_ssl, data, length)) < 0)
return ret;
return ret;
}
/*!
*/
static int
on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
//session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d", __FUNCTION__, frame->hd.stream_id);
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE)
clixon_debug(CLIXON_DBG_DEFAULT, "All headers received %d", frame->hd.stream_id);
return 0;
}
/*!
*/
static int
on_data_chunk_recv_callback(nghttp2_session *session,
uint8_t flags,
int32_t stream_id,
const uint8_t *data,
size_t len,
void *user_data)
{
session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d", __FUNCTION__, stream_id);
if (sd->sd_session == session &&
sd->sd_stream_id == stream_id)
fwrite(data, 1, len, stdout); /* This is where data is printed */
return 0;
}
/*!
*/
static int
on_stream_close_callback(nghttp2_session *session,
int32_t stream_id,
nghttp2_error_code error_code,
void *user_data)
{
//session_data *sd = (session_data*)user_data;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen,
uint8_t flags,
void *user_data)
{
// session_data *sd = (session_data*)user_data;
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE){
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d:", __FUNCTION__, frame->hd.stream_id);
print_header(name, namelen, value, valuelen);
}
return 0;
}
/*!
*/
static int
on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// session_data *sd = (session_data*)user_data;
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_RESPONSE)
clixon_debug(CLIXON_DBG_DEFAULT, "%s Response headers %d",
__FUNCTION__, frame->hd.stream_id);
return 0;
}
/*! Initialize callbacks
*/
static int
session_init(nghttp2_session **session,
session_data *sd)
{
nghttp2_session_callbacks *callbacks = NULL;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
on_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(
callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks,
on_header_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(
callbacks, on_begin_headers_callback);
nghttp2_session_client_new(session, callbacks, sd);
nghttp2_session_callbacks_del(callbacks);
return 0;
}
static int
send_client_connection_header(nghttp2_session *session)
{
int retval = -1;
nghttp2_settings_entry iv[1] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
int rv;
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
/* client 24 bytes magic string will be sent by nghttp2 library */
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv));
if (rv != 0) {
clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
goto done;
}
retval = 0;
done:
return retval;
}
/*! Sets sd->sd_stream_id
*/
static int
submit_request(session_data *sd,
char *schema,
char *hostname,
char *path)
{
int retval = -1;
nghttp2_nv hdrs[] = {
MAKE_NV2(":method", "GET"),
MAKE_NV(":scheme", schema, strlen(schema)),
MAKE_NV(":authority", hostname, strlen(hostname)),
MAKE_NV(":path", path, strlen(path))
};
clixon_debug(CLIXON_DBG_DEFAULT, "%s Request headers:", __FUNCTION__);
print_headers(hdrs, ARRLEN(hdrs));
if ((sd->sd_stream_id = nghttp2_submit_request(sd->sd_session,
NULL,
hdrs,
ARRLEN(hdrs),
NULL,
NULL)) < 0){
clicon_err(OE_XML, 0, "Could not submit HTTP request: %s",
nghttp2_strerror(sd->sd_stream_id));
goto done;
}
retval = 0;
done:
return retval;
}
static int
socket_connect_inet(char *hostname,
uint16_t port,
int *sock0)
{
int retval = -1;
int s = -1;
struct sockaddr_in addr;
int ret;
struct hostent *host;
int one = 1;
clixon_debug(CLIXON_DBG_DEFAULT, "%s to %s:%hu", __FUNCTION__, hostname, port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if ((ret = inet_pton(addr.sin_family, hostname, &addr.sin_addr)) < 0){
clicon_err(OE_UNIX, errno, "inet_pton");
goto done;
}
if (ret == 0){ /* Try DNS NOTE OBSOLETE */
if ((host = gethostbyname(hostname)) == NULL){
clicon_err(OE_UNIX, errno, "gethostbyname");
goto done;
}
addr.sin_addr.s_addr = *(long*)(host->h_addr); /* XXX Just to get it to work */
}
/* special error handling to get understandable messages (otherwise ENOENT) */
if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_CFG, errno, "socket");
return -1;
}
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){
clicon_err(OE_CFG, errno, "connecting socket inet4");
close(s);
goto done;
}
/* libev requires this */
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one));
if (sock0 != NULL)
*sock0 = s;
retval = 0;
done:
if (sock0 == NULL && s >= 0)
close(s);
return retval;
}
/* NPN TLS extension client callback. We check that server advertised
the HTTP/2 protocol the nghttp2 library supports. If not, exit
the program. */
static int
select_next_proto_cb(SSL *ssl,
unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0)
return -1;
clixon_debug(CLIXON_DBG_DEFAULT, "%s out: %s in:%s", __FUNCTION__, *out, in);
return SSL_TLSEXT_ERR_OK;
}
static SSL_CTX*
InitCTX(void)
{
const SSL_METHOD *method;
SSL_CTX *ctx;
#if 1
method = SSLv23_client_method();
#else
OpenSSL_add_all_algorithms(); /* Load cryptos, et.al. */
SSL_load_error_strings(); /* Bring in and register error messages */
method = TLSv1_2_client_method(); /* Create new client-method instance */
#endif
/* Create new context */
if ((ctx = SSL_CTX_new(method)) == NULL){
clicon_err(OE_XML, errno, "SSL_CTX_new");
goto done;
}
SSL_CTX_set_options(ctx,
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_COMPRESSION |
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#ifndef OPENSSL_NO_NEXTPROTONEG
SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)"\x02h2", 3);
#endif
done:
return ctx;
}
static int
ssl_input_cb(int s,
void *arg)
{
int retval = -1;
session_data *sd = (session_data *)arg;
SSL *ssl;
char buf[1024];
int n;
nghttp2_session *session;
int readlen;
ssl = sd->sd_ssl;
session = sd->sd_session;
/* get reply & decrypt */
if ((n = SSL_read(ssl, buf, sizeof(buf))) < 0){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
if (n == 0){
fprintf(stdout, "%s closed\n", __FUNCTION__);
goto done;
}
if ((readlen = nghttp2_session_mem_recv(session, (unsigned char*)buf, n)) < 0){
clicon_err(OE_XML, errno, "nghttp2_session_mem_recv");
goto done;
}
nghttp2_session_send(session);
#if 0
buf[n] = 0;
fprintf(stdout, "%s\n", buf);
#endif
retval = 0;
done:
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] with xml on stdin\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-H <hostname> \tURI hostname\n"
,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
clicon_handle h;
int c;
char *hostname = NULL;
uint16_t port = 443;
SSL_CTX *ctx = NULL;
int ss = -1;
SSL *ssl;
int ret;
nghttp2_session *session = NULL;
session_data *sd = NULL;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
while ((c = getopt(argc, argv, UTIL_SSL_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'H': /* hostname */
hostname = optarg;
break;
default:
usage(argv[0]);
break;
}
if (hostname == NULL){
fprintf(stderr, "-H <hostname> is mandatory\n");
usage(argv[0]);
}
clixon_debug_init(dbg, NULL);
SSL_library_init();
if ((ctx = InitCTX()) == NULL)
goto done;
if (socket_connect_inet(hostname, port, &ss) < 0)
goto done;
ssl = SSL_new(ctx); /* create new SSL connection state */
SSL_set_fd(ssl, ss); /* attach the socket descriptor */
/* perform the connection */
if ((ret = SSL_connect(ssl)) < 0){
clicon_err(OE_XML, errno, "SSL_connect");
goto done;
}
/* In the nghttp2 code, there is an asynchronous step for
* a connected socket, here I just assume it is connected. */
if ((sd = malloc(sizeof(*sd))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(sd, 0, sizeof(*sd));
sd->sd_s = ss;
sd->sd_ssl = ssl;
if (session_init(&session, sd) < 0)
goto done;
sd->sd_session = session;
if (send_client_connection_header(session) < 0)
goto done;
if (submit_request(sd, "https", hostname, "/") < 0)
goto done;
if (nghttp2_session_send(session) != 0){
clicon_err(OE_XML, errno, "nghttp2_session_send");
goto done;
}
if (clixon_event_reg_fd(ss, ssl_input_cb, sd, "ssl socket") < 0)
goto done;
if (clixon_event_loop(h) < 0)
goto done;
retval = 0;
done:
if (ss != -1)
close(ss);
if (ctx)
SSL_CTX_free(ctx); /* release context */
return retval;
}

View file

@ -1,286 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Stream restconf support functions.
* (Original in grideye)
* Example: clixon_util_stream -u http://localhost/streams/EXAMPLE -s 2018-10-21T19:22:16
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <signal.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/*
* Types (curl)
*/
struct curlbuf{
size_t b_len;
char *b_buf;
};
/*
* For the asynchronous case. I think we must handle the case where of many of these
* come in before we can handle them in the upper-level polling routine.
* realloc. Therefore, we append new data to the userdata buffer.
*/
static size_t
curl_get_cb(void *ptr,
size_t size,
size_t nmemb,
void *userdata)
{
struct curlbuf *buf = (struct curlbuf *)userdata;
int len;
len = size*nmemb;
if ((buf->b_buf = realloc(buf->b_buf, buf->b_len+len+1)) == NULL)
return 0;
memcpy(buf->b_buf+buf->b_len, ptr, len);
buf->b_len += len;
buf->b_buf[buf->b_len] = '\0';
// fprintf(stderr, "%s\n", buf->b_buf);
return len;
}
/*! Given an URL and data to post, do a (curl) get request with data.
*
* If getdata is set, return the (malloced) data (which should be freed).
*
* @param[in] start 'start-time' parameter that will be URL-encoded
* @retval 1 ok
* @retval -1 fatal error
*
* @note curl_easy_perform blocks
* @note New handle is created every time, the handle can be re-used for
* better TCP performance
*/
int
stream_url_get(char *url,
char *start,
char *stop,
int timeout,
char **getdata)
{
int retval = -1;
CURL *curl;
char *err = NULL;
char *encoded = NULL;
struct curlbuf cb = {0, };
cbuf *cbf = NULL;
struct curl_slist *list = NULL;
int ret;
clixon_debug(CLIXON_DBG_DEFAULT, "%s: curl -G %s start-time=%s stop-time=%s",
__FUNCTION__, url, start?start:"", stop?stop:"");
/* Set up curl for doing the communication with the controller */
if ((curl = curl_easy_init()) == NULL) {
clicon_err(OE_PLUGIN, errno, "curl_easy_init");
goto done;
}
if ((cbf = cbuf_new()) == NULL)
goto done;
if ((err = malloc(CURL_ERROR_SIZE)) == NULL) {
clixon_debug(CLIXON_DBG_DEFAULT, "%s: malloc", __FUNCTION__);
goto done;
}
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
/* HEADERS */
list = curl_slist_append(list, "Accept: text/event-stream");
// list = curl_slist_append(list, "Cache-Control: no-cache");
// list = curl_slist_append(list, "Connection: keep-alive");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
/* specify URL to get */
cprintf(cbf, "%s", url);
if (strlen(start)||strlen(stop))
cprintf(cbf, "?");
if (strlen(start)){
if ((encoded = curl_easy_escape(curl, start, 0)) == NULL)
goto done;
cprintf(cbf, "start-time=%s", encoded);
curl_free(encoded);
encoded = NULL;
}
if (strlen(stop)){
if (strlen(start))
cprintf(cbf, "&");
if ((encoded = curl_easy_escape(curl, stop, 0)) == NULL)
goto done;
cprintf(cbf, "stop-time=%s", encoded);
curl_free(encoded);
encoded = NULL;
}
clixon_debug(CLIXON_DBG_DEFAULT, "url: %s\n", cbuf_get(cbf));
curl_easy_setopt(curl, CURLOPT_URL, cbuf_get(cbf));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_get_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cb);
/* some servers don't like requests that are made without a user-agent
field, so we provide one */
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
ret = curl_easy_perform(curl);
if (ret != CURLE_OPERATION_TIMEDOUT && ret != CURLE_OK){
fprintf(stderr, "curl: %s %d", err, ret);
goto done;
}
if (getdata && cb.b_buf){
*getdata = cb.b_buf;
cb.b_buf = NULL;
}
retval = 1;
done:
if (cbf)
cbuf_free(cbf);
if (err)
free(err);
if (encoded)
curl_free(encoded);
if (cb.b_buf)
free(cb.b_buf);
if (curl)
curl_easy_cleanup(curl); /* cleanup */
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s <options>*\n"
"where options are:\n"
"\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n"
"\t-u <url>\tURL (mandatory)\n"
"\t-s <start>\tStart-time (format: 2018-10-21T19:22:16 OR +/-<x>s\n"
"\t-e <end>\tStop-time (same format as start)\n"
"\t-t <timeout>\tTimeout (default: 10)\n"
, argv0);
exit(0);
}
int
main(int argc, char **argv)
{
cbuf *cb = cbuf_new();
char *url = NULL;
char *getdata = NULL;
int timeout = 10;
char start[28] = {0,}; /* strlen = 0 */
char stop[28] = {0,};
int c;
char *argv0 = argv[0];
struct timeval now;
int dbg = 0;
clicon_log_init("xpath", LOG_DEBUG, CLICON_LOG_STDERR);
gettimeofday(&now, NULL);
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hDu:s:e:t:")) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
dbg = 1;
break;
case 'u': /* URL */
url = optarg;
break;
case 's': /* start-time */
if (*optarg == '+' || *optarg == '-'){
struct timeval t = now;
t.tv_sec += atoi(optarg);
if (time2str(&t, start, sizeof(start)) < 0)
goto done;
}
else
strcpy(start, optarg);
break;
case 'e': /* stop-time */
if (*optarg == '+' || *optarg == '-'){
struct timeval t = now;
t.tv_sec += atoi(optarg);
if (time2str(&t, stop, sizeof(stop)) < 0)
goto done;
}
else
strcpy(stop, optarg);
break;
case 't': /* timeout */
timeout = atoi(optarg);
break;
default:
usage(argv[0]);
break;
}
clixon_debug_init(dbg, NULL);
if (url == NULL)
usage(argv[0]);
curl_global_init(0);
if (stream_url_get(url, start, stop, timeout, &getdata) < 0)
goto done;
if (getdata)
fprintf(stdout, "%s", getdata);
fflush(stdout);
done:
curl_global_cleanup();
if (getdata)
free(getdata);
if (cb)
cbuf_free(cb);
return 0;
}

View file

@ -1,248 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Utility to validate and/or commit as a single utility, to be used in eg shell scripts
* Does much of what backend_main.c does, only less so
* Example:
* 1) validate foo_db using a tmp dbdir
* ./clixon_util_validate -f /usr/local/etc/example.xml -d foo -o CLICON_XMLDB_DIR=/tmp
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/* For validate and commit commands. */
#include "clixon/clixon_backend.h"
/* Command line options passed to getopt(3) */
#define UTIL_COMMIT_OPTS "hD:f:cd:o:"
static int
usage(char *argv0)
{
fprintf(stderr, "Tool to validate a database\nusage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file>\tClixon config file\n"
"\t-d <file>\tDatabase name (if not candidate, must be in XMLDBDIR)\n"
"\t-c \t\tValidate + commit, otherwise only validate\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
int c;
yang_stmt *yspec = NULL;
int commit = 0;
char *database = NULL;
clicon_handle h;
int dbg = 0;
char *dir;
char *str;
int ret;
cbuf *cb = NULL;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
/* Initialize clixon handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
/*
* Command-line options for help, debug, and config-file
*/
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_COMMIT_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'f': /* config file */
if (!strlen(optarg))
usage(argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'c': /* commit (otherwise only validate) */
case 'd': /* candidate database (if not candidate) */
case 'o': /* Configuration option */
break; /* see next getopt */
default:
usage(argv[0]);
break;
}
clixon_debug_init(dbg, NULL);
yang_init(h);
/* Find and read configfile */
if (clicon_options_main(h) < 0)
goto done;
/* Initialize plugin module by creating a handle holding plugin and callback lists */
if (clixon_plugin_module_init(h) < 0)
goto done;
/* Now run through the operational args */
opterr = 1;
optind = 1;
while ((c = getopt(argc, argv, UTIL_COMMIT_OPTS)) != -1)
switch (c) {
case 'h' : /* help */
case 'D' : /* debug */
case 'f': /* config file */
break;
case 'c': /* commit (otherwise only validate) */
commit++;
break;
case 'd': /* candidate database (if not candidate) */
database = optarg;
break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(argv[0]);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default:
usage(argv[0]);
break;
}
/* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
xml_nsctx_namespace_netconf_default(h);
/* Add (hardcoded) netconf features in case ietf-netconf loaded here
* Otherwise it is loaded in netconf_module_load below
*/
if (netconf_module_features(h) < 0)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load backend plugins before yangs are loaded (eg extension callbacks) */
if ((dir = clicon_backend_dir(h)) != NULL &&
clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
clicon_option_str(h, "CLICON_BACKEND_REGEXP")) < 0)
goto done;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL)
if (yang_spec_parse_file(h, str, yspec) < 0)
goto done;
/* 2. Load a (single) main module */
if ((str = clicon_yang_module_main(h)) != NULL)
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
yspec) < 0)
goto done;
/* 3. Load all modules in a directory (will not overwrite file loaded ^) */
if ((str = clicon_yang_main_dir(h)) != NULL)
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
/* Add generic yang specs, used by netconf client and as internal protocol
*/
if (netconf_module_load(h) < 0)
goto done;
/* Load yang restconf module */
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
/* Load yang YANG module state */
if (clicon_option_bool(h, "CLICON_XMLDB_MODSTATE") &&
yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
goto done;
/* Here all modules are loaded */
if (database == NULL)
database = "candidate";
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (commit){
if ((ret = candidate_commit(h, NULL, database, 0, 0, cb)) < 0)
goto done;
}
else{
if ((ret = candidate_validate(h, database, cb)) < 0)
goto done;
}
if (ret == 0){
clicon_err(OE_DB, 0, " Failed: %s", cbuf_get(cb));
goto done;
}
fprintf(stdout, "OK\n");
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -1,371 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* XML support functions.
* @see https://www.w3.org/TR/2008/REC-xml-20081126
* https://www.w3.org/TR/2009/REC-xml-names-20091208
* The function can do yang validation, process xml and json, etc.
* On success, nothing is printed and exitcode 0
* On failure, an error is printed on stderr and exitcode != 0
* Failure error prints are different, it would be nice to make them more
* uniform. (see clixon_netconf_error)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/* Command line options passed to getopt(3) */
#define UTIL_XML_OPTS "hD:f:JjXl:pvoy:Y:t:T:u"
static int
validate_tree(clicon_handle h,
cxobj *xt,
yang_stmt *yspec)
{
int retval = -1;
int ret;
cxobj *xerr = NULL; /* malloced must be freed */
cbuf *cbret = NULL;
/* should already be populated */
/* Add default values */
if (xml_default_recurse(xt, 0) < 0)
goto done;
if (xml_apply(xt, -1, xml_sort_verify, h) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
if ((ret = xml_yang_validate_all_top(h, xt, &xerr)) < 0)
goto done;
if (ret > 0 && (ret = xml_yang_validate_add(h, xt, &xerr)) < 0)
goto done;
if (ret == 0){
if ((cbret = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (netconf_err2cb(h, xerr, cbret) < 0)
goto done;
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cbret));
goto done;
}
retval = 0;
done:
if (cbret)
cbuf_free(cbret);
if (xerr)
xml_free(xerr);
return retval;
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] with xml on stdin (unless -f)\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file>\tXML input file (overrides stdin)\n"
"\t-J \t\tInput as JSON\n"
"\t-j \t\tOutput as JSON\n"
"\t-X \t\tOutput as TEXT \n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n"
"\t-o \t\tOutput the file\n"
"\t-v \t\tValidate the result in terms of Yang model (requires -y)\n"
"\t-p \t\tPretty-print output\n"
"\t-y <filename> \tYang filename or dir (load all files)\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"\t-t <file>\tXML top input file (where base tree is pasted to)\n"
"\t-T <path>\tXPath to where in top input file base should be pasted\n"
"\t-u \t\tTreat unknown XML as anydata\n"
,
argv0);
exit(0);
}
int
main(int argc,
char **argv)
{
int retval = -1;
int ret;
cxobj *xt = NULL; /* Base cxobj tree parsed from xml or json */
cbuf *cb = cbuf_new();
int c;
int logdst = CLICON_LOG_STDERR;
int jsonin = 0;
int jsonout = 0;
int textout = 0;
char *input_filename = NULL;
char *top_input_filename = NULL;
char *yang_file_dir = NULL;
yang_stmt *yspec = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
int pretty = 0;
int validate = 0;
int output = 0;
clicon_handle h;
struct stat st;
FILE *fp = stdin; /* base file, stdin */
FILE *tfp = NULL; /* top file */
cxobj *xcfg = NULL;
cbuf *cbret = NULL;
cxobj *xtop = NULL; /* Top tree if any */
char *top_path = NULL;
cxobj *xbot; /* Place in xtop where base cxobj is parsed */
cvec *nsc = NULL;
yang_bind yb;
int dbg = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
/* Initialize clixon handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
goto done;
if (clicon_conf_xml_set(h, xcfg) < 0)
goto done;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_XML_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'f':
input_filename = optarg;
break;
case 'J':
jsonin++;
break;
case 'j':
jsonout++;
break;
case 'X':
textout++;
break;
case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(argv[0]);
break;
case 'o':
output++;
break;
case 'v':
validate++;
break;
case 'p':
pretty++;
break;
case 'y':
yang_file_dir = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
case 't':
top_input_filename = optarg;
break;
case 'T': /* top file xpath */
top_path = optarg;
break;
case 'u':
if (clicon_option_bool_set(h, "CLICON_YANG_UNKNOWN_ANYDATA", 1) < 0)
goto done;
xml_bind_yang_unknown_anydata(1);
break;
default:
usage(argv[0]);
break;
}
if (validate && !yang_file_dir){
fprintf(stderr, "-v requires -y\n");
usage(argv[0]);
}
if (top_input_filename && top_path == NULL){
fprintf(stderr, "-t requires -T\n");
usage(argv[0]);
}
clicon_log_init(__FILE__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
yang_init(h);
/* 1. Parse yang */
if (yang_file_dir){
if ((yspec = yspec_new()) == NULL)
goto done;
if (stat(yang_file_dir, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", yang_file_dir);
goto done;
}
if (S_ISDIR(st.st_mode)){
if (yang_spec_load_dir(h, yang_file_dir, yspec) < 0)
goto done;
}
else{
if (yang_spec_parse_file(h, yang_file_dir, yspec) < 0)
goto done;
}
}
/* If top file is declared, the base XML/JSON is pasted as child to the top-file.
* This is to emulate sub-tress, not just top-level parsing.
* Always validated
*/
if (top_input_filename){
if ((tfp = fopen(top_input_filename, "r")) == NULL){
clicon_err(OE_YANG, errno, "fopen(%s)", top_input_filename);
goto done;
}
if ((ret = clixon_xml_parse_file(tfp, YB_MODULE, yspec, &xtop, &xerr)) < 0){
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "Parse top file", NULL);
goto done;
}
if (validate_tree(h, xtop, yspec) < 0)
goto done;
/* Compute canonical namespace context */
if (xml_nsctx_yangspec(yspec, &nsc) < 0)
goto done;
if ((xbot = xpath_first(xtop, nsc, "%s", top_path)) == NULL){
fprintf(stderr, "Path not found in top tree: %s\n", top_path);
goto done;
}
xt = xbot;
}
if (input_filename){
if ((fp = fopen(input_filename, "r")) == NULL){
clicon_err(OE_YANG, errno, "open(%s)", input_filename);
goto done;
}
}
/* 2. Parse data (xml/json) */
if (jsonin){
if ((ret = clixon_json_parse_file(fp, 1, top_input_filename?YB_PARENT:YB_MODULE, yspec, &xt, &xerr)) < 0)
goto done;
if (ret == 0){
clixon_netconf_error(h, xerr, "util_xml", NULL);
goto done;
}
}
else{ /* XML */
if (!yang_file_dir)
yb = YB_NONE;
else if (xt == NULL)
yb = YB_MODULE;
else
yb = YB_PARENT;
if ((ret = clixon_xml_parse_file(fp, yb, yspec, &xt, &xerr)) < 0){
fprintf(stderr, "xml parse error: %s\n", clicon_err_reason);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "util_xml", NULL);
goto done;
}
}
/* 3. Validate data (if yspec) */
if (validate){
if (validate_tree(h, xt, yspec) < 0)
goto done;
}
/* 4. Output data (xml/json/text) */
if (output){
if (textout){
if (clixon_text2file(stdout, xt, 0, fprintf, 1, 0) < 0)
goto done;
}
else if (jsonout){
if (clixon_json2cbuf(cb, xt, pretty, 1, 0) < 0)
goto done;
}
else if (clixon_xml2cbuf(cb, xt, 0, pretty, NULL, -1, 1) < 0)
goto done;
fprintf(stdout, "%s", cbuf_get(cb));
fflush(stdout);
}
retval = 0;
done:
if (tfp)
fclose(tfp);
if (fp)
fclose(fp);
if (nsc)
cvec_free(nsc);
if (cbret)
cbuf_free(cbret);
if (xcfg)
xml_free(xcfg);
if (xerr)
xml_free(xerr);
if (xtop)
xml_free(xtop);
else if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -1,310 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Utility for manipulating XML trees. In all operations, there is a primary base tree x0/xb) and a
* secondary tree (x1/xs). There are several operations on how to modify the base tree using the
* secondary tree. Both x0 and x1 are root trees, whereas xb/xs are subytrees of x0/x1 respectively
* after path has been applied.
* This includes:
* - Insert subtree (last) in list: -b <x0> -x <x1> -p <path>
* which gives xb and xs. The first element of xs is inserted under xb
* Example: xb := <b><c/></b>; xs := <b><d/></b>
* Result is : xb = <b><c/><d/></b>
* - Merging trees: -o merge -b <base> -x <2nd> -p <path>
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/* Command line options passed to getopt(3) */
#define UTIL_XML_MOD_OPTS "hD:o:y:Y:b:x:p:s"
enum opx{
OPX_ERROR = -1,
OPX_INSERT,
OPX_MERGE,
OPX_PARENT
};
static const map_str2int opx_map[] = {
{"insert", OPX_INSERT},
{"merge", OPX_MERGE},
{"parent", OPX_PARENT},
{NULL, -1}
};
const enum opx
opx_str2int(char *opstr)
{
return clicon_str2int(opx_map, opstr);
}
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level>\tDebug\n"
"\t-o <op> \tOperation: parent, insert or merge\n"
"\t-y <file> \tYANG spec file\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"\t-b <base> \tXML base expression\n"
"\t-x <xml> \tXML to insert\n"
"\t-p <xpath>\tXpath to where in base and XML\n"
"\t-s \tSort output after operation\n",
argv0
);
exit(0);
}
int
main(int argc, char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int c;
char *yangfile = NULL;
int fd = 0; /* unless overriden by argv[1] */
char *x0str = NULL;
char *x1str = NULL;
char *xpath = NULL;
yang_stmt *yspec = NULL;
cxobj *x0 = NULL;
cxobj *x1 = NULL;
cxobj *xb = NULL;
cxobj *xi = NULL;
cxobj *xi1 = NULL;
cxobj *xerr = NULL;
int sort = 0;
int ret;
clicon_handle h;
enum opx opx = OPX_ERROR;
char *reason = NULL;
int dbg = 0;
cxobj *xcfg = NULL;
clicon_log_init("clixon_insert", LOG_DEBUG, CLICON_LOG_STDERR);
if ((h = clicon_handle_init()) == NULL)
goto done;
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
goto done;
if (clicon_conf_xml_set(h, xcfg) < 0)
goto done;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, UTIL_XML_MOD_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0);
break;
case 'o': /* Operation */
opx = opx_str2int(optarg);
break;
case 'y': /* YANG spec file */
yangfile = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
case 'b': /* Base XML expression */
x0str = optarg;
break;
case 'x': /* XML to insert */
x1str = optarg;
break;
case 'p': /* XPath base */
xpath = optarg;
break;
case 's': /* sort output after insert */
sort++;
break;
default:
usage(argv[0]);
break;
}
/* Sanity check: check mandatory arguments */
if (x1str == NULL || x0str == NULL || yangfile == NULL)
usage(argv0);
if (opx == OPX_ERROR)
usage(argv0);
clixon_debug_init(dbg, NULL);
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_spec_parse_file(h, yangfile, yspec) < 0)
goto done;
/* Parse base XML */
if ((ret = clixon_xml_parse_string(x0str, YB_MODULE, yspec, &x0, &xerr)) < 0){
clicon_err(OE_XML, 0, "Parsing base xml: %s", x0str);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "Parsing base xml", NULL);
goto done;
}
/* Get base subtree by xpath */
if (xpath == NULL)
xb = x0;
else if ((xb = xpath_first(x0, NULL, "%s", xpath)) == NULL){
clicon_err(OE_XML, 0, "xpath: %s not found in x0", xpath);
goto done;
}
if (clixon_debug_get()){
clixon_debug(CLIXON_DBG_DEFAULT, "xb:");
xml_print(stderr, xb);
}
switch (opx){
case OPX_PARENT:
/* Parse insert XML */
if ((ret = clixon_xml_parse_string(x1str, YB_PARENT, yspec, &xb, &xerr)) < 0){
clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "Parsing secondary xml", NULL);
goto done;
}
break;
case OPX_MERGE:
/* Parse merge XML */
if ((ret = clixon_xml_parse_string(x1str, YB_MODULE, yspec, &x1, &xerr)) < 0){
clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "Parsing secondary xml", NULL);
goto done;
}
if (xpath == NULL)
xi = x1;
else if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
goto done;
}
if ((ret = xml_merge(xb, xi, yspec, &reason)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "%s", reason);
goto done;
}
break;
case OPX_INSERT:
/* Parse insert XML */
if ((ret = clixon_xml_parse_string(x1str, YB_MODULE, yspec, &x1, &xerr)) < 0){
clicon_err(OE_XML, 0, "Parsing insert xml: %s", x1str);
goto done;
}
if (ret == 0){
clixon_netconf_error(h, xerr, "Parsing secondary xml", NULL);
goto done;
}
/* Get secondary subtree by xpath */
if (xpath == NULL)
xi = x1;
else if ((xi = xpath_first(x1, NULL, "%s", xpath)) == NULL){
clicon_err(OE_XML, 0, "xpath: %s not found in xi", xpath);
goto done;
}
/* Find first element child of secondary */
if ((xi1 = xml_child_i_type(xi, 0, CX_ELMNT)) == NULL){
clicon_err(OE_XML, 0, "xi has no element child");
goto done;
}
/* Remove it from parent */
if (xml_rm(xi1) < 0)
goto done;
if (xml_insert(xb, xi1, INS_LAST, NULL, NULL) < 0)
goto done;
break;
default:
usage(argv0);
}
if (clixon_debug_get()){
clixon_debug(CLIXON_DBG_DEFAULT, "x0:");
xml_print(stderr, x0);
}
if (sort)
xml_sort_recurse(xb);
if (strcmp(xml_name(xb),"top")==0){
if (clixon_xml2file(stdout, xb, 0, 0, NULL, fprintf, 1, 0) < 0)
goto done;
}
else{
if (clixon_xml2file(stdout, xb, 0, 0, NULL, fprintf, 0, 0) < 0)
goto done;
}
fprintf(stdout, "\n");
retval = 0;
done:
if (x0)
xml_free(x0);
if (x1)
xml_free(x1);
if (xcfg)
xml_free(xcfg);
if (xerr)
xml_free(xerr);
if (reason)
free(reason);
if (yspec)
ys_free(yspec);
if (fd > 0)
close(fd);
return retval;
}

View file

@ -1,418 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
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 *****
See https://www.w3.org/TR/xpath/
*
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/* Command line options to be passed to getopt(3) */
#define XPATH_OPTS "hD:f:p:i:In:cl:y:Y:"
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-f <file> \tXML file\n"
"\t-p <xpath> \tPrimary XPath string\n"
"\t-i <xpath0>\t(optional) Initial XPath string\n"
"\t-I \t\tCheck inverse, map back xml result to xpath and check if equal\n"
"\t-n <pfx:id>\tNamespace binding (pfx=NULL for default)\n"
"\t-c \t\tMap xpath to canonical form\n"
"\t-l <s|e|o|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n"
"\t-y <filename> \tYang filename or dir (load all files)\n"
"\t-Y <dir> \tYang dirs (can be several)\n"
"and the following extra rules:\n"
"\tif -f is not given, XML input is expected on stdin\n"
"\tif -p is not given, <xpath> is expected as the first line on stdin\n"
"This means that with no arguments, <xpath> and XML is expected on stdin.\n",
argv0
);
exit(0);
}
static int
ctx_print2(cbuf *cb,
xp_ctx *xc)
{
int retval = -1;
int i;
cprintf(cb, "%s:", (char*)clicon_int2str(ctxmap, xc->xc_type));
switch (xc->xc_type){
case XT_NODESET:
for (i=0; i<xc->xc_size; i++){
cprintf(cb, "%d:", i);
if (clixon_xml2cbuf(cb, xc->xc_nodeset[i], 0, 0, NULL, -1, 0) < 0)
goto done;
}
break;
case XT_BOOL:
cprintf(cb, "%s", xc->xc_bool?"true":"false");
break;
case XT_NUMBER:
cprintf(cb, "%lf", xc->xc_number);
break;
case XT_STRING:
cprintf(cb, "%s", xc->xc_string);
break;
}
retval = 0;
done:
return retval;
}
int
main(int argc,
char **argv)
{
int retval = -1;
char *argv0 = argv[0];
int i;
cxobj *x0 = NULL;
cxobj *x;
int c;
int len;
char *buf = NULL;
int ret;
FILE *fp = stdin; /* unless overriden by -f */
char *yang_file_dir = NULL;
yang_stmt *yspec = NULL;
char *xpath = NULL;
char *xpath0 = NULL;
char *filename;
xp_ctx *xc = NULL;
cbuf *cb = NULL;
clicon_handle h;
struct stat st;
cvec *nsc = NULL;
int canonical = 0;
cxobj *xcfg = NULL;
cbuf *cbret = NULL;
cxobj *xerr = NULL; /* malloced must be freed */
int logdst = CLICON_LOG_STDERR;
int dbg = 0;
int xpath_inverse = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init("xpath", LOG_DEBUG, logdst);
/* Initialize clixon handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
/* Initialize config tree (needed for -Y below) */
if ((xcfg = xml_new("clixon-config", NULL, CX_ELMNT)) == NULL)
goto done;
if (clicon_conf_xml_set(h, xcfg) < 0)
goto done;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, XPATH_OPTS)) != -1)
switch (c) {
case 'h':
usage(argv0);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv0);
break;
case 'f': /* XML file */
filename = optarg;
if ((fp = fopen(filename, "r")) == NULL){
clicon_err(OE_UNIX, errno, "fopen(%s)", optarg);
goto done;
}
break;
case 'p': /* Primary XPath string */
xpath = optarg;
break;
case 'i': /* Optional initial XPath string */
xpath0 = optarg;
break;
case 'I': /* Check inverse */
xpath_inverse++;
break;
case 'n':{ /* Namespace binding */
char *prefix;
char *id;
if (nsc == NULL &&
(nsc = xml_nsctx_init(NULL, NULL)) == NULL)
goto done;
if (nodeid_split(optarg, &prefix, &id) < 0)
goto done;
if (prefix && strcmp(prefix, "null")==0){
free(prefix);
prefix = NULL;
}
if (xml_nsctx_add(nsc, prefix, id) < 0)
goto done;
if (prefix)
free(prefix);
if (id)
free(id);
break;
}
case 'c': /* Map namespace to canonical form */
canonical = 1;
break;
case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(argv[0]);
if (logdst == CLICON_LOG_FILE &&
strlen(optarg)>1 &&
clicon_log_file(optarg+1) < 0)
goto done;
break;
case 'y':
yang_file_dir = optarg;
break;
case 'Y':
if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0)
goto done;
break;
default:
usage(argv[0]);
break;
}
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
clicon_log_init("xpath", dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
yang_init(h);
/* Parse yang */
if (yang_file_dir){
if ((yspec = yspec_new()) == NULL)
goto done;
if (stat(yang_file_dir, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", yang_file_dir);
goto done;
}
if (S_ISDIR(st.st_mode)){
if (yang_spec_load_dir(h, yang_file_dir, yspec) < 0)
goto done;
}
else{
if (yang_spec_parse_file(h, yang_file_dir, yspec) < 0)
goto done;
}
}
if (xpath==NULL){
/* First read xpath */
len = 1024; /* any number is fine */
if ((buf = malloc(len)) == NULL){
perror("pt_file malloc");
return -1;
}
memset(buf, 0, len);
i = 0;
while (1){
if ((ret = read(0, &c, 1)) < 0){
perror("read");
goto done;
}
if (ret == 0)
break;
if (c == '\n')
break;
if (len==i){
if ((buf = realloc(buf, 2*len)) == NULL){
fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno));
return -1;
}
memset(buf+len, 0, len);
len *= 2;
}
buf[i++] = (char)(c&0xff);
}
xpath = buf;
}
/* If canonical, translate nsc and xpath to canonical form */
if (canonical){
char *xpath1 = NULL;
cvec *nsc1 = NULL;
cbuf *cbreason = NULL;
if ((ret = xpath2canonical(xpath, nsc, yspec, &xpath1, &nsc1, &cbreason)) < 0)
goto done;
if (ret == 0){
fprintf(stderr, "Error with %s: %s", xpath, cbuf_get(cbreason));
goto ok;
}
xpath = xpath1;
if (xpath)
fprintf(stdout, "%s\n", xpath);
if (nsc)
xml_nsctx_free(nsc);
nsc = nsc1;
if (nsc)
cvec_print(stdout, nsc);
goto ok; /* need a switch to continue, now just print and quit */
}
/*
* If fp=stdin, then continue reading from stdin (after CR)
* XXX Note 0 above, stdin here
*/
if (clixon_xml_parse_file(fp, YB_NONE, NULL, &x0, NULL) < 0){
fprintf(stderr, "Error: parsing: %s\n", clicon_err_reason);
goto done;
}
/* Validate XML as well */
if (yang_file_dir){
/* Populate */
if ((ret = xml_bind_yang(h, x0, YB_MODULE, yspec, &xerr)) < 0)
goto done;
if (ret == 0){
if ((cbret = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (netconf_err2cb(h, xerr, cbret) < 0)
goto done;
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cbret));
goto done;
}
/* Sort */
if (xml_sort_recurse(x0) < 0)
goto done;
/* Add default values */
if (xml_default_recurse(x0, 0) < 0)
goto done;
if (xml_apply0(x0, -1, xml_sort_verify, h) < 0)
clicon_log(LOG_NOTICE, "%s: sort verify failed", __FUNCTION__);
if ((ret = xml_yang_validate_all_top(h, x0, &xerr)) < 0)
goto done;
if (ret > 0 && (ret = xml_yang_validate_add(h, x0, &xerr)) < 0)
goto done;
if (ret == 0){
if ((cbret = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (netconf_err2cb(h, xerr, cbret) < 0)
goto done;
fprintf(stderr, "xml validation error: %s\n", cbuf_get(cbret));
goto done;
}
}
/* If xpath0 given, position current x (ie somewhere else than root) */
if (xpath0){
if ((x = xpath_first(x0, NULL, "%s", xpath0)) == NULL){
fprintf(stderr, "Error: xpath0 returned NULL\n");
return -1;
}
}
else
x = x0;
#if 0 // filter syntax errors
{
xpath_tree *xptree = NULL;
if (xpath_parse(xpath, &xptree) < 0)
goto ok; // Parse errors returns OK
}
#endif
if (xpath_vec_ctx(x, nsc, xpath, 0, &xc) < 0)
return -1;
/* Check inverse, eg XML back to xpath and compare with original, only if nodes */
if (xpath_inverse && xc->xc_type == XT_NODESET){
cxobj *xi;
char *xpathi = NULL;
for (i=0; i<xc->xc_size; i++){
xi = xc->xc_nodeset[i];
if (xml2xpath(xi, nsc, 0, 0, &xpathi) < 0)
goto done;
fprintf(stdout, "Inverse: %s\n", xpathi);
if (xpathi){
free(xpathi);
xpathi = NULL;
}
}
goto ok;
}
/* Print results */
cb = cbuf_new();
ctx_print2(cb, xc);
fprintf(stdout, "%s\n", cbuf_get(cb));
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (nsc)
xml_nsctx_free(nsc);
if (xc)
ctx_free(xc);
if (xcfg)
xml_free(xcfg);
if (buf)
free(buf);
if (x0)
xml_free(x0);
if (fp)
fclose(fp);
if (h)
clicon_handle_exit(h);
return retval;
}

View file

@ -1,120 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2017-2019 Olof Hagsand
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 *****
* Parse a SINGLE yang file - no dependencies - utility function only useful
* for basic syntactic checks.
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include <dirent.h>
#include <syslog.h>
#include <signal.h>
#include <sys/stat.h>
#include <libgen.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon/clixon.h"
/*
*/
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s [options] # input yang spec on stdin\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr, std(o)ut (stderr is default)\n",
argv0);
exit(0);
}
int
main(int argc, char **argv)
{
yang_stmt *yspec = NULL;
int c;
int logdst = CLICON_LOG_STDERR;
int dbg = 0;
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, "hD:l:")) != -1)
switch (c) {
case 'h':
usage(argv[0]);
break;
case 'D':
if (sscanf(optarg, "%d", &dbg) != 1)
usage(argv[0]);
break;
case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(argv[0]);
break;
default:
usage(argv[0]);
break;
}
clicon_log_init("clixon_util_yang", dbg?LOG_DEBUG:LOG_INFO, logdst);
clixon_debug_init(dbg, NULL);
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse_file(stdin, "yang test", yspec) == NULL){
fprintf(stderr, "yang parse error %s\n", clicon_err_reason);
return -1;
}
yang_print(stdout, yspec);
done:
if (yspec)
ys_free(yspec);
return 0;
}