1
0
Fork 0

Merging upstream version 3.5.5 (Closes: #1098233).

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-18 11:33:30 +01:00
parent c86ae7dcba
commit 6af28b7e8e
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
144 changed files with 43534 additions and 11497 deletions

View file

@ -10,12 +10,12 @@ on:
- devel
env:
DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev
DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev libpam0g-dev libcurl4-openssl-dev
jobs:
git-branch:
name: Get git branch
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
outputs:
branch-name: ${{ steps.get-git-branch.outputs.branch-name }}
steps:
@ -26,7 +26,7 @@ jobs:
else
export GIT_BRANCH=${{ github.base_ref }}
fi
echo "::set-output name=branch-name::$GIT_BRANCH"
echo "branch-name=$GIT_BRANCH" >> $GITHUB_OUTPUT
build:
name: ${{ matrix.config.name }}
@ -37,96 +37,118 @@ jobs:
matrix:
config:
- {
name: "Release, Ubuntu 18.04, gcc",
os: "ubuntu-18.04",
name: "Release, gcc, OpenSSL",
os: "ubuntu-22.04",
build-type: "Release",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_TESTS=ON -DENABLE_DNSSEC=ON",
tls-lib: "OpenSSL",
packages: "",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "Release, Ubuntu 18.04, clang",
os: "ubuntu-18.04",
name: "Release, gcc, MbedTLS",
os: "ubuntu-22.04",
build-type: "Release",
dep-build-type: "Release",
cc: "clang",
cc: "gcc",
options: "-DENABLE_TESTS=ON -DENABLE_DNSSEC=ON",
tls-lib: "MbedTLS",
packages: "",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "Debug, Ubuntu 18.04, gcc",
os: "ubuntu-18.04",
name: "Release, clang",
os: "ubuntu-22.04",
build-type: "Release",
dep-build-type: "Release",
cc: "clang",
options: "-DENABLE_TESTS=ON -DENABLE_DNSSEC=ON",
tls-lib: "OpenSSL",
packages: "",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "Debug, gcc, OpenSSL",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_DNSSEC=ON",
tls-lib: "OpenSSL",
packages: "valgrind",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "Debug, Ubuntu 18.04, clang",
os: "ubuntu-18.04",
name: "Debug, gcc, MbedTLS",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_DNSSEC=ON",
tls-lib: "MbedTLS",
packages: "valgrind",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "Debug, clang",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "clang",
options: "-DENABLE_DNSSEC=ON",
packages: "valgrind",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "SSH Only",
os: "ubuntu-18.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_TLS=OFF -DENABLE_SSH=ON",
packages: "valgrind",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "TLS Only",
os: "ubuntu-18.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_TLS=ON -DENABLE_SSH=OFF",
packages: "valgrind",
tls-lib: "OpenSSL",
# no valgrind because it does not support DWARF5 yet generated by clang 14
packages: "",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "No SSH nor TLS",
os: "ubuntu-18.04",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "gcc",
options: "-DENABLE_TLS=OFF -DENABLE_SSH=OFF",
options: "-DENABLE_SSH_TLS=OFF",
tls-lib: "",
packages: "valgrind",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "ASAN and UBSAN",
os: "ubuntu-18.04",
name: "ASAN and UBSAN, OpenSSL",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "clang",
options: "-DCMAKE_C_FLAGS=-fsanitize=address,undefined -DENABLE_VALGRIND_TESTS=OFF",
tls-lib: "OpenSSL",
packages: "",
snaps: "",
make-prepend: "",
make-target: ""
}
- {
name: "ASAN and UBSAN, MbedTLS",
os: "ubuntu-22.04",
build-type: "Debug",
dep-build-type: "Release",
cc: "clang",
options: "-DCMAKE_C_FLAGS=-fsanitize=address,undefined -DENABLE_VALGRIND_TESTS=OFF",
tls-lib: "MbedTLS",
packages: "",
snaps: "",
make-prepend: "",
@ -134,53 +156,85 @@ jobs:
}
- {
name: "ABI Check",
os: "ubuntu-latest",
os: "ubuntu-22.04",
build-type: "ABICheck",
dep-build-type: "Debug",
cc: "gcc",
options: "",
tls-lib: "OpenSSL",
packages: "abi-dumper abi-compliance-checker snap",
snaps: "core universal-ctags",
make-prepend: "",
make-target: "abi-check"
}
- {
name: "DEB Package",
os: "ubuntu-22.04",
build-type: "Release",
dep-build-type: "Release",
cc: "gcc",
options: "",
tls-lib: "OpenSSL",
packages: "cmake debhelper valgrind python3-pip",
snaps: "",
make-prepend: "",
make-target: ""
}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@main
with:
fetch-depth: 100
- name: Deps-packages
shell: bash
run: |
sudo add-apt-repository ppa:kedazo/libssh-0.7.x -y
sudo apt-get update
sudo apt-get install $DEFAULT_PACKAGES ${{ matrix.config.packages }}
if ${{ matrix.config.snaps != '' }}
then sudo snap refresh; sudo snap install ${{ matrix.config.snaps }}
fi
if ${{ matrix.config.name == 'DEB Package' }}; then
pip install apkg
apkg system-setup
fi
- name: Deps-uncrustify
shell: bash
working-directory: ${{ github.workspace }}
run: |
git clone --branch uncrustify-0.71.0 https://github.com/uncrustify/uncrustify
git clone --branch uncrustify-0.77.1 https://github.com/uncrustify/uncrustify
cd uncrustify
mkdir build
cd build
CC=${{ matrix.config.cc }} cmake ..
make
sudo make install
if: ${{ matrix.config.name == 'Debug, Ubuntu 18.04, gcc' }}
if: ${{ matrix.config.name == 'Debug, gcc, OpenSSL' || matrix.config.name == 'Debug, gcc, MbedTLS' }}
- name: Deps-libyang
shell: bash
run: |
git clone -b ${{ needs.git-branch.outputs.branch-name }} https://github.com/CESNET/libyang.git
cd libyang
if ${{ matrix.config.name == 'DEB Package' }}; then
apkg build
apkg install
else
mkdir build
cd build
CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_BUILD_TESTS=OFF ..
CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_TESTS=OFF ..
make -j2
sudo make install
fi
- name: Build-and-install-package
shell: bash
working-directory: ${{ github.workspace }}
run: |
apkg build
apkg install
if: ${{ matrix.config.name == 'DEB Package' }}
- name: Deps-libval
shell: bash
@ -190,6 +244,19 @@ jobs:
./configure
make -j2
sudo make install
if: ${{ matrix.config.name != 'DEB Package' }}
- name: Deps-MbedTLS
shell: bash
run: |
git clone -b mbedtls-3.5.2 https://github.com/Mbed-TLS/mbedtls.git
cd mbedtls
mkdir build
cd build
CC=${{ matrix.config.cc }} cmake -DUSE_SHARED_MBEDTLS_LIBRARY=On -DENABLE_TESTING=Off ..
make -j2
sudo make install
if: ${{ matrix.config.tls-lib == 'MbedTLS' }}
- name: Configure
shell: bash
@ -198,6 +265,7 @@ jobs:
mkdir build
cd build
CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} ${{ matrix.config.options }} ..
if: ${{ matrix.config.name != 'DEB Package' }}
- name: Build
shell: bash
@ -206,8 +274,12 @@ jobs:
export LC_ALL=C.UTF-8
export PATH=/snap/bin:${{ github.workspace }}/coverity-tools/bin:$PATH
${{ matrix.config.make-prepend }} make ${{ matrix.config.make-target }}
if: ${{ matrix.config.name != 'DEB Package' }}
- name: Test
shell: bash
working-directory: ${{ github.workspace }}/build
run: ctest --output-on-failure
run: |
export LSAN_OPTIONS=suppressions=${{ github.workspace }}/tests/library_lsan.supp
ctest -j4 --output-on-failure
if: ${{ matrix.config.name != 'DEB Package' }}

View file

@ -5,13 +5,13 @@ on:
- devel
env:
DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev
DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev libpam0g-dev libcurl4-openssl-dev
COVERITY_PROJECT: CESNET%2Flibnetconf2
jobs:
git-branch:
name: Get git branch
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
outputs:
branch-name: ${{ steps.get-git-branch.outputs.branch-name }}
steps:
@ -22,7 +22,7 @@ jobs:
else
export GIT_BRANCH=${{ github.base_ref }}
fi
echo "::set-output name=branch-name::$GIT_BRANCH"
echo "branch-name=$GIT_BRANCH" >> $GITHUB_OUTPUT
build:
name: ${{ matrix.config.name }}
@ -58,7 +58,7 @@ jobs:
}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@main
- name: Deps-packages
shell: bash
@ -88,7 +88,7 @@ jobs:
cd libyang
mkdir build
cd build
CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_BUILD_TESTS=OFF ..
CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_TESTS=OFF ..
make -j2
sudo make install

2
.gitignore vendored
View file

@ -1 +1,3 @@
/pkg
/build
/doc/html

View file

@ -1,19 +0,0 @@
extraction:
cpp:
prepare:
packages: libpcre2-dev
after_prepare:
- cd $LGTM_WORKSPACE
- git clone -b devel https://github.com/CESNET/libyang.git
- cd libyang; mkdir build; cd build
- cmake -DCMAKE_INSTALL_PREFIX=$LGTM_WORKSPACE -DENABLE_BUILD_TESTS=OFF ..
- make -j2
- make install
configure:
command:
- mkdir build; cd build
- cmake -DCMAKE_INCLUDE_PATH=$LGTM_WORKSPACE/include -DCMAKE_LIBRARY_PATH=$LGTM_WORKSPACE/lib ..
index:
build_command:
- cd build
- make -j2

View file

@ -1,9 +1,9 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 3.5...3.28.1)
project(libnetconf2 C)
# include custom Modules
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/CMakeModules/")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules/")
include(GNUInstallDirs)
include(CheckFunctionExists)
@ -19,16 +19,6 @@ if(POLICY CMP0075)
cmake_policy(SET CMP0075 NEW)
endif()
set(LIBNETCONF2_DESCRIPTION "NETCONF server and client library in C.")
# check the supported platform
if(NOT UNIX)
message(FATAL_ERROR "Only *nix like systems are supported.")
endif()
# osx specific
set(CMAKE_MACOSX_RPATH TRUE)
# set default build type if not specified by user
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
@ -53,28 +43,40 @@ elseif("${BUILD_TYPE_UPPER}" STREQUAL "DOCONLY")
set(CMAKE_BUILD_TYPE "DocOnly" CACHE STRING "Build Type" FORCE)
endif()
#
# variables
#
set(LIBNETCONF2_DESCRIPTION "NETCONF server and client library in C.")
# osx specific
set(CMAKE_MACOSX_RPATH TRUE)
# Version of the project
# Generic version of not only the library. Major version is reserved for really big changes of the project,
# minor version changes with added functionality (new tool, functionality of the tool or library, ...) and
# micro version is changed with a set of small changes or bugfixes anywhere in the project.
set(LIBNETCONF2_MAJOR_VERSION 2)
set(LIBNETCONF2_MINOR_VERSION 0)
set(LIBNETCONF2_MICRO_VERSION 24)
set(LIBNETCONF2_MAJOR_VERSION 3)
set(LIBNETCONF2_MINOR_VERSION 5)
set(LIBNETCONF2_MICRO_VERSION 5)
set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION})
# Version of the library
# Major version is changed with every backward non-compatible API/ABI change in libyang, minor version changes
# Major version is changed with every backward non-compatible API/ABI change in the library, minor version changes
# with backward compatible change and micro version is connected with any internal change of the library.
set(LIBNETCONF2_MAJOR_SOVERSION 2)
set(LIBNETCONF2_MINOR_SOVERSION 1)
set(LIBNETCONF2_MICRO_SOVERSION 20)
set(LIBNETCONF2_MAJOR_SOVERSION 4)
set(LIBNETCONF2_MINOR_SOVERSION 4)
set(LIBNETCONF2_MICRO_SOVERSION 5)
set(LIBNETCONF2_SOVERSION_FULL ${LIBNETCONF2_MAJOR_SOVERSION}.${LIBNETCONF2_MINOR_SOVERSION}.${LIBNETCONF2_MICRO_SOVERSION})
set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_SOVERSION})
# libyang SO version required
set(LIBYANG_DEP_SOVERSION_MAJOR 2)
# Version of libyang library that this project depends on
set(LIBYANG_DEP_VERSION 2.0.0)
set(LIBYANG_DEP_SOVERSION 3.0.0)
set(LIBYANG_DEP_SOVERSION_MAJOR 3)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fvisibility=hidden -std=gnu99")
# global C flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fvisibility=hidden -std=c99")
#
# options
@ -86,15 +88,18 @@ else()
option(ENABLE_TESTS "Build tests" OFF)
option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF)
endif()
option(ENABLE_EXAMPLES "Build examples" ON)
option(ENABLE_COVERAGE "Build code coverage report from tests" OFF)
option(ENABLE_SSH "Enable NETCONF over SSH support (via libssh)" ON)
option(ENABLE_TLS "Enable NETCONF over TLS support (via OpenSSL)" ON)
option(ENABLE_SSH_TLS "Enable NETCONF over SSH and TLS support (via libssh and OpenSSL)" ON)
option(ENABLE_DNSSEC "Enable support for SSHFP retrieval using DNSSEC for SSH (requires OpenSSL and libval)" OFF)
option(ENABLE_COMMON_TARGETS "Define common custom target names such as 'doc' or 'uninstall', may cause conflicts when using add_subdirectory() to build this project" ON)
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
set(READ_INACTIVE_TIMEOUT 20 CACHE STRING "Maximum number of seconds waiting for new data once some data have arrived")
set(READ_ACTIVE_TIMEOUT 300 CACHE STRING "Maximum number of seconds for receiving a full message")
set(MAX_PSPOLL_THREAD_COUNT 6 CACHE STRING "Maximum number of threads that could simultaneously access a ps_poll structure")
set(TIMEOUT_STEP 100 CACHE STRING "Number of microseconds tasks are repeated until timeout elapses")
set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/share/yang/modules" CACHE STRING "Directory with common YANG modules")
set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/yang/modules/libnetconf2" CACHE STRING "Directory where to copy the YANG modules to")
set(CLIENT_SEARCH_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/yang/modules" CACHE STRING "Default NC client YANG module search directory")
#
# sources
@ -106,20 +111,21 @@ set(libsrc
src/messages_server.c
src/session.c
src/session_client.c
src/session_server.c)
src/session_server.c
src/server_config.c
src/server_config_util.c)
if(ENABLE_SSH)
if(ENABLE_SSH_TLS)
list(APPEND libsrc
src/session_client_ssh.c
src/session_server_ssh.c)
set(SSH_MACRO "#ifndef NC_ENABLED_SSH\n#define NC_ENABLED_SSH\n#endif")
endif()
if(ENABLE_TLS)
list(APPEND libsrc
src/session_server_ssh.c
src/server_config_util_ssh.c
src/session_client_tls.c
src/session_server_tls.c)
set(TLS_MACRO "#ifndef NC_ENABLED_TLS\n#define NC_ENABLED_TLS\n#endif")
src/session_server_tls.c
src/server_config_util_tls.c
src/server_config_ks.c
src/server_config_ts.c)
set(SSH_TLS_MACRO "#ifndef NC_ENABLED_SSH_TLS\n#define NC_ENABLED_SSH_TLS\n#endif")
endif()
set(headers
@ -131,11 +137,12 @@ set(headers
src/session_client.h
src/session_client_ch.h
src/session_server.h
src/session_server_ch.h)
src/session_server_ch.h
src/server_config.h)
# files to generate doxygen from
set(doxy_files
src/libnetconf.h
doc/libnetconf.doc
src/log.h
src/netconf.h
src/session.h
@ -144,21 +151,23 @@ set(doxy_files
src/session_client.h
src/session_client_ch.h
src/session_server.h
src/session_server_ch.h)
src/session_server_ch.h
src/server_config.h)
# source files to be covered by the 'format' target
set(format_sources
compat/*.c
compat/*.h*
examples/*.c
examples/*.h*
src/*.c
src/*.h
tests/*.c
tests/client/*.c)
tests/*.c)
#
# checks
#
if(ENABLE_DNSSEC AND NOT ENABLE_SSH)
if(ENABLE_DNSSEC AND NOT ENABLE_SSH_TLS)
message(WARNING "DNSSEC SSHFP retrieval cannot be used without SSH support.")
set(ENABLE_DNSSEC OFF)
endif()
@ -186,7 +195,7 @@ if(ENABLE_COVERAGE)
endif()
if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG")
source_format_enable()
source_format_enable(0.77)
endif()
if("${BUILD_TYPE_UPPER}" STREQUAL "DOCONLY")
@ -201,12 +210,15 @@ endif()
# use compat
use_compat()
# netconf2 target
add_library(netconf2 SHARED ${libsrc} ${compatsrc})
# netconf2 sourceless target - need it for linking libs, but the required sources will be added later
add_library(netconf2)
# set the shared library version
set_target_properties(netconf2 PROPERTIES VERSION ${LIBNETCONF2_SOVERSION_FULL} SOVERSION ${LIBNETCONF2_SOVERSION})
# include repository files with highest priority
include_directories(${PROJECT_BINARY_DIR}/src)
include_directories(${PROJECT_BINARY_DIR}/include)
# dependencies - pthread
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
@ -217,41 +229,75 @@ target_link_libraries(netconf2 ${CMAKE_THREAD_LIBS_INIT})
set(CMAKE_REQUIRED_LIBRARIES pthread)
check_function_exists(pthread_rwlockattr_setkind_np HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP)
# dependencies - openssl
if(ENABLE_TLS OR ENABLE_DNSSEC OR ENABLE_SSH)
find_package(OpenSSL REQUIRED)
if(ENABLE_TLS)
message(STATUS "OpenSSL found, required for TLS")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNC_ENABLED_TLS")
endif()
if(OPENSSL_VERSION VERSION_LESS 1.1.1)
message(WARNING "OpenSSL version ${OPENSSL_VERSION} is no longer maintained, consider an update.")
endif()
# header file compatibility
check_include_file("shadow.h" HAVE_SHADOW)
check_include_file("termios.h" HAVE_TERMIOS)
target_link_libraries(netconf2 ${OPENSSL_LIBRARIES})
if(ENABLE_SSH_TLS)
# dependencies - mbedTLS (higher preference) or OpenSSL
find_package(MbedTLS 3.5.0)
if (MBEDTLS_FOUND)
# dependencies - mbedtls
set(HAVE_MBEDTLS TRUE)
list(APPEND libsrc src/session_mbedtls.c)
include_directories(${MBEDTLS_INCLUDE_DIRS})
target_link_libraries(netconf2 ${MBEDTLS_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${MBEDTLS_LIBRARIES})
else()
# dependencies - openssl
find_package(OpenSSL 3.0.0 REQUIRED)
list(APPEND libsrc src/session_openssl.c)
include_directories(${OPENSSL_INCLUDE_DIR})
target_link_libraries(netconf2 ${OPENSSL_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES})
endif()
# dependencies - libssh
if(ENABLE_SSH)
find_package(LibSSH 0.7.1 REQUIRED)
if(LIBSSH_VERSION VERSION_EQUAL 0.9.3 OR LIBSSH_VERSION VERSION_EQUAL 0.9.4)
message(FATAL_ERROR "LibSSH ${LIBSSH_VERSION} includes regression bugs and libnetconf2 will NOT work properly, try to use another version")
endif()
find_package(LibSSH 0.9.5 REQUIRED)
target_link_libraries(netconf2 ${LIBSSH_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
include_directories(${LIBSSH_INCLUDE_DIRS})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNC_ENABLED_SSH")
# crypt
if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "QNX")
target_link_libraries(netconf2 -lcrypt)
list(APPEND CMAKE_REQUIRED_LIBRARIES crypt)
# dependencies - libcurl
find_package(CURL 7.30.0 REQUIRED)
if(TARGET CURL::libcurl)
target_link_libraries(netconf2 CURL::libcurl)
else()
target_link_libraries(netconf2 -llogin)
list(APPEND CMAKE_REQUIRED_LIBRARIES login)
target_link_libraries(netconf2 ${CURL_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${CURL_LIBRARY})
include_directories(${CURL_INCLUDE_DIRS})
endif()
# crypt (if not found, assume no library needs to be linked)
if(${CMAKE_SYSTEM_NAME} MATCHES "QNX")
set(LIBCRYPT login)
else()
set(LIBCRYPT crypt)
endif()
check_library_exists(${LIBCRYPT} crypt "" HAVE_CRYPT)
if(HAVE_CRYPT)
target_link_libraries(netconf2 ${LIBCRYPT})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBCRYPT})
endif()
# libpam
find_package(LibPAM)
if(LibPAM_FOUND)
set(HAVE_LIBPAM TRUE)
target_link_libraries(netconf2 ${LIBPAM_LIBRARIES})
list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBPAM_LIBRARIES})
include_directories(${LIBPAM_INCLUDE_DIRS})
message(STATUS "SSH Keyboard Interactive system method: Linux PAM")
elseif(HAVE_SHADOW)
message(STATUS "SSH Keyboard Interactive system method: local users")
else()
message(WARNING "SSH Keyboard Interactive system method: disabled")
endif()
# set compiler flag
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNC_ENABLED_SSH_TLS")
endif()
# dependencies - libval
@ -263,14 +309,10 @@ if(ENABLE_DNSSEC)
endif()
# dependencies - libyang
find_package(LibYANG ${LIBYANG_DEP_SOVERSION_MAJOR} REQUIRED)
find_package(LibYANG ${LIBYANG_DEP_SOVERSION} REQUIRED)
target_link_libraries(netconf2 ${LIBYANG_LIBRARIES})
include_directories(${LIBYANG_INCLUDE_DIRS})
# header file compatibility - shadow.h and crypt.h
check_include_file("shadow.h" HAVE_SHADOW)
check_include_file("crypt.h" HAVE_CRYPT)
# function compatibility - getpeereid on QNX
if(${CMAKE_SYSTEM_NAME} MATCHES "QNX")
target_link_libraries(netconf2 -lsocket)
@ -281,24 +323,35 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "QNX")
list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D_QNX_SOURCE)
endif()
# generate files
# set sources
target_sources(netconf2 PRIVATE ${libsrc} ${compatsrc})
# generate config file
configure_file("${PROJECT_SOURCE_DIR}/src/config.h.in" "${PROJECT_BINARY_DIR}/src/config.h" ESCAPE_QUOTES @ONLY)
configure_file(nc_client.h.in nc_client.h)
configure_file(nc_server.h.in nc_server.h)
# generate and copy public header files
configure_file("${PROJECT_SOURCE_DIR}/nc_client.h.in" "${PROJECT_BINARY_DIR}/include/nc_client.h")
configure_file("${PROJECT_SOURCE_DIR}/nc_server.h.in" "${PROJECT_BINARY_DIR}/include/nc_server.h")
configure_file("${PROJECT_SOURCE_DIR}/nc_version.h.in" "${PROJECT_BINARY_DIR}/include/nc_version.h")
file(COPY ${headers} DESTINATION "${PROJECT_BINARY_DIR}/include/libnetconf2")
# install YANG modules
install(DIRECTORY "${PROJECT_SOURCE_DIR}/modules/" DESTINATION ${YANG_MODULE_DIR} FILES_MATCHING PATTERN "*.yang")
# install library
install(TARGETS netconf2 DESTINATION ${CMAKE_INSTALL_LIBDIR})
# install headers
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nc_client.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nc_server.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${headers} ${PROJECT_BINARY_DIR}/src/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libnetconf2)
install(FILES ${PROJECT_BINARY_DIR}/include/nc_client.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${PROJECT_BINARY_DIR}/include/nc_server.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${PROJECT_BINARY_DIR}/include/nc_version.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(DIRECTORY ${PROJECT_BINARY_DIR}/include/libnetconf2 DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
# install pkg-config file
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
configure_file("libnetconf2.pc.in" "libnetconf2.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnetconf2.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
install(FILES "${PROJECT_BINARY_DIR}/libnetconf2.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
# check that pkg-config includes the used path
execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable pc_path pkg-config RESULT_VARIABLE RETURN OUTPUT_VARIABLE PC_PATH ERROR_QUIET)
if(RETURN EQUAL 0)
@ -310,6 +363,15 @@ if(PKG_CONFIG_FOUND)
endif()
endif()
# examples
if(ENABLE_EXAMPLES)
if(NOT ENABLE_SSH_TLS)
message(WARNING "Examples will not be compiled because SSH and TLS are disabled.")
else()
add_subdirectory(examples)
endif()
endif()
# tests
if(ENABLE_TESTS)
enable_testing()
@ -320,22 +382,28 @@ endif()
gen_coverage("test_.*" "test_.*_valgrind")
# generate doxygen documentation for libnetconf2 API
if(ENABLE_COMMON_TARGETS)
gen_doc("${doxy_files}" ${LIBNETCONF2_VERSION} ${LIBNETCONF2_DESCRIPTION} "")
endif()
# generate API/ABI report
if ("${BUILD_TYPE_UPPER}" STREQUAL "ABICHECK")
lib_abi_check(netconf2 "${headers}" ${LIBNETCONF2_SOVERSION_FULL} e7402149e5b36de7acab2e38970a3a9d6a8165d5)
lib_abi_check(netconf2 "${headers}" ${LIBNETCONF2_SOVERSION_FULL} 15fbc59efa5e6f1f7bea19ab561d03f8852caabb)
endif()
# source files to be covered by the 'format' target and a test with 'format-check' target
source_format(${format_sources})
# clean cmake cache
if(ENABLE_COMMON_TARGETS)
add_custom_target(cleancache
COMMAND make clean
COMMAND find . -iname '*cmake*' -not -name CMakeLists.txt -exec rm -rf {} +
COMMAND rm -rf Makefile Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif()
# uninstall
if(ENABLE_COMMON_TARGETS)
add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_MODULE_PATH}/uninstall.cmake")
endif()

View file

@ -0,0 +1,86 @@
# - Try to find LibPAM
# Once done this will define
#
# LIBPAM_FOUND - system has LibPAM
# LIBPAM_INCLUDE_DIRS - the LibPAM include directory
# LIBPAM_LIBRARIES - link these to use LibPAM
#
# Author Roman Janota <xjanot04@fit.vutbr.cz>
# Copyright (c) 2022 CESNET, z.s.p.o.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
if(LIBPAM_LIBRARIES AND LIBPAM_INCLUDE_DIRS)
# in cache already
set(LIBPAM_FOUND TRUE)
else()
find_path(LIBPAM_INCLUDE_DIR
NAMES
security/pam_appl.h
security/pam_modules.h
PATHS
/opt/local/include
/sw/include
${CMAKE_INCLUDE_PATH}
${CMAKE_INSTALL_PREFIX}/include
)
find_library(LIBPAM_LIBRARY
NAMES
pam
PATHS
/usr/lib
/usr/lib64
/opt/local/lib
/sw/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)
if(LIBPAM_INCLUDE_DIR AND LIBPAM_LIBRARY)
set(LIBPAM_FOUND TRUE)
# check if the function pam_start_confdir is in pam_appl.h header (added in PAM 1.4)
file(STRINGS ${LIBPAM_INCLUDE_DIR}/security/pam_appl.h PAM_CONFDIR REGEX "pam_start_confdir")
if ("${PAM_CONFDIR}" STREQUAL "")
set(LIBPAM_HAVE_CONFDIR FALSE)
else()
set(LIBPAM_HAVE_CONFDIR TRUE)
endif()
else()
set(LIBPAM_FOUND FALSE)
endif()
set(LIBPAM_INCLUDE_DIRS ${LIBPAM_INCLUDE_DIR})
set(LIBPAM_LIBRARIES ${LIBPAM_LIBRARY})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibPAM DEFAULT_MSG LIBPAM_LIBRARIES LIBPAM_INCLUDE_DIRS)
# show the LIBPAM_INCLUDE_DIRS and LIBPAM_LIBRARIES variables only in the advanced view
mark_as_advanced(LIBPAM_INCLUDE_DIRS LIBPAM_LIBRARIES)
endif()

View file

@ -0,0 +1,110 @@
# - Try to find MbedTLS
# Once done this will define
#
# MBEDTLS_FOUND - MbedTLS was found
# MBEDTLS_INCLUDE_DIRS - MbedTLS include directories
# MBEDTLS_LIBRARIES - link these to use MbedTLS
# MBEDTLS_VERSION - version of MbedTLS
#
# Author Roman Janota <janota@cesnet.cz>
# Copyright (c) 2024 CESNET, z.s.p.o.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
include(FindPackageHandleStandardArgs)
if(MBEDTLS_LIBRARIES AND MBEDTLS_INCLUDE_DIRS)
# in cache already
set(MBEDTLS_FOUND TRUE)
else()
find_path(MBEDTLS_INCLUDE_DIR
NAMES
mbedtls/ssl.h
PATHS
/opt/local/include
/sw/include
${CMAKE_INCLUDE_PATH}
${CMAKE_INSTALL_PREFIX}/include
)
find_library(MBEDTLS_LIBRARY
NAMES
libmbedtls.so
PATHS
/usr/lib
/usr/lib64
/opt/local/lib
/sw/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)
find_library(MBEDX509_LIBRARY
NAMES
libmbedx509.so
PATHS
/usr/lib
/usr/lib64
/opt/local/lib
/sw/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)
find_library(MBEDCRYPTO_LIBRARY
NAMES
libmbedcrypto.so
PATHS
/usr/lib
/usr/lib64
/opt/local/lib
/sw/lib
${CMAKE_LIBRARY_PATH}
${CMAKE_INSTALL_PREFIX}/lib
)
if(MBEDTLS_INCLUDE_DIR AND MBEDTLS_LIBRARY AND MBEDX509_LIBRARY AND MBEDCRYPTO_LIBRARY)
# learn MbedTLS version
if(EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h")
file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h" MBEDTLS_VERSION
REGEX "#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"([0-9]+\.[0-9]+\.[0-9]+)\"")
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" MBEDTLS_VERSION ${MBEDTLS_VERSION})
endif()
if(NOT MBEDTLS_VERSION)
message(STATUS "MBEDTLS_VERSION not found, assuming MbedTLS is too old and cannot be used!")
set(MBEDTLS_INCLUDE_DIR "MBEDTLS_INCLUDE_DIR-NOTFOUND")
set(MBEDTLS_LIBRARY "MBEDTLS_LIBRARY-NOTFOUND")
endif()
endif()
set(MBEDTLS_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR})
set(MBEDTLS_LIBRARIES ${MBEDTLS_LIBRARY} ${MBEDX509_LIBRARY} ${MBEDCRYPTO_LIBRARY})
find_package_handle_standard_args(MbedTLS FOUND_VAR MBEDTLS_FOUND
REQUIRED_VARS MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARIES
VERSION_VAR MBEDTLS_VERSION)
# show the MBEDTLS_INCLUDE_DIR and MBEDTLS_LIBRARIES variables only in the advanced view
mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARIES)
endif()

View file

@ -2,7 +2,11 @@
# check that format checking is available - always use before SOURCE_FORMAT
macro(SOURCE_FORMAT_ENABLE)
find_package(Uncrustify 0.71)
if(NOT ${ARGC} EQUAL 1)
message(FATAL_ERROR "source_format_enable() needs the required Uncrustify version!")
endif()
find_package(Uncrustify ${ARGV0})
if(UNCRUSTIFY_FOUND)
set(SOURCE_FORMAT_ENABLED TRUE)
else()

View file

@ -6,7 +6,7 @@
# Additionally, "compat.h" include directory is added and can be included.
#
# Author Michal Vasko <mvasko@cesnet.cz>
# Copyright (c) 2021 CESNET, z.s.p.o.
# Copyright (c) 2021 - 2023 CESNET, z.s.p.o.
#
# This source code is licensed under BSD 3-Clause License (the "License").
# You may not use this file except in compliance with the License.
@ -15,7 +15,6 @@
# https://opensource.org/licenses/BSD-3-Clause
#
include(CheckSymbolExists)
include(CheckFunctionExists)
include(CheckIncludeFile)
include(TestBigEndian)
if(POLICY CMP0075)
@ -24,10 +23,35 @@ endif()
macro(USE_COMPAT)
# compatibility checks
set(CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__BSD_VISIBLE=1)
set(CMAKE_REQUIRED_LIBRARIES pthread)
check_symbol_exists(_POSIX_TIMERS "unistd.h" HAVE_CLOCK)
if(NOT HAVE_CLOCK)
message(FATAL_ERROR "Missing support for clock_gettime() and similar functions!")
endif()
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads)
list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
check_symbol_exists(pthread_mutex_timedlock "pthread.h" HAVE_PTHREAD_MUTEX_TIMEDLOCK)
check_symbol_exists(pthread_mutex_clocklock "pthread.h" HAVE_PTHREAD_MUTEX_CLOCKLOCK)
check_symbol_exists(pthread_rwlock_timedrdlock "pthread.h" HAVE_PTHREAD_RWLOCK_TIMEDRDLOCK)
check_symbol_exists(pthread_rwlock_clockrdlock "pthread.h" HAVE_PTHREAD_RWLOCK_CLOCKRDLOCK)
check_symbol_exists(pthread_rwlock_timedwrlock "pthread.h" HAVE_PTHREAD_RWLOCK_TIMEDWRLOCK)
check_symbol_exists(pthread_rwlock_clockwrlock "pthread.h" HAVE_PTHREAD_RWLOCK_CLOCKWRLOCK)
check_symbol_exists(pthread_cond_clockwait "pthread.h" HAVE_PTHREAD_COND_CLOCKWAIT)
if(HAVE_PTHREAD_MUTEX_CLOCKLOCK)
# can use CLOCK_MONOTONIC only if we have pthread_mutex_clocklock()
check_symbol_exists(_POSIX_MONOTONIC_CLOCK "unistd.h" HAVE_CLOCK_MONOTONIC)
endif()
if(HAVE_CLOCK_MONOTONIC)
set(COMPAT_CLOCK_ID "CLOCK_MONOTONIC")
else()
set(COMPAT_CLOCK_ID "CLOCK_REALTIME")
endif()
check_symbol_exists(vdprintf "stdio.h;stdarg.h" HAVE_VDPRINTF)
check_symbol_exists(asprintf "stdio.h" HAVE_ASPRINTF)
@ -41,14 +65,26 @@ macro(USE_COMPAT)
check_symbol_exists(get_current_dir_name "unistd.h" HAVE_GET_CURRENT_DIR_NAME)
check_function_exists(pthread_mutex_timedlock HAVE_PTHREAD_MUTEX_TIMEDLOCK)
check_function_exists(timegm HAVE_TIMEGM)
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
# crypt
check_include_file("crypt.h" HAVE_CRYPT_H)
if(${CMAKE_SYSTEM_NAME} MATCHES "QNX")
list(APPEND CMAKE_REQUIRED_LIBRARIES -llogin)
elseif(NOT APPLE)
list(APPEND CMAKE_REQUIRED_LIBRARIES -lcrypt)
endif()
check_symbol_exists(crypt_r "crypt.h" HAVE_CRYPT_R)
test_big_endian(IS_BIG_ENDIAN)
check_include_file("stdatomic.h" HAVE_STDATOMIC)
unset(CMAKE_REQUIRED_DEFINITIONS)
unset(CMAKE_REQUIRED_LIBRARIES)
list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L)
list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D__BSD_VISIBLE=1)
list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
# header and source file (adding the source directly allows for hiding its symbols)
configure_file(${PROJECT_SOURCE_DIR}/compat/compat.h.in ${PROJECT_BINARY_DIR}/compat/compat.h @ONLY)

View file

@ -561,7 +561,7 @@ INLINE_INFO = YES
# name. If set to NO, the members will appear in declaration order.
# The default value is: YES.
SORT_MEMBER_DOCS = YES
SORT_MEMBER_DOCS = NO
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
# descriptions of file, namespace and class members alphabetically by member
@ -2069,7 +2069,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
PREDEFINED = NC_ENABLED_SSH NC_ENABLED_TLS
PREDEFINED = NC_ENABLED_SSH_TLS
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
@ -2184,7 +2184,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
HAVE_DOT = @HAVE_DOT@
HAVE_DOT = YES
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of

45
FAQ.md
View file

@ -7,7 +7,7 @@ __Q: Having a fresh installation of *netopeer2-server*, when I connect to it I s
__A:__ You are using *libssh* that was compiled with *gcrypt* library
as the crypto backend. It does not support default SSH keys generated
during *netopeer2-server* installation. To fix, disable suport for this
during *netopeer2-server* installation. To fix, disable support for this
backend when compiling *libssh* so that some other one is used.
__Q: When a new NETCONF session is being created, I see the error:__
@ -17,8 +17,9 @@ Starting the SSH session failed ()
__A:__ The most likely reason for this is that the SSH key that is used
for this session authentication uses an algorithm not supported by
your system. The supported algorithms are automatically loaded by *libssh*
from OpenSSH configuration files (more info in `ssh_config(5)` and `sshd_config(5)`).
your system. The supported algorithms can be configured but if not, they
are automatically loaded by *libssh* from OpenSSH configuration files
(more info in `ssh_config(5)` and `sshd_config(5)`).
__Q: When I try to connect to a server I immediately get a timeout after authenticating:__
@ -36,3 +37,41 @@ __A:__ There are 2 most common reasons for this error. Either you are not using
To fix, use a NETCONF client instead. Another reason may be that you are using *libssh*
version 0.9.4. It includes a [regression bug](https://gitlab.com/libssh/libssh-mirror/-/merge_requests/101)
that causes this problem and you must use another version to fix it.
__Q: When I try to enter authentication tokens, they always echo back even though I set echo off:__
__A:__ You are most likely using an older version of *libssh* which contains a bug.
The bug was fixed in *libssh* 0.9.0, so you must use at least that version.
__Q: When connecting over SSH and using publickey authentication, can I use a certificate:__
__A:__ No, it is not possible. There are currently 2 main types of certificates - *X.509v3* and *OpenSSH*.
*X.509v3* certificates for Secure Shell Authentication are a part of *NETCONF* specification
according to [RFC 6187](https://datatracker.ietf.org/doc/html/rfc6187), however using them
is currently not supported by *libssh* (version 0.9.6 as of writing this), which *libnetconf2* depends on.
As per the RFC mentioned before there are currently these `publickey` algorithms for *X.509v3*
supported by *NETCONF*: `x509v3-ssh-dss`, `x509v3-ssh-rsa`, `x509v3-rsa2048-sha256` and the family of
Elliptic Curve Digital Signature Algorithms `x509v3-ecdsa-sha2-*`. *libssh* 0.9.6 supports
these certificate publickey algorithms: `ssh-ed25519-cert-v01@openssh.com`,
`ecdsa-sha2-nistp521-cert-v01@openssh.com`, `ecdsa-sha2-nistp384-cert-v01@openssh.com`,
`ecdsa-sha2-nistp256-cert-v01@openssh.com`, `rsa-sha2-512-cert-v01@openssh.com`,
`rsa-sha2-256-cert-v01@openssh.com`, `ssh-rsa-cert-v01@openssh.com` and `ssh-dss-cert-v01@openssh.com`.
On the other hand there is a basic support for *OpenSSH* certificates in *libssh*.
The problem is that they are very minimalistic compared to *X.509v3* certificates
as per this [document](https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD).
So when `publickey` authentication happens only the client's `publickey`,
which is extracted from the certificate, is sent to the server instead of the whole certificate.
This means that the `cert-to-name` process required by *NETCONF* can not take place. Specifically,
OpenSSH certificates are missing important fields such as `Common Name`, `Subject Alternative Name` and so on.
__Q: I have client-side keepalives and monitoring enabled, but it takes a long time for the client to detect that the connection was terminated:__
__A:__ Assuming that the network connection is fine or is loopback, then this is the standard TCP behavior.
The client will not immediately detect that the connection was terminated unless
it tries to send some data or unless a specific timeout occurs.
Even though the server was terminated, its socket remains in a lingering state for some time and continues to reply to incoming
TCP keepalive packets. In particular, this timeout you're encountering is most likely affected by the `tcp_fin_timeout` kernel parameter,
which controls how long the TCP stack waits before timing out a half-closed connection after receiving a FIN packet.
The default value is typically 60 seconds, but it can be configured based on your needs.

View file

@ -4,11 +4,12 @@
# LIBNETCONF2_FOUND - system has LibNETCONF2
# LIBNETCONF2_INCLUDE_DIRS - the LibNETCONF2 include directory
# LIBNETCONF2_LIBRARIES - Link these to use LibNETCONF2
# LIBNETCONF2_VERSION - SO version of the found libNETCONF2 library
# LIBNETCONF2_ENABLED_SSH - LibNETCONF2 was compiled with SSH support
# LIBNETCONF2_ENABLED_TLS - LibNETCONF2 was compiled with TLS support
#
# Author Michal Vasko <mvasko@cesnet.cz>
# Copyright (c) 2020 CESNET, z.s.p.o.
# Copyright (c) 2021 CESNET, z.s.p.o.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@ -34,7 +35,6 @@
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
include(FindPackageHandleStandardArgs)
include(CheckSymbolExists)
if(LIBNETCONF2_LIBRARIES AND LIBNETCONF2_INCLUDE_DIRS)
# in cache already
@ -68,13 +68,28 @@ else()
${CMAKE_INSTALL_PREFIX}/lib
)
if(LIBNETCONF2_INCLUDE_DIR)
find_path(NC_VERSION_PATH "nc_version.h" HINTS ${LIBNETCONF2_INCLUDE_DIR})
if(NOT NC_VERSION_PATH)
message(STATUS "libnetconf2 version header not found, assuming libnetconf2 is too old and cannot be used!")
set(LIBNETCONF2_INCLUDE_DIR "LIBNETCONF2_INCLUDE_DIR-NOTFOUND")
set(LIBNETCONF2_LIBRARY "LIBNETCONF2_LIBRARY-NOTFOUND")
else()
file(READ "${NC_VERSION_PATH}/nc_version.h" NC_VERSION_FILE)
string(REGEX MATCH "#define NC_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+\"" NC_VERSION_MACRO "${NC_VERSION_FILE}")
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LIBNETCONF2_VERSION "${NC_VERSION_MACRO}")
endif()
endif()
set(LIBNETCONF2_INCLUDE_DIRS ${LIBNETCONF2_INCLUDE_DIR})
set(LIBNETCONF2_LIBRARIES ${LIBNETCONF2_LIBRARY})
mark_as_advanced(LIBNETCONF2_INCLUDE_DIRS LIBNETCONF2_LIBRARIES)
# handle the QUIETLY and REQUIRED arguments and set SYSREPO_FOUND to TRUE
# if all listed variables are TRUE
find_package_handle_standard_args(LibNETCONF2 DEFAULT_MSG LIBNETCONF2_LIBRARY LIBNETCONF2_INCLUDE_DIR)
find_package_handle_standard_args(LibNETCONF2 FOUND_VAR LIBNETCONF2_FOUND
REQUIRED_VARS LIBNETCONF2_LIBRARY LIBNETCONF2_INCLUDE_DIR
VERSION_VAR LIBNETCONF2_VERSION)
# check the configured options and make them available through cmake
list(INSERT CMAKE_REQUIRED_INCLUDES 0 "${LIBNETCONF2_INCLUDE_DIR}")

144
README.md
View file

@ -22,13 +22,14 @@ NETCONF 1.0 ([RFC 4741](https://tools.ietf.org/html/rfc4741)) as well as NETCONF
* NETCONF over pre-established transport sessions (using this mechanism the communication can be tunneled through
sshd(8), for instance).
* NETCONF Call Home ([RFC 8071](https://tools.ietf.org/html/rfc8071)).
* NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)),
* NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)).
* Compatibility with the [ietf-netconf-server](https://datatracker.ietf.org/doc/html/draft-ietf-netconf-netconf-client-server-29#name-the-ietf-netconf-server-mod) YANG module.
**libnetconf2** is maintained and further developed by the [Tools for
Monitoring and Configuration](https://www.liberouter.org/) department of
[CESNET](http://www.ces.net/). Any testing or improving/fixing the library
is welcome. Please inform us about your experiences with using **libnetconf2**
via the [issue tracker](https://github.com/CESNET/libnetconf/issues).
via the [issue tracker](https://github.com/CESNET/libnetconf2/issues).
Besides the [**libyang**](https://github.com/CESNET/libyang), **libnetconf2** is
another basic building block for the [**Netopeer2** toolset](https://github.com/CESNET/Netopeer2).
@ -51,79 +52,41 @@ and it occurs on the `master` branch, the **first response will likely be** to u
of the [**libnetconf**](https://github.com/CESNET/libnetconf) library, which
is now obsolete and should not be used.
# Installation
## Packages
## Required Dependencies
Binary RPM or DEB packages of the latest release can be built locally using `apkg`, look into `README` in
the `distro` directory.
Install the following libraries and tools the libnetconf2 depends on.
## Requirements
### libyang
Follow the [libyang instructions](https://github.com/CESNET/libyang/blob/master/README.md),
in short:
```
$ git clone https://github.com/CESNET/libyang.git
$ cd libyang; mkdir build; cd build
$ cmake ..
$ make
# make install
```
* C compiler (gcc >= 4.8.4, clang >= 3.0, ...)
* cmake >= 3.5.0
* crypt(3)
* [libyang](https://github.com/CESNET/libyang)
* libssh >= 0.9.5 (for SSH support)
* OpenSSL >= 3.0.0 or MbedTLS >= 3.5.0 (for TLS support)
* curl >= 7.30.0
### libssh
Required version is at least 0.7.1. This dependency can be removed by disabling
SSH support (see the [Build Options](#build-options) section). Below si the basic
sequence of commands for compiling and installing it from source. However, there
are packages for certain Linux distributions available [here](https://www.libssh.org/get-it/).
```
$ git clone http://git.libssh.org/projects/libssh.git
$ cd libssh; mkdir build; cd build
$ cmake ..
$ make
# make install
```
#### Optional
### OpenSSL
This dependency is required when the TLS support is enabled, which it is by
default but libssh requires it too. So, to remove this dependency, you need
to disable both SSH and TLS (see the [Build Options](#build-options) section).
* libpam (for PAM-based SSH `keyboard-interactive` authentication method)
* libval (only for DNSSEC SSHFP retrieval)
* [DNSSEC-Tools/dnssec-tools/validator](https://github.com/DNSSEC-Tools/DNSSEC-Tools/tree/master/dnssec-tools/validator)
part of the DNSSEC-Tools suite
* doxygen (for generating documentation)
* cmocka >= 1.0.1 (for tests only, see [Tests](#Tests))
* valgrind (for enhanced testing)
* gcov (for code coverage)
* lcov (for code coverage)
* genhtml (for code coverage)
OpenSSL is a standard part of the most distribution, so ask your package
manager for OpenSSL package including the necessary development files
(usually -dev or -devel package).
## Optional Dependencies
### libval (part of the DNSSEC-Tools suite)
It is required only if DNSSEC SSHFP retrieval is enabled (it is disabled by
default, see the [Build Options](#build-options) section).
The easier way of installing it is as the libval-dev package (or a part of
the dnssec-tools package), if you can find it for your distribution. Otherwise,
compile and install it from [source](https://github.com/DNSSEC-Tools/DNSSEC-Tools/).
Only the validator component (`DNSSEC-Tools/dnssec-tools/validator`) is needed.
### cmocka
For running the tests cmocka 1.0.1 is required (see the [Tests](#tests) section for more information).
```
$ sudo apt-get install libcmocka-dev
```
### Doxygen
For building the library documentation.
Doxygen is a standard part of the most distribution, so ask your package
manager for doxygen package.
### gcov
For code coverage, `gcov`, `lcov`, and `genhtml` are needed.
## Building libnetconf2
## Building
```
$ mkdir build; cd build
$ cmake ..
$ make
# install
# make install
```
The library documentation can be generated directly from the source codes using
@ -161,7 +124,7 @@ and enabling both the transport protocols can be made
in the same way. The following command has actually the same effect as
specifying no option since it specifies the default settings.
```
$ cmake -DENABLE_TLS=ON -DENABLE_SSH=ON ..
$ cmake -DENABLE_SSH_TLS=ON ..
```
### DNSSEC SSHFP Retrieval
@ -229,10 +192,7 @@ $ make
$ make coverage
```
Note that `gcc` compiler is required for this option and additional tools are required:
* gcov
* lcov
* genhtml
Note that `gcc` compiler is required for this option.
### CMake Notes
@ -248,7 +208,7 @@ All public functions are available via 2 headers:
#include <nc_client.h>
```
You need to include either one if imeplementing a NETCONF server or a NETCONF client,
You need to include either one if implementing a NETCONF server or a NETCONF client,
respectively.
To compile your program with libnetconf2, it is necessary to link it with it using the
@ -257,6 +217,10 @@ following linker parameters:
-lnetconf2
```
## Examples
See [examples](examples) directory for an example client and server.
## Tests
The repository includes several tests built with [cmocka](https://cmocka.org/).
@ -283,3 +247,43 @@ Tests can be run by the make's `test` target:
$ make test
```
## Supported YANG modules
### Server
The *libnetconf2* NETCONF server has two APIs that load YANG modules into the context. The first API is [nc_server_init_ctx](https://netopeer.liberouter.org/doc/libnetconf2/master/html/group__server__functions.html#ga35cccf2dbe9204abe01ccb4b93db7438), which loads the following YANG modules with their features:
- **ietf-netconf**: writable-running, candidate, rollback-on-error, validate, startup, url, xpath, confirmed-commit,
- **ietf-netconf-monitoring**: no features.
The second API is [nc_server_config_load_modules](https://netopeer.liberouter.org/doc/libnetconf2/master/html/group__server__config__functions.html#ga3760b87e3ab4309514e9ad82c4c09cdb). Supported features (marked by ✔) are loaded into the context by this API.
- **iana-crypt-hash**: crypt-hash-md5 ✔, crypt-hash-sha-256 ✔, crypt-hash-sha-512 ✔,
- **ietf-netconf-server**: ssh-listen ✔, tls-listen ✔, ssh-call-home ✔, tls-call-home ✔, central-netconf-server-supported ✔,
- **iana-ssh-encryption-algs**: no features,
- **iana-ssh-key-exchange-algs**: no features,
- **iana-ssh-mac-algs**: no features,
- **iana-ssh-public-key-algs**: no features,
- **iana-tls-cipher-suite-algs**: no features,
- **ietf-crypto-types**: cleartext-passwords ✔, cleartext-private-keys ✔, private-key-encryption ✘, csr-generation ✘, p10-csr-format ✘, certificate-expiration-notification **?**, encrypted-passwords ✘, hidden-symmetric-keys ✘, encrypted-symmetric-keys ✘, hidden-private-keys ✘, encrypted-private-keys ✘, one-symmetric-key-format ✘, one-asymmetric-key-format ✘, symmetrically-encrypted-value-format ✘, asymmetrically-encrypted-value-format ✘, cms-enveloped-data-format ✘, cms-encrypted-data-format ✘, cleartext-symmetric-keys ✘,
- **ietf-keystore**: central-keystore-supported ✔, inline-definitions-supported ✔, asymmetric-keys ✔, symmetric-keys ✘,
- **ietf-netconf-server**: ssh-listen ✔, tls-listen ✔, ssh-call-home ✔, tls-call-home ✔, central-netconf-server-supported ✔,
- **ietf-ssh-common**: transport-params ✔, ssh-x509-certs ✘, public-key-generation ✘,
- **ietf-ssh-server**: local-users-supported **?**, local-user-auth-publickey ✔, local-user-auth-password ✔, local-user-auth-none ✔, ssh-server-keepalives ✘, local-user-auth-hostbased ✘,
- **ietf-tcp-client**: tcp-client-keepalives ✔, proxy-connect ✘, socks5-gss-api ✘, socks5-username-password ✘, local-binding-supported ✔,
- **ietf-tcp-common**: transport-params ✔, ssh-x509-certs ✘, public-key-generation ✘,
- **ietf-tcp-server**: tcp-server-keepalives ✔,
- **ietf-tls-common**: tls10 ✔, tls11 ✔, tls12 ✔, tls13 ✔, hello-params ✔, public-key-generation ✘,
- **ietf-tls-server**: server-ident-x509-cert ✔, client-auth-supported ✔, client-auth-x509-cert ✔, tls-server-keepalives ✘, server-ident-raw-public-key ✘, server-ident-tls12-psk ✘, server-ident-tls13-epsk ✘, client-auth-raw-public-key ✘, client-auth-tls12-psk ✘, client-auth-tls13-epsk ✘,
- **ietf-truststore**: central-truststore-supported ✔, inline-definitions-supported ✔, certificates ✔, public-keys ✔,
- **ietf-x509-cert-to-name**: no features,
- **libnetconf2-netconf-server**: no features.
The following features can be enabled/disabled to influence the behaviour of the `libnetconf2` NETCONF server:
- `local-users-supported` - enabled by default, disable to change the behaviour of the SSH authentication (see the *libnetconf2* [documentation](https://netopeer.liberouter.org/doc/libnetconf2/master/html/howtoserver.html)).
- `certificate-expiration-notification` - disabled by default, but certificate expiration notifications are supported and you can enable this feature to create such YANG data (see the *libnetconf2* documentation).
### Client
Currently no client specific YANG modules are supported.

30
SECURITY.md Normal file
View file

@ -0,0 +1,30 @@
# Security Policy
If you discover a security-related issue, please report it based on the instructions below.
## Reporting a Vulnerability
Please **DO NOT** file a public issue, instead report the vulnerability on the relevant
[GitHub security](https://github.com/CESNET/libnetconf2/security) page. If you do not receive any reaction within 48 hours,
please also send an email to [mvasko@cesnet.cz].
## Review Process
After receiving the report, an initial triage and technical analysis is performed to confirm the report and determine
its scope. We may request additional information in this stage of the process.
Once a reviewer has confirmed the relevance of the report, a draft security advisory will be created on GitHub. The
draft advisory will be used to discuss the issue with maintainers, the reporter(s), and where applicable, other affected
parties under embargo.
If the vulnerability is accepted, a timeline for developing a patch, public disclosure, and patch release will be
determined. If there is an embargo period on public disclosure before the patch release, the reporter(s) are expected to
participate in the discussion of the timeline and abide by agreed upon dates for public disclosure.
Usually, the reasonably complex issues are fixed within hours of being reported.
## Supported Versions
After an issue is fixed, it **WILL NOT** be backported to any released version. Instead, it is kept in the public `devel`
branch, which is periodically merged into the main branch when a new release is due. So, the issue will be fixed in the
next release after it is fixed.

View file

@ -3,7 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief compatibility functions
*
* Copyright (c) 2021 CESNET, z.s.p.o.
* Copyright (c) 2021 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
#include "compat.h"
#include <crypt.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
@ -28,6 +29,179 @@
#include <time.h>
#include <unistd.h>
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
int
pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime)
{
int64_t nsec_diff;
struct timespec cur, dur;
int rc;
/* try to acquire the lock and, if we fail, sleep for 5ms. */
while ((rc = pthread_mutex_trylock(mutex)) == EBUSY) {
/* get time */
clock_gettime(COMPAT_CLOCK_ID, &cur);
/* get time diff */
nsec_diff = 0;
nsec_diff += (((int64_t)abstime->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
nsec_diff += ((int64_t)abstime->tv_nsec) - ((int64_t)cur.tv_nsec);
if (nsec_diff <= 0) {
/* timeout */
rc = ETIMEDOUT;
break;
} else if (nsec_diff < 5000000) {
/* sleep until timeout */
dur.tv_sec = 0;
dur.tv_nsec = nsec_diff;
} else {
/* sleep 5 ms */
dur.tv_sec = 0;
dur.tv_nsec = 5000000;
}
nanosleep(&dur, NULL);
}
return rc;
}
#endif
#ifndef HAVE_PTHREAD_MUTEX_CLOCKLOCK
int
pthread_mutex_clocklock(pthread_mutex_t *mutex, clockid_t clockid, const struct timespec *abstime)
{
/* only real time supported without this function */
if (clockid != CLOCK_REALTIME) {
return EINVAL;
}
return pthread_mutex_timedlock(mutex, abstime);
}
#endif
#ifndef HAVE_PTHREAD_RWLOCK_TIMEDRDLOCK
int
pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
{
int64_t nsec_diff;
struct timespec cur, dur;
int rc;
/* try to acquire the lock and, if we fail, sleep for 5ms. */
while ((rc = pthread_rwlock_tryrdlock(rwlock)) == EBUSY) {
/* get time */
clock_gettime(COMPAT_CLOCK_ID, &cur);
/* get time diff */
nsec_diff = 0;
nsec_diff += (((int64_t)abstime->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
nsec_diff += ((int64_t)abstime->tv_nsec) - ((int64_t)cur.tv_nsec);
if (nsec_diff <= 0) {
/* timeout */
rc = ETIMEDOUT;
break;
} else if (nsec_diff < 5000000) {
/* sleep until timeout */
dur.tv_sec = 0;
dur.tv_nsec = nsec_diff;
} else {
/* sleep 5 ms */
dur.tv_sec = 0;
dur.tv_nsec = 5000000;
}
nanosleep(&dur, NULL);
}
return rc;
}
#endif
#ifndef HAVE_PTHREAD_RWLOCK_CLOCKRDLOCK
int
pthread_rwlock_clockrdlock(pthread_rwlock_t *rwlock, clockid_t clockid, const struct timespec *abstime)
{
/* only real time supported without this function */
if (clockid != CLOCK_REALTIME) {
return EINVAL;
}
return pthread_rwlock_timedrdlock(rwlock, abstime);
}
#endif
#ifndef HAVE_PTHREAD_RWLOCK_TIMEDWRLOCK
int
pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, const struct timespec *abstime)
{
int64_t nsec_diff;
struct timespec cur, dur;
int rc;
/* try to acquire the lock and, if we fail, sleep for 5ms. */
while ((rc = pthread_rwlock_trywrlock(rwlock)) == EBUSY) {
/* get time */
clock_gettime(COMPAT_CLOCK_ID, &cur);
/* get time diff */
nsec_diff = 0;
nsec_diff += (((int64_t)abstime->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
nsec_diff += ((int64_t)abstime->tv_nsec) - ((int64_t)cur.tv_nsec);
if (nsec_diff <= 0) {
/* timeout */
rc = ETIMEDOUT;
break;
} else if (nsec_diff < 5000000) {
/* sleep until timeout */
dur.tv_sec = 0;
dur.tv_nsec = nsec_diff;
} else {
/* sleep 5 ms */
dur.tv_sec = 0;
dur.tv_nsec = 5000000;
}
nanosleep(&dur, NULL);
}
return rc;
}
#endif
#ifndef HAVE_PTHREAD_RWLOCK_CLOCKWRLOCK
int
pthread_rwlock_clockwrlock(pthread_rwlock_t *rwlock, clockid_t clockid, const struct timespec *abstime)
{
/* only real time supported without this function */
if (clockid != CLOCK_REALTIME) {
return EINVAL;
}
return pthread_rwlock_timedwrlock(rwlock, abstime);
}
#endif
#ifndef HAVE_PTHREAD_COND_CLOCKWAIT
int
pthread_cond_clockwait(pthread_cond_t *cond, pthread_mutex_t *mutex, clockid_t UNUSED(clockid),
const struct timespec *abstime)
{
/* assume the correct clock is set during cond init */
return pthread_cond_timedwait(cond, mutex, abstime);
}
#endif
#ifndef HAVE_VDPRINTF
int
vdprintf(int fd, const char *format, va_list ap)
@ -200,51 +374,54 @@ get_current_dir_name(void)
#endif
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
int
pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime)
#ifndef HAVE_CRYPT_R
char *
crypt_r(const char *phrase, const char *setting, struct crypt_data *data)
{
int64_t nsec_diff;
int32_t diff;
struct timespec cur, dur;
int rc;
static pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER;
char *hash;
/* try to acquire the lock and, if we fail, sleep for 5ms. */
while ((rc = pthread_mutex_trylock(mutex)) == EBUSY) {
/* get real time */
#ifdef CLOCK_REALTIME
clock_gettime(CLOCK_REALTIME, &cur);
#else
struct timeval tv;
(void) data;
pthread_mutex_lock(&crypt_lock);
hash = crypt(phrase, setting);
pthread_mutex_unlock(&crypt_lock);
return hash;
}
gettimeofday(&tv, NULL);
cur.tv_sec = (time_t)tv.tv_sec;
cur.tv_nsec = 1000L * (long)tv.tv_usec;
#endif
/* get time diff */
nsec_diff = 0;
nsec_diff += (((int64_t)abstime->tv_sec) - ((int64_t)cur.tv_sec)) * 1000000000L;
nsec_diff += ((int64_t)abstime->tv_nsec) - ((int64_t)cur.tv_nsec);
diff = (nsec_diff ? nsec_diff / 1000000L : 0);
#ifndef HAVE_TIMEGM
time_t
timegm(struct tm *tm)
{
pthread_mutex_t tz_lock = PTHREAD_MUTEX_INITIALIZER;
time_t ret;
char *tz;
if (diff < 1) {
/* timeout */
break;
} else if (diff < 5) {
/* sleep until timeout */
dur.tv_sec = 0;
dur.tv_nsec = (long)diff * 1000000;
pthread_mutex_lock(&tz_lock);
tz = getenv("TZ");
if (tz) {
tz = strdup(tz);
}
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz) {
setenv("TZ", tz, 1);
free(tz);
} else {
/* sleep 5 ms */
dur.tv_sec = 0;
dur.tv_nsec = 5000000;
unsetenv("TZ");
}
tzset();
nanosleep(&dur, NULL);
}
pthread_mutex_unlock(&tz_lock);
return rc;
return ret;
}
#endif

View file

@ -3,7 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief compatibility functions header
*
* Copyright (c) 2021 CESNET, z.s.p.o.
* Copyright (c) 2021 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -15,12 +15,22 @@
#ifndef _COMPAT_H_
#define _COMPAT_H_
#define _GNU_SOURCE /* pthread_rwlock_t */
#cmakedefine HAVE_CRYPT_H
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
#include <alloca.h>
#include <limits.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#ifndef __WORDSIZE
# if defined __x86_64__ && !defined __ILP32__
@ -48,6 +58,15 @@
# define _PACKED
#endif
#define COMPAT_CLOCK_ID @COMPAT_CLOCK_ID@
#cmakedefine HAVE_PTHREAD_MUTEX_TIMEDLOCK
#cmakedefine HAVE_PTHREAD_MUTEX_CLOCKLOCK
#cmakedefine HAVE_PTHREAD_RWLOCK_TIMEDRDLOCK
#cmakedefine HAVE_PTHREAD_RWLOCK_CLOCKRDLOCK
#cmakedefine HAVE_PTHREAD_RWLOCK_TIMEDWRLOCK
#cmakedefine HAVE_PTHREAD_RWLOCK_CLOCKWRLOCK
#cmakedefine HAVE_PTHREAD_COND_CLOCKWAIT
#cmakedefine HAVE_VDPRINTF
#cmakedefine HAVE_ASPRINTF
#cmakedefine HAVE_VASPRINTF
@ -57,7 +76,8 @@
#cmakedefine HAVE_STRDUPA
#cmakedefine HAVE_STRCHRNUL
#cmakedefine HAVE_GET_CURRENT_DIR_NAME
#cmakedefine HAVE_PTHREAD_MUTEX_TIMEDLOCK
#cmakedefine HAVE_CRYPT_R
#cmakedefine HAVE_TIMEGM
#ifndef bswap64
#define bswap64(val) \
@ -87,6 +107,10 @@
# define ATOMIC_T atomic_uint_fast32_t
# define ATOMIC_T_MAX UINT_FAST32_MAX
# define ATOMIC64_T atomic_uint_fast64_t
# define ATOMIC64_T_MAX UINT_FAST64_MAX
# define ATOMIC_PTR_T atomic_uintptr_t
# define ATOMIC_STORE_RELAXED(var, x) atomic_store_explicit(&(var), x, memory_order_relaxed)
# define ATOMIC_LOAD_RELAXED(var) atomic_load_explicit(&(var), memory_order_relaxed)
@ -94,11 +118,20 @@
# define ATOMIC_ADD_RELAXED(var, x) atomic_fetch_add_explicit(&(var), x, memory_order_relaxed)
# define ATOMIC_DEC_RELAXED(var) atomic_fetch_sub_explicit(&(var), 1, memory_order_relaxed)
# define ATOMIC_SUB_RELAXED(var, x) atomic_fetch_sub_explicit(&(var), x, memory_order_relaxed)
# define ATOMIC_COMPARE_EXCHANGE_RELAXED(var, exp, des, result) \
result = atomic_compare_exchange_strong_explicit(&(var), &(exp), des, memory_order_relaxed, memory_order_relaxed)
# define ATOMIC_PTR_STORE_RELAXED(var, x) atomic_store_explicit(&(var), (uintptr_t)(x), memory_order_relaxed)
# define ATOMIC_PTR_LOAD_RELAXED(var) ((void *)atomic_load_explicit(&(var), memory_order_relaxed))
#else
# include <stdint.h>
# define ATOMIC_T uint32_t
# define ATOMIC_T_MAX UINT32_MAX
# define ATOMIC64_T uint64_t
# define ATOMIC64_T_MAX UINT64_MAX
# define ATOMIC_PTR_T void *
# define ATOMIC_STORE_RELAXED(var, x) ((var) = (x))
# define ATOMIC_LOAD_RELAXED(var) (var)
@ -106,6 +139,35 @@
# define ATOMIC_ADD_RELAXED(var, x) __sync_fetch_and_add(&(var), x)
# define ATOMIC_DEC_RELAXED(var) __sync_fetch_and_sub(&(var), 1)
# define ATOMIC_SUB_RELAXED(var, x) __sync_fetch_and_sub(&(var), x)
# define ATOMIC_COMPARE_EXCHANGE_RELAXED(var, exp, des, result) \
{ \
ATOMIC_T __old = __sync_val_compare_and_swap(&(var), exp, des); \
result = ATOMIC_LOAD_RELAXED(__old) == ATOMIC_LOAD_RELAXED(exp) ? 1 : 0; \
ATOMIC_STORE_RELAXED(exp, ATOMIC_LOAD_RELAXED(__old)); \
}
# define ATOMIC_PTR_STORE_RELAXED(var, x) ((var) = (x))
# define ATOMIC_PTR_LOAD_RELAXED(var) (var)
#endif
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime);
#endif
#ifndef HAVE_PTHREAD_MUTEX_CLOCKLOCK
int pthread_mutex_clocklock(pthread_mutex_t *mutex, clockid_t clockid, const struct timespec *abstime);
#endif
#ifndef HAVE_PTHREAD_RWLOCK_CLOCKRDLOCK
int pthread_rwlock_clockrdlock(pthread_rwlock_t *rwlock, clockid_t clockid, const struct timespec *abstime);
#endif
#ifndef HAVE_PTHREAD_RWLOCK_CLOCKWRLOCK
int pthread_rwlock_clockwrlock(pthread_rwlock_t *rwlock, clockid_t clockid, const struct timespec *abstime);
#endif
#ifndef HAVE_PTHREAD_COND_CLOCKWAIT
int pthread_cond_clockwait(pthread_cond_t *cond, pthread_mutex_t *mutex, clockid_t clockid, const struct timespec *abstime);
#endif
#ifndef HAVE_VDPRINTF
@ -151,8 +213,18 @@ char *strchrnul(const char *s, int c);
char *get_current_dir_name(void);
#endif
#ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK
int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime);
#ifndef HAVE_CRYPT_R
/* unused anyway */
struct crypt_data {
char a;
};
char *crypt_r(const char *phrase, const char *setting, struct crypt_data *data);
#endif
#ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm);
#endif
#endif /* _COMPAT_H_ */

17
distro/README.md Normal file
View file

@ -0,0 +1,17 @@
# upstream packaging
This directory contains upstream packaging sources in apkg format.
apkg tool can be used to build packages directly from this source repo.
See apkg docs: https://pkg.labs.nic.cz/pages/apkg/
## RPM-based system (Fedora, CentOS, SUSE, ...) quickstart
```
sudo dnf install -y git rpm-build python3-pip
pip3 install apkg
apkg build -b
```

View file

@ -3,8 +3,8 @@ name = "libnetconf2"
make_archive_script = "distro/scripts/make-archive.sh"
[upstream]
archive_url = "https://github.com/CESNET/libnetconf2/archive/refs/tags/v{{ version }}.tar.gz"
archive_url = "https://github.com/CESNET/libnetconf2/archive/v{{ version }}/libnetconf2-{{ version }}.tar.gz"
version_script = "distro/scripts/upstream-version.sh"
[apkg]
compat = 1
compat = 2

View file

@ -6,14 +6,16 @@ Priority: optional
Standards-Version: 4.5.0
Build-Depends: cmake,
debhelper (>= 10),
libyang2-dev,
libssl-dev,
libssh-dev (>= 0.7.1),
pkg-config
libyang-dev,
libssl-dev (>= 3.0.0),
libssh-dev (>= 0.9.5),
libpam0g-dev,
pkg-config,
libcurl4-openssl-dev (>= 7.30.0)
Vcs-Browser: https://github.com/CESNET/libnetconf2/tree/master
Vcs-Git: https://github.com/CESNET/libnetconf2.git
Package: libnetconf2-2
Package: libnetconf4
Depends: ${misc:Depends},
${shlibs:Depends}
Architecture: any
@ -27,8 +29,8 @@ Description: library implementing NETCONF protocol - runtime
It is implemented in C.
Package: libnetconf2-dev
Depends: libyang2-dev,
libnetconf2-2 (= ${binary:Version}),
Depends: libyang-dev,
libnetconf4 (= ${binary:Version}),
${misc:Depends}
Section: libdevel
Architecture: any
@ -41,4 +43,3 @@ Description: library implementing NETCONF protocol - development files
.
This package contains the C headers, a pkgconfig file, and .so entry
point for libnetconf2.

View file

@ -1 +0,0 @@
usr/lib/*/libnetconf2.so.*

View file

@ -3,3 +3,4 @@ usr/lib/*/pkgconfig/libnetconf2.pc
usr/include/libnetconf2/*
usr/include/nc_client.h
usr/include/nc_server.h
usr/include/nc_version.h

View file

@ -0,0 +1,2 @@
usr/lib/*/libnetconf2.so.*
usr/share/yang/modules/libnetconf2

View file

@ -3,20 +3,22 @@ Version: {{ version }}
Release: {{ release }}%{?dist}
Summary: NETCONF protocol library
Url: https://github.com/CESNET/libnetconf2
Source: libnetconf2-%{version}.tar.gz
Source: %{url}/archive/v%{version}/%{name}-%{version}.tar.gz
License: BSD
BuildRequires: cmake
BuildRequires: make
BuildRequires: gcc
BuildRequires: libssh-devel
BuildRequires: openssl-devel
BuildRequires: pam-devel
BuildRequires: pkgconfig(libyang) >= 2
BuildRequires: libcurl-devel
%package devel
Summary: Headers of libnetconf2 library
Conflicts: libnetconf-devel
Requires: %{name}%{?_isa} = %{version}-%{release}
Requires: pkgconfig
%description devel
Headers of libnetconf library.
@ -28,27 +30,24 @@ servers. NETCONF is the NETwork CONFiguration protocol introduced by IETF.
%prep
%autosetup -p1
mkdir build
%build
cd build
cmake \
-DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \
-DCMAKE_BUILD_TYPE:String="Release" \
-DCMAKE_C_FLAGS="${RPM_OPT_FLAGS}" \
-DCMAKE_CXX_FLAGS="${RPM_OPT_FLAGS}" \
..
make
%cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO -DENABLE_TESTS=OFF
%cmake_build
%install
cd build
make DESTDIR=%{buildroot} install
%cmake_install
%files
%license LICENSE
%{_libdir}/libnetconf2.so.2*
%doc README.md FAQ.md
%{_libdir}/libnetconf2.so.*
%{_datadir}/yang/modules/libnetconf2/*.yang
%dir %{_datadir}/yang/modules/libnetconf2/
%files devel
%doc CODINGSTYLE.md
%{_libdir}/libnetconf2.so
%{_libdir}/pkgconfig/libnetconf2.pc
%{_includedir}/*.h
@ -57,5 +56,5 @@ make DESTDIR=%{buildroot} install
%changelog
* Tue Oct 12 2021 Jakub Ružička <jakub.ruzicka@nic.cz> - {{ version }}-{{ release }}
* {{ now }} Jakub Ružička <jakub.ruzicka@nic.cz> - {{ version }}-{{ release }}
- upstream package

View file

@ -1,10 +1,7 @@
#!/bin/bash
# create archive from current source using git
VERSION=$(git describe --tags --always)
# skip "v" from start of version number (if it exists) and replace - with .
VERSION=${VERSION#v}
VERSION=${VERSION//[-]/.}
VERSION=$(git log --oneline -n1 --grep="^VERSION" | rev | cut -d' ' -f1 | rev)
NAMEVER=libnetconf2-$VERSION
ARCHIVE=$NAMEVER.tar.gz

1
distro/tests/control Normal file
View file

@ -0,0 +1 @@
Tests: test-pkg-config.sh

View file

@ -0,0 +1,5 @@
#!/bin/bash
set -ex
version=`pkg-config --modversion libnetconf2`
echo "$version" | grep '2\.[0-9.]\+'

View file

@ -1,31 +1,3 @@
/**
* @file libnetconf.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 main internal header.
*
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef NC_LIBNETCONF_H_
#define NC_LIBNETCONF_H_
#include "config.h"
#include "log_p.h"
#include "messages_p.h"
#include "netconf.h"
#include "session_p.h"
/* Tests whether string is empty or non-empty. */
#define strisempty(str) ((str)[0] == '\0')
#define strnonempty(str) ((str)[0] != '\0')
/**
* @mainpage About
*
@ -45,7 +17,8 @@
* - Creating NETCONF Call Home sessions ([RFC 8071](https://tools.ietf.org/html/rfc8071)).
* - Creating, sending, receiving, and replying to RPCs ([RFC 4741](https://tools.ietf.org/html/rfc4741),
* [RFC 6241](https://tools.ietf.org/html/rfc6241)).
* - Creating, sending and receiving NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)),
* - Creating, sending and receiving NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)).
* - Configuring the NETCONF server based on the [ietf-netconf-server](https://datatracker.ietf.org/doc/html/draft-ietf-netconf-netconf-client-server-29) YANG module
*
* @section about-license License
*
@ -82,13 +55,11 @@
* @page howtoinit Init and Thread-safety Information
*
* Before working with the library, it must be initialized using ::nc_client_init()
* or ::nc_server_init(). Based on how the library was compiled, also _libssh_ and/or
* _libssh_/_libcrypto_ are initialized (for multi-threaded use) too. To prevent
* any reachable memory at the end of your application, there are complementary
* destroy functions (::nc_server_destroy() and ::nc_client_destroy() available. If your
* and/or ::nc_server_init(). To prevent any reachable memory at the end of your
* application, there are complementary destroy functions
* (::nc_server_destroy() and ::nc_client_destroy() available). If your
* application is multi-threaded, call the destroy functions in the main thread,
* after all the other threads have ended. In every other thread you should call
* ::nc_thread_destroy() just before it exits.
* after all the other threads have ended.
*
* If _libnetconf2_ is used in accordance with this information, there should
* not be memory leaks of any kind at program exit. For thread-safety details
@ -103,7 +74,7 @@
* specified via ::nc_client_set_schema_searchpath(). Alternatively, _libnetconf2_ can use callback
* provided via ::nc_client_set_schema_callback(). If these ways do not succeed and the server
* implements NETCONF \<get-schema\> operation, the schema is retrieved from the server and stored
* localy into the searchpath (if specified) for a future use. If none of these methods succeed to
* locally into the searchpath (if specified) for a future use. If none of these methods succeed to
* load particular schema, the data from this schema are ignored during the communication with the
* server.
*
@ -115,8 +86,9 @@
* a case, be careful and avoid concurrent execution of the mentioned setters/getters and functions
* creating connection (no matter if it is a standard NETCONF connection or Call Home).
*
* In the client, it is thread-safe to work with distinguish NETCONF sessions since the client
* settings are thread-specific as described above.
* In the client, it is always thread-safe to work with a NETCONF session in a single thread since the client
* settings are thread-specific as described above. Generally, one can access a session in several threads
* as well but there is little incentive to do so.
*
* Server
* ------
@ -148,10 +120,6 @@
*
* - ::nc_server_init()
* - ::nc_server_destroy()
*
* Available in both __nc_client.h__ and __nc_server.h__.
*
* - ::nc_thread_destroy()
*/
/**
@ -196,8 +164,6 @@
*
* Available in __nc_client.h__.
*
* - ::nc_client_ssh_set_auth_hostkey_check_clb()
* - ::nc_client_ssh_get_auth_hostkey_check_clb()
* - ::nc_client_ssh_set_auth_password_clb()
* - ::nc_client_ssh_get_auth_password_clb()
* - ::nc_client_ssh_set_auth_interactive_clb()
@ -240,8 +206,6 @@
* - ::nc_client_tls_get_cert_key_paths()
* - ::nc_client_tls_set_trusted_ca_paths()
* - ::nc_client_tls_get_trusted_ca_paths()
* - ::nc_client_tls_set_crl_paths()
* - ::nc_client_tls_get_crl_paths()
*
* - ::nc_connect_tls()
* - ::nc_connect_libssl()
@ -279,7 +243,6 @@
*
* Available in __nc_client.h__.
*
* - ::nc_client_ssh_ch_set_auth_hostkey_check_clb()
* - ::nc_client_ssh_ch_set_auth_password_clb()
* - ::nc_client_ssh_ch_set_auth_interactive_clb()
* - ::nc_client_ssh_ch_set_auth_privkey_passphrase_clb()
@ -300,8 +263,6 @@
* - ::nc_client_tls_ch_get_cert_key_paths()
* - ::nc_client_tls_ch_set_trusted_ca_paths()
* - ::nc_client_tls_ch_get_trusted_ca_paths()
* - ::nc_client_tls_ch_set_crl_paths()
* - ::nc_client_tls_ch_get_crl_paths()
*
* - ::nc_accept_callhome()
*
@ -319,14 +280,10 @@
* Init
* ====
*
* Server takes an argument for its [initialization function](@ref howtoinit).
* In it, you set the server context, which determines what modules it
* supports and what capabilities to advertise. Few capabilities that
* Server must start with [initialization](@ref howtoinit). Its capabilities are
* determined by the context used when accepting new NETCONF sessions. Few capabilities that
* cannot be learnt from the context are set with separate functions
* ::nc_server_set_capab_withdefaults() and generally ::nc_server_set_capability().
* Timeout for receiving the _hello_ message on a new session can be set
* by ::nc_server_set_hello_timeout() and the timeout for disconnecting
* an inactive session by ::nc_server_set_idle_timeout().
*
* Context does not only determine server modules, but its overall
* functionality as well. For every RPC the server should support,
@ -337,13 +294,7 @@
* establish SSH or TLS transport or do it yourself and only provide the file
* descriptors of the connection.
*
* Server options can be only set, there are no getters.
*
* To be able to accept any connections, endpoints must first be added
* with ::nc_server_add_endpt() and configured with ::nc_server_endpt_set_address()
* and ::nc_server_endpt_set_port(). For unix sockets, ::nc_server_endpt_set_perms()
* is available to set the unix socket file permissions, and ::nc_server_endpt_set_port()
* is invalid.
* To be able to accept any connections, the server must first be configured.
*
* Functions List
* --------------
@ -352,46 +303,141 @@
*
* - ::nc_server_set_capab_withdefaults()
* - ::nc_server_set_capability()
* - ::nc_server_set_hello_timeout()
* - ::nc_server_set_idle_timeout()
* - ::nc_server_endpt_count()
* - ::nc_server_add_endpt_unix_socket_listen()
* - ::nc_server_del_endpt_unix_socket()
*
* - ::nc_server_add_endpt()
* - ::nc_server_del_endpt()
* - ::nc_server_endpt_set_address()
* - ::nc_server_endpt_set_port()
* - ::nc_server_endpt_set_perms()
*
*
* SSH
* Server Configuration
* ===
*
* To successfully accept an SSH session you must set at least the host key using
* ::nc_server_ssh_endpt_add_hostkey(), which are ordered. This way you simply add
* some hostkey identifier, but the key itself will be retrieved always when needed
* by calling the callback set by ::nc_server_ssh_set_hostkey_clb().
* To successfully accept connections on a server, you first need to configure it.
* The *libnetconf2* server natively supports the *ietf-netconf-server YANG* module.
* This allows for a bigger scaling and flexibility of the *NETCONF* server.
* By using *ietf-netconf-server YANG* data you can express network configurations
* in a standardized and hierarchical format, enabling you to define complex network
* structures with greater ease.
*
* There are also some other optional settings. Note that authorized
* public keys are set for the server as a whole, not endpoint-specifically.
* The process of configuring a server is comprised of two steps. The first step is creating the
* configuration data and the second is applying it. The server supports two forms of the configuration
* data - *YANG data* and *YANG diff*.
*
* YANG data
* ---
* Configuring the server using YANG data simplifies the management of network services.
* With YANG data, you build a structured configuration tree and apply it as a whole.
* This approach is user-friendly, allowing you to modify the configuration by adding or deleting nodes,
* and then deploying the updated configuration tree in its entirety, providing a way to manage your server's settings.
* The *libnetconf2* library exports API functions that can help you with creation or deletion of the *YANG* data.
*
* YANG diff
* ---
* YANG diff, enriched with operation attributes, offers advanced configuration control.
* It empowers the user to make precise changes within the configuration tree,
* enabling operations like specific node deletions, additions, and modifications.
* On the other hand, unlike YANG data, YANG diff represents only a subtree of the
* changes expecting the whole configuration to be managed externally.
* For example this is done by the tool [sysrepo](https://www.sysrepo.org/).
*
* Usage
* ---
* To be able to configure the server, the required models first need to be implemented.
* To do this, see ::nc_server_config_load_modules().
* Not all of the *ietf-netconf-server* (and all of its associated modules) features are enabled.
* If you wish to see which features are enabled, extract them from the context after calling the mentioned function.
*
* If you wish not to create the __YANG data__ yourself, you may use the library's functions to do this for you.
* For example ::nc_server_config_add_address_port() creates __YANG data__ corresponding to an SSH/TLS endpoint.
* You can then apply this data by calling ::nc_server_config_setup_data() (or ::nc_server_config_setup_diff() for diff).
* See *examples/server.c* for a simple example.
*
* You may also create entries in the keystore or truststore. For example the asymmetric key and certificate entries
* in the keystore can be then referenced as the SSH hostkeys or TLS server certificates, respectively.
* As for the truststore, you may create public key and certificate entries, which can then be used
* as SSH user's public keys or TLS server's end-entity/trust-anchor certificates, respectively.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_ssh_endpt_add_hostkey()
* - ::nc_server_ssh_endpt_del_hostkey()
* - ::nc_server_ssh_endpt_mov_hostkey()
* - ::nc_server_ssh_endpt_mod_hostkey()
* - ::nc_server_ssh_endpt_set_auth_methods()
* - ::nc_server_ssh_endpt_set_auth_attempts()
* - ::nc_server_ssh_endpt_set_auth_timeout()
* - ::nc_server_config_load_modules()
* - ::nc_server_config_setup_diff()
* - ::nc_server_config_setup_data()
* - ::nc_server_config_setup_path()
*
* - ::nc_server_ssh_set_hostkey_clb()
* - ::nc_server_config_add_address_port()
* - ::nc_server_config_del_endpt()
* - ::nc_server_config_add_keystore_asym_key()
* - ::nc_server_config_del_keystore_asym_key()
* - ::nc_server_config_add_keystore_cert()
* - ::nc_server_config_del_keystore_cert()
* - ::nc_server_config_add_truststore_pubkey()
* - ::nc_server_config_del_truststore_pubkey()
* - ::nc_server_config_add_truststore_cert()
* - ::nc_server_config_del_truststore_cert()
*
* - ::nc_server_ssh_add_authkey()
* - ::nc_server_ssh_add_authkey_path()
* - ::nc_server_ssh_del_authkey()
* SSH
* ===
*
* To successfully accept an SSH session you must configure at least one host key.
* You may create this data yourself or by using ::nc_server_config_add_ssh_hostkey().
*
* It is important to decide whether the users that can connect to the SSH server should be obtained from the configuration or from the system.
* If the YANG feature *local-users-supported* is enabled (the default), then the authorized users are derived from the configuration.
* When a client connects to the server, he must be found in the configuration and he must authenticate to **all** of his configured authentication methods.
* If the feature is disabled, then the system will be used to try to authenticate the client via one of the three
* methods - publickey, keyboard-interactive or password (only one of them has to succeed).
*
* If the local users are supported then each SSH endpoint can define it's own authorized clients and their authentication methods.
* For example if you wish to create an SSH user that can authenticate using a password, use ::nc_server_config_add_ssh_user_password().
* Another option for authorized clients is to reference another endpoint's clients, however be careful not to create a cyclic reference
* (see ::nc_server_config_add_ssh_endpoint_client_ref()).
*
* \anchor ln2doc_pubkey
* The Public Key authentication method is supported. If you wish to use this method, you need to specify the given user's
* public keys, which will be compared with the key(s) presented by the SSH client when authenticating. One option is to configure
* the public keys directly in the ietf-netconf-server YANG data (inline-definition). Other option is to configure the keys' data
* in the ietf-trustore module's YANG data and then reference them (truststore-reference). The final option is to set the global
* path to file with public keys. This path may contain special tokens, see ::nc_server_ssh_set_authkey_path_format().
* If the path is set and the use-system-keys container is present in the data for the client wishing to authenticate,
* then the keys from the file will be used for authentication. If the YANG feature *local-users-supported* is disabled,
* then it's neccessary to set the path format using ::nc_server_ssh_set_authkey_path_format().
*
* \anchor ln2doc_kbdint
* The Keyboard Interactive authentication method is also supported. It can be done in three ways.
* If libpam is found, Linux PAM is used to handle the authentication. You need to specify the service name using ::nc_server_ssh_set_pam_conf_filename().
* Else if the standard functions for accessing local users are found on the system, they are used. The only Keyboard Interactive challenge will be the given
* user's password (that is if he's found on the system).
* Either way, you can always define your own callback to perform the authentication, see ::nc_server_ssh_set_interactive_auth_clb().
* The callback has a higher priority than the other two methods.
*
* There are also some other optional settings.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_config_add_ssh_hostkey()
* - ::nc_server_config_del_ssh_hostkey()
* - ::nc_server_config_add_ssh_keystore_ref()
* - ::nc_server_config_del_ssh_keystore_ref()
*
* - ::nc_server_config_add_ssh_user_pubkey()
* - ::nc_server_config_del_ssh_user_pubkey()
* - ::nc_server_config_add_ssh_user_password()
* - ::nc_server_config_del_ssh_user_password()
* - ::nc_server_config_add_ssh_user_interactive()
* - ::nc_server_config_del_ssh_user_interactive()
* - ::nc_server_config_del_ssh_user()
* - ::nc_server_config_add_ssh_truststore_ref()
* - ::nc_server_config_del_ssh_truststore_ref()
* - ::nc_server_config_add_ssh_endpoint_client_ref()
* - ::nc_server_config_del_ssh_endpoint_client_ref()
*
* - ::nc_server_ssh_set_authkey_path_format()
* - ::nc_server_ssh_set_pam_conf_filename()
* - ::nc_server_ssh_set_interactive_auth_clb()
*
* TLS
* ===
@ -399,47 +445,53 @@
* TLS works with endpoints too, but its options differ
* significantly from the SSH ones, especially in the _cert-to-name_
* options that TLS uses to derive usernames from client certificates.
* So, after starting listening on an endpoint you need to set the server
* certificate (::nc_server_tls_endpt_set_server_cert()). Its actual content
* together with the matching private key will be loaded using a callback
* from ::nc_server_tls_set_server_cert_clb(). Additional certificates needed
* for the client to verify the server's certificate chain can be loaded using
* a callback from ::nc_server_tls_set_server_cert_chain_clb().
*
* To accept client certificates, they must first be considered trusted,
* which you have three ways of achieving. You can add each of their Certificate Authority
* certificates to the trusted ones or mark a specific client certificate
* as trusted. Lastly, you can set paths with all the trusted CA certificates
* with ::nc_server_tls_endpt_set_trusted_ca_paths(). Adding specific certificates
* is also performed only as an arbitrary identificator and later retrieved from
* callback set by ::nc_server_tls_set_trusted_cert_list_clb(). But, you can add
* certficates as whole lists, not one-by-one.
* If you wish to listen on a TLS endpoint, you need to configure the endpoint's
* server certificate (see ::nc_server_config_add_tls_server_cert()).
*
* To accept client certificates, they must first be considered trusted.
* For each TLS endpoint you may configure two types of client certificates.
* The first type are end-entity (client) certificates. These are certificates that belong
* to given clients. These certificates need to be trusted.
* The second type are trust-anchor (certificate authority) certificates,
* which carry over the trust (a chain of trust).
* Another option is to reference another TLS endpoint's end-entity certificates, however be careful not to create a cyclic reference
* (see ::nc_server_config_add_tls_endpoint_client_ref()).
*
* Then, from each trusted client certificate a username must be derived
* for the NETCONF session. This is accomplished by finding a matching
* _cert-to-name_ entry. They are added using ::nc_server_tls_endpt_add_ctn().
* _cert-to-name_ entry.
*
* If you need to remove trusted certificates, you can do so with ::nc_server_tls_endpt_del_trusted_cert_list().
* To clear all Certificate Revocation Lists use ::nc_server_tls_endpt_clear_crls().
* There are some further options. For example you can configure the TLS
* version and ciphers to be used.
*
* You may also choose to use a Certificate Revocation List. These lists
* are downloaded from the URIs specified in the x509 CRLDistributionPoints extensions.
* Be mindful that if any CRL is successfully downloaded and set, then at least one of them has to belong
* to the peer (e.g. the client) certificate (in other words it has to be issued by peer's CA).
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_tls_endpt_set_server_cert()
* - ::nc_server_tls_endpt_add_trusted_cert_list()
* - ::nc_server_tls_endpt_del_trusted_cert_list()
* - ::nc_server_tls_endpt_set_trusted_ca_paths()
* - ::nc_server_tls_endpt_set_crl_paths()
* - ::nc_server_tls_endpt_clear_crls()
* - ::nc_server_tls_endpt_add_ctn()
* - ::nc_server_tls_endpt_del_ctn()
* - ::nc_server_tls_endpt_get_ctn()
* - ::nc_server_config_add_tls_server_cert()
* - ::nc_server_config_del_tls_server_cert()
* - ::nc_server_config_add_tls_keystore_ref()
* - ::nc_server_config_del_tls_keystore_ref()
*
* - ::nc_server_tls_set_server_cert_clb()
* - ::nc_server_tls_set_server_cert_chain_clb()
* - ::nc_server_tls_set_trusted_cert_list_clb()
* - ::nc_server_config_add_tls_client_cert()
* - ::nc_server_config_del_tls_client_cert()
* - ::nc_server_config_add_tls_client_cert_truststore_ref()
* - ::nc_server_config_del_tls_client_cert_truststore_ref()
* - ::nc_server_config_add_tls_ca_cert()
* - ::nc_server_config_del_tls_ca_cert()
* - ::nc_server_config_add_tls_ca_cert_truststore_ref()
* - ::nc_server_config_del_tls_ca_cert_truststore_ref()
* - ::nc_server_config_add_tls_endpoint_client_ref()
* - ::nc_server_config_del_tls_endpoint_client_ref()
* - ::nc_server_config_add_tls_ctn()
* - ::nc_server_config_del_tls_ctn()
*
* FD
* ==
@ -461,54 +513,64 @@
*
* _Call Home_ works with endpoints just like standard sessions, but
* the options are organized a bit differently and endpoints are added
* for CH clients. However, one important difference is that
* once all the mandatory options are set, _libnetconf2_ __will not__
* immediately start connecting to a client. It will do so only after
* calling ::nc_connect_ch_client_dispatch() in a separate thread.
*
* Lastly, monitoring of these sessions is up to the application.
* for CH clients.
* You may choose one of two approaches for creating a new Call Home
* session (or in other words making a server connect to a client).
* The first is to set all the required callbacks
* by calling ::nc_server_ch_set_dispatch_data(). By setting the callbacks,
* the server will automatically start connecting to a client, whenever
* a new Call Home client is created.
* The second approach is to create the Call Home thread manually.
* To do this, you need to call ::nc_connect_ch_client_dispatch(),
* which then creates a new thread and the server will start to connect.
* Unix socket _Call Home_ sessions are not supported.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_ch_add_client()
* - ::nc_server_ch_del_client()
* - ::nc_server_ch_is_client()
* - ::nc_server_ch_client_add_endpt()
* - ::nc_server_ch_client_del_endpt()
* - ::nc_server_ch_client_is_endpt()
* - ::nc_server_ch_client_endpt_set_address()
* - ::nc_server_ch_client_endpt_set_port()
* - ::nc_server_ch_client_endpt_enable_keepalives()
* - ::nc_server_ch_client_endpt_set_keepalives()
* - ::nc_server_ch_client_set_conn_type()
* - ::nc_server_ch_client_periodic_set_period()
* - ::nc_server_ch_client_periodic_set_anchor_time()
* - ::nc_server_ch_client_periodic_set_idle_timeout()
* - ::nc_server_ch_client_set_start_with()
* - ::nc_server_ch_client_set_max_attempts()
* - ::nc_connect_ch_client_dispatch()
* - ::nc_server_config_add_ch_address_port()
* - ::nc_server_config_del_ch_client()
* - ::nc_server_config_del_ch_endpt()
* - ::nc_server_config_add_ch_persistent()
* - ::nc_server_config_add_ch_period()
* - ::nc_server_config_del_ch_period()
* - ::nc_server_config_add_ch_anchor_time()
* - ::nc_server_config_del_ch_anchor_time()
* - ::nc_server_config_add_ch_idle_timeout()
* - ::nc_server_config_del_ch_idle_timeout()
* - ::nc_server_config_add_ch_reconnect_strategy()
* - ::nc_server_config_del_ch_reconnect_strategy()
*
* - ::nc_server_ssh_ch_client_endpt_add_hostkey()
* - ::nc_server_ssh_ch_client_endpt_del_hostkey()
* - ::nc_server_ssh_ch_client_endpt_mov_hostkey()
* - ::nc_server_ssh_ch_client_endpt_set_auth_methods()
* - ::nc_server_ssh_ch_client_endpt_get_auth_methods()
* - ::nc_server_ssh_ch_client_endpt_set_auth_attempts()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
*
* - ::nc_server_tls_ch_client_endpt_set_server_cert()
* - ::nc_server_tls_ch_client_endpt_add_trusted_cert_list()
* - ::nc_server_tls_ch_client_endpt_del_trusted_cert_list()
* - ::nc_server_tls_ch_client_endpt_set_trusted_ca_paths()
* - ::nc_server_tls_ch_client_endpt_set_crl_paths()
* - ::nc_server_tls_ch_client_endpt_clear_crls()
* - ::nc_server_tls_ch_client_endpt_add_ctn()
* - ::nc_server_tls_ch_client_endpt_del_ctn()
* - ::nc_server_tls_ch_client_endpt_get_ctn()
* - ::nc_server_config_add_ch_ssh_hostkey()
* - ::nc_server_config_del_ch_ssh_hostkey()
* - ::nc_server_config_add_ch_ssh_keystore_ref()
* - ::nc_server_config_del_ch_ssh_keystore_ref()
* - ::nc_server_config_add_ch_ssh_user_pubkey()
* - ::nc_server_config_del_ch_ssh_user_pubkey()
* - ::nc_server_config_add_ch_ssh_user_password()
* - ::nc_server_config_del_ch_ssh_user_password()
* - ::nc_server_config_add_ch_ssh_user_interactive()
* - ::nc_server_config_del_ch_ssh_user_interactive()
* - ::nc_server_config_del_ch_ssh_user()
* - ::nc_server_config_add_ch_ssh_truststore_ref()
* - ::nc_server_config_del_ch_ssh_truststore_ref()
*
* - ::nc_server_config_add_ch_tls_server_cert()
* - ::nc_server_config_del_ch_tls_server_cert()
* - ::nc_server_config_add_ch_tls_keystore_ref()
* - ::nc_server_config_del_ch_tls_keystore_ref()
* - ::nc_server_config_add_ch_tls_client_cert()
* - ::nc_server_config_del_ch_tls_client_cert()
* - ::nc_server_config_add_ch_tls_client_cert_truststore_ref()
* - ::nc_server_config_del_ch_tls_client_cert_truststore_ref()
* - ::nc_server_config_add_ch_tls_ca_cert()
* - ::nc_server_config_del_ch_tls_ca_cert()
* - ::nc_server_config_add_ch_tls_ca_cert_truststore_ref()
* - ::nc_server_config_del_ch_tls_ca_cert_truststore_ref()
* - ::nc_server_config_add_ch_tls_ctn()
* - ::nc_server_config_del_ch_tls_ctn()
*
* Connecting And Cleanup
* ======================
@ -588,6 +650,13 @@
* this request with ::nc_ps_accept_ssh_channel() or ::nc_session_accept_ssh_channel()
* depending on the structure you want to use as the argument.
*
* The server-side notifications are also supported. You can create a new notification
* with ::nc_server_notif_new() and send it via ::nc_server_notif_send() to subscribed clients.
* Keep in mind that the session you wish to send a notification on has to have at least one
* subscriber, see ::nc_session_inc_notif_status().
* Currently, only notifications about certificate expiration are implemented,
* see ::nc_server_notif_cert_expiration_thread_start().
*
* Functions List
* --------------
*
@ -603,6 +672,18 @@
* - ::nc_ps_clear()
* - ::nc_ps_accept_ssh_channel()
* - ::nc_session_accept_ssh_channel()
*
* - ::nc_server_notif_new()
* - ::nc_server_notif_send()
* - ::nc_server_notif_free()
* - ::nc_server_notif_get_time()
* - ::nc_session_inc_notif_status()
* - ::nc_session_dec_notif_status()
* - ::nc_session_get_notif_status()
* - ::nc_server_notif_cert_expiration_thread_start()
* - ::nc_server_notif_cert_expiration_thread_stop()
*/
/**
@ -610,7 +691,7 @@
*
* There are several timeouts which are used throughout _libnetconf2_ to
* assure that it will never indefinitely hang on any operation. Normally,
* you should not need to worry about them much necause they are set by
* you should not need to worry about them much because they are set by
* default to reasonable values for common systems. However, if your
* platform is not common (embedded, ...), adjusting these timeouts may
* save a lot of debugging and time.
@ -621,8 +702,8 @@
* You can adjust active and inactive read timeout using `cmake` variables.
* For details look into `README.md`.
*
* API Functions
* -------------
* Configurable timeouts
* ---------------------
*
* Once a new connection is established including transport protocol negotiations,
* _hello_ message is exchanged. You can set how long will the server wait for
@ -632,23 +713,10 @@
* To free up some resources, it is possible to adjust the maximum idle period
* of a session before it is disconnected. In _Call Home_, for both a persistent
* and periodic connection can this idle timeout be specified separately for each
* client using corresponding functions.
*
* Lastly, SSH user authentication timeout can be also modified. It is the time
* client. Lastly, SSH user authentication timeout can be also modified. It is the time
* a client has to successfully authenticate after connecting before it is disconnected.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_set_hello_timeout()
* - ::nc_server_get_hello_timeout()
* - ::nc_server_set_idle_timeout()
* - ::nc_server_get_idle_timeout()
* - ::nc_server_ch_client_periodic_set_idle_timeout()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
* These timeouts can be toggled by applying corresponding configuration data.
*/
/**
@ -664,6 +732,6 @@
/**
* @defgroup server Server
* @brief NETCONF server functionality.
* @{
* @} Server
*/
#endif /* NC_LIBNETCONF_H_ */

21
examples/CMakeLists.txt Normal file
View file

@ -0,0 +1,21 @@
if(NOT LIBNETCONF2_VERSION)
message(FATAL_ERROR "Please use the root CMakeLists file instead.")
endif()
# correct RPATH usage on OS X
set(CMAKE_MACOSX_RPATH TRUE)
# include all the library headers
include_directories(BEFORE "${CMAKE_SOURCE_DIR}/src")
# generate example header
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})
configure_file(example.h.in example.h)
# list of all the examples
set(examples server client)
foreach(app_name IN LISTS examples)
add_executable(${app_name} ${app_name}.c)
target_link_libraries(${app_name} netconf2)
endforeach(app_name)

134
examples/README.md Normal file
View file

@ -0,0 +1,134 @@
# libnetconf2 - examples
There are two examples `server` and `client` demonstrating a simple NETCONF server and client using libnetconf2 C library. This is an extensively documented example, which is trying to showcase the key parts of the libnetconf2 library in a simple way. The library configuration is kept to the minimum just to achieve basic functionality. Two types of transport are supported in this example: _UNIX Socket_ and _SSH_. Both examples have the `-h` option that displays their usage.
## Server
The example server provides `ietf-yang-library` state data that are returned as a reply to `get` RPC. In case an XPath filter is used it is properly applied on these data. If some unsupported parameters are specified, the server replies with a NETCONF error.
### Server Configuration
The server's default configuration can be found in the `config.json` file. The YANG data stored in this file define two SSH endpoints - they differ in port and in how clients get authenticated.
You can modify this configuration in any way you want, however, configuring the server may fail if the configuration is not valid.
## Example usage
### Server
First start the server:
```
$ server -u ./example-socket
```
The server will be started and configured per YANG data stored in the file `config.json`.
Two SSH endpoints with the addresses `127.0.0.1:10000` and `127.0.0.1:10001` will start listening for new connections.
This first endpoint has a single user that can authenticate with a password (which is set to `admin` by default).
The second endpoint has a single user that can authenticate with a publickey (the asymmetric key pair used is stored in `admin_key` and `admin_key.pub`).
The `-u` option specifies that a UNIX socket endpoint will be created and `./example-socket` is the path to where the socket will be listening.
### Client
#### UNIX socket
After the server has been run, in another terminal instance, with the default configuration:
```
$ client -u ./example-socket get "/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']"
```
In this case, `-u` means that a connection to an UNIX socket will be attempted and a path to the socket needs to be specified.
The `get` parameter is the name of the RPC and `/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']` is the RPC's optional XPath filter.
##### Server output
```
Listening for new connections! <-- server created
Connection established <-- client joined
Received RPC:
get-schema <-- name of the RPC
identifier = "ietf-datastores" <-- name of the requested YANG module
format = "ietf-netconf-monitoring:yang" <-- format of the requested YANG module
Received RPC:
get-schema
identifier = "ietf-netconf-nmda"
format = "ietf-netconf-monitoring:yang"
Received RPC:
get
filter = "(null)" <-- XPath filter has no value in the anyxml
type = "xpath" <-- defines XPath filter type (which may also be subtree)
select = "/ietf-yang-library:*" <-- contains a string representing the XPath filter
Received RPC:
get
filter = "(null)"
type = "xpath"
select = "/ietf-yang-library:yang-library/module-set/module[name='ietf-netconf']"
Received RPC:
close-session <-- communication with client terminated
```
The server received five supported RPCs. First, the client attempts to obtain basic YANG modules using `get-schema`. Then, it retrieves all the `ietf-yang-library` data to be used for creating its context, which should ideally be the same as that of the server. Next the example `get` RPC is received and lastly `close-session` RPC terminates the connection.
##### Client output
```
<get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library">
<module-set>
<name>complete</name>
<module>
<name>ietf-netconf</name> <-- requested name of a module
<revision>2013-09-29</revision>
<namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace>
<location>file:///home/roman/libnetconf2/modules/ietf-netconf@2013-09-29.yang</location>
<feature>writable-running</feature>
<feature>candidate</feature>
<feature>confirmed-commit</feature>
<feature>rollback-on-error</feature>
<feature>validate</feature>
<feature>startup</feature>
<feature>url</feature>
<feature>xpath</feature>
</module>
</module-set>
</yang-library>
</data>
</get>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="4"/>
```
The client received a single `ietf-yang-library` module based on the used filter.
#### SSH
After the server has been run, in another terminal instance, with the default configuration:
```
$ client -p 10000 get-config startup
```
In this case, `-p 10000` is the port to connect to. By default the endpoint with this port has a single authorized client that needs to authenticate with a password.
The parameter `get-config` is the name of the RPC and `startup` is the source datastore for the retrieved data of the get-config RPC.
##### Server output
```
Using SSH!
Connection established
Received RPC:
get-schema
identifier = "ietf-datastores"
format = "ietf-netconf-monitoring:yang"
Received RPC:
get-schema
identifier = "ietf-netconf-nmda"
format = "ietf-netconf-monitoring:yang"
Received RPC:
get
filter = "(null)"
type = "xpath"
select = "/ietf-yang-library:*"
Received RPC:
get-config <-- name of the RPC
candidate = "" <-- source datastore, which is of type empty
Received RPC:
close-session
```
##### Client output
```
admin@127.0.0.1 password: <-- prompts for password, type in 'admin'
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="4">
<ok/>
</rpc-reply>
```
No `startup` configuration is returned, because the example server lacks this functionality.
The _username_ in the `example.h` header file. The _password_ is located in `config.json`.
If you wish to connect to the SSH public key endpoint, you need to specify its port and the asymmetric key pair to use.
By default the command to connect would look like so:
```
$ client -p 10001 -P ~/libnetconf2/examples/admin_key.pub -i ~/libnetconf2/examples/admin_key get
```

7
examples/admin_key Normal file
View file

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDq+Oq6bYOgbFoTtSTKJrod3LgmJnrjuiXzlD7P2Dt+cAAAAJC1rL1gtay9
YAAAAAtzc2gtZWQyNTUxOQAAACDq+Oq6bYOgbFoTtSTKJrod3LgmJnrjuiXzlD7P2Dt+cA
AAAEAQm84SEphEUZEbuCRmXrMcYyv70wNEVziE/SbBC6+trOr46rptg6BsWhO1JMomuh3c
uCYmeuO6JfOUPs/YO35wAAAADXJvbWFuQHBjdmFza28=
-----END OPENSSH PRIVATE KEY-----

1
examples/admin_key.pub Normal file
View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOr46rptg6BsWhO1JMomuh3cuCYmeuO6JfOUPs/YO35w test@libnetconf2

266
examples/client.c Normal file
View file

@ -0,0 +1,266 @@
/**
* @file client.c
* @author Roman Janota <xjanot04@fit.vutbr.cz>
* @brief libnetconf2 client example
*
* @copyright
* Copyright (c) 2022 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#include "example.h"
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libyang/libyang.h>
#include "log.h"
#include "messages_client.h"
#include "netconf.h"
#include "session_client.h"
#include "session_client_ch.h"
static void
help_print()
{
printf("Example usage:\n"
" client get\n"
"\n"
" Available options:\n"
" -h, --help\t \tPrint usage help.\n"
" -p, --port\t\t<port>\tSpecify the port to connect to.\n"
" -u, --unix-path\t<path>\tConnect to a UNIX socket located at <path>.\n"
" -P, --ssh-pubkey\t<path>\tSet the path to an SSH Public key.\n"
" -i, --ssh-privkey\t<path>\tSet the path to an SSH Private key.\n\n"
" Available RPCs:\n"
" get [xpath-filter]\t\t\t\t\t send a <get> RPC with optional XPath filter\n"
" get-config [datastore] [xpath-filter]\t\t send a <get-config> RPC with optional XPath filter and datastore, the default datastore is \"running\" \n\n");
}
static enum NC_DATASTORE_TYPE
string2datastore(const char *str)
{
if (!str) {
return NC_DATASTORE_RUNNING;
}
if (!strcmp(str, "candidate")) {
return NC_DATASTORE_CANDIDATE;
} else if (!strcmp(str, "running")) {
return NC_DATASTORE_RUNNING;
} else if (!strcmp(str, "startup")) {
return NC_DATASTORE_STARTUP;
} else {
return 0;
}
}
static int
send_rpc(struct nc_session *session, NC_RPC_TYPE rpc_type, const char *param1, const char *param2)
{
enum NC_DATASTORE_TYPE datastore;
int r = 0, rc = 0;
uint64_t msg_id = 0;
struct lyd_node *envp = NULL, *op = NULL;
struct nc_rpc *rpc = NULL;
/* decide which type of RPC to send */
switch (rpc_type) {
case NC_RPC_GET:
/* create get RPC with an optional filter */
rpc = nc_rpc_get(param1, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
break;
case NC_RPC_GETCONFIG:
/* create get-config RPC with a source datastore and an optional filter */
datastore = string2datastore(param1);
if (!datastore) {
ERR_MSG_CLEANUP("Invalid name of a datastore. Use candidate, running, startup or neither.\n");
}
rpc = nc_rpc_getconfig(datastore, param2, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
break;
default:
break;
}
if (!rpc) {
ERR_MSG_CLEANUP("Error while creating a RPC\n");
}
/* send the RPC on the session and remember NETCONF message ID */
r = nc_send_rpc(session, rpc, 100, &msg_id);
if (r != NC_MSG_RPC) {
ERR_MSG_CLEANUP("Couldn't send a RPC\n");
}
/* receive the server's reply with the expected message ID
* as separate rpc-reply NETCONF envelopes and the parsed YANG output itself, if any */
r = nc_recv_reply(session, rpc, msg_id, 100, &envp, &op);
if (r != NC_MSG_REPLY) {
ERR_MSG_CLEANUP("Couldn't receive a reply from the server\n");
}
/* print the whole reply */
if (!op) {
r = lyd_print_file(stdout, envp, LYD_XML, 0);
} else {
r = lyd_print_file(stdout, op, LYD_XML, 0);
if (r) {
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
}
r = lyd_print_file(stdout, envp, LYD_XML, 0);
}
if (r) {
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
}
cleanup:
lyd_free_all(envp);
lyd_free_all(op);
nc_rpc_free(rpc);
return rc;
}
int
main(int argc, char **argv)
{
int rc = 0, opt, port = 0;
struct nc_session *session = NULL;
const char *unix_socket_path = NULL, *rpc_parameter_1 = NULL, *rpc_parameter_2 = NULL;
const char *ssh_pubkey_path = NULL, *ssh_privkey_path = NULL;
struct option options[] = {
{"help", no_argument, NULL, 'h'},
{"port", required_argument, NULL, 'p'},
{"unix-path", required_argument, NULL, 'u'},
{"ssh-pubkey", required_argument, NULL, 'P'},
{"ssh-privkey", required_argument, NULL, 'i'},
{"debug", no_argument, NULL, 'd'},
{NULL, 0, NULL, 0}
};
if (argc == 1) {
help_print();
goto cleanup;
}
/* set the path to search for schemas */
nc_client_set_schema_searchpath(MODULES_DIR);
opterr = 0;
while ((opt = getopt_long(argc, argv, "hp:u:P:i:d", options, NULL)) != -1) {
switch (opt) {
case 'h':
help_print();
goto cleanup;
case 'p':
port = strtoul(optarg, NULL, 10);
break;
case 'u':
unix_socket_path = optarg;
break;
case 'P':
ssh_pubkey_path = optarg;
break;
case 'i':
ssh_privkey_path = optarg;
break;
case 'd':
nc_verbosity(NC_VERB_DEBUG);
nc_libssh_thread_verbosity(2);
break;
default:
ERR_MSG_CLEANUP("Invalid option or missing argument\n");
}
}
if (optind == argc) {
ERR_MSG_CLEANUP("Expected the name of RPC after options\n");
}
/* check invalid args combinations */
if (unix_socket_path && port) {
ERR_MSG_CLEANUP("Both UNIX socket path and port specified. Please choose either SSH or UNIX.\n");
} else if (unix_socket_path && (ssh_pubkey_path || ssh_privkey_path)) {
ERR_MSG_CLEANUP("Both UNIX socket path and a path to key(s) specified. Please choose either SSH or UNIX.\n");
} else if ((port == 10001) && (!ssh_pubkey_path || !ssh_privkey_path)) {
ERR_MSG_CLEANUP("You need to specify both paths to private and public keys, if you want to connect to a publickey endpoint.\n");
} else if ((port == 10000) && (ssh_pubkey_path || ssh_privkey_path)) {
ERR_MSG_CLEANUP("Public or private key specified, when connecting to the password endpoint.\n");
} else if (!unix_socket_path && !port) {
ERR_MSG_CLEANUP("Neither UNIX socket or SSH specified.\n");
}
/* connect to the server using the specified transport protocol */
if (unix_socket_path) {
/* it's UNIX socket */
session = nc_connect_unix(unix_socket_path, NULL);
} else {
/* it must be SSH, so set the client SSH username to always be used when connecting to the server */
if (nc_client_ssh_set_username(SSH_USERNAME)) {
ERR_MSG_CLEANUP("Couldn't set the SSH username\n");
}
if (ssh_pubkey_path && ssh_privkey_path) {
/* set the client's SSH keypair to be used for authentication if necessary */
if (nc_client_ssh_add_keypair(ssh_pubkey_path, ssh_privkey_path)) {
ERR_MSG_CLEANUP("Couldn't set client's SSH keypair.\n");
}
}
/* try to connect via SSH */
session = nc_connect_ssh(SSH_ADDRESS, port, NULL);
}
if (!session) {
ERR_MSG_CLEANUP("Couldn't connect to the server\n");
}
/* sending a get RPC */
if (!strcmp(argv[optind], "get")) {
if (optind + 1 < argc) {
/* use the specified XPath filter */
rpc_parameter_1 = argv[optind + 1];
}
if (send_rpc(session, NC_RPC_GET, rpc_parameter_1, rpc_parameter_2)) {
rc = 1;
goto cleanup;
}
/* sending a get-config RPC */
} else if (!strcmp(argv[optind], "get-config")) {
/* use the specified datastore and optional XPath filter */
if (optind + 2 < argc) {
rpc_parameter_1 = argv[optind + 1];
rpc_parameter_2 = argv[optind + 2];
} else if (optind + 1 < argc) {
rpc_parameter_1 = argv[optind + 1];
}
if (send_rpc(session, NC_RPC_GETCONFIG, rpc_parameter_1, rpc_parameter_2)) {
rc = 1;
goto cleanup;
}
} else {
ERR_MSG_CLEANUP("Invalid name of a RPC\n");
}
cleanup:
nc_session_free(session, NULL);
nc_client_destroy();
return rc;
}

93
examples/config.json Normal file
View file

@ -0,0 +1,93 @@
{
"ietf-netconf-server:netconf-server": {
"listen": {
"idle-timeout": 10,
"endpoints": {
"endpoint": [
{
"name": "ssh-password-auth-endpt",
"ssh": {
"tcp-server-parameters": {
"local-address": "127.0.0.1",
"local-port": 10000
},
"ssh-server-parameters": {
"server-identity": {
"host-key": [
{
"name": "key",
"public-key": {
"inline-definition": {
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
"public-key": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDRIB2eNSRWU+HNWRUGKr76ghCLg8RaMlUCps9lBjnc6ggaJl2Q+TOLn8se2wAdK3lYBMz3dcqR+SlU7eB8wJAc=",
"private-key-format": "ietf-crypto-types:ec-private-key-format",
"cleartext-private-key": "MHcCAQEEICQ2fr9Jt2xluom0YQQ7HseE8YTo5reZRVcQENKUWOrooAoGCCqGSM49AwEHoUQDQgAENEgHZ41JFZT4c1ZFQYqvvqCEIuDxFoyVQKmz2UGOdzqCBomXZD5M4ufyx7bAB0reVgEzPd1ypH5KVTt4HzAkBw=="
}
}
}
]
},
"client-authentication": {
"users": {
"user": [
{
"name": "admin",
"password": "$0$admin"
}
]
}
}
}
}
},
{
"name": "ssh-pubkey-auth-endpt",
"ssh": {
"tcp-server-parameters": {
"local-address": "127.0.0.1",
"local-port": 10001
},
"ssh-server-parameters": {
"server-identity": {
"host-key": [
{
"name": "key",
"public-key": {
"inline-definition": {
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
"public-key": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDRIB2eNSRWU+HNWRUGKr76ghCLg8RaMlUCps9lBjnc6ggaJl2Q+TOLn8se2wAdK3lYBMz3dcqR+SlU7eB8wJAc=",
"private-key-format": "ietf-crypto-types:ec-private-key-format",
"cleartext-private-key": "MHcCAQEEICQ2fr9Jt2xluom0YQQ7HseE8YTo5reZRVcQENKUWOrooAoGCCqGSM49AwEHoUQDQgAENEgHZ41JFZT4c1ZFQYqvvqCEIuDxFoyVQKmz2UGOdzqCBomXZD5M4ufyx7bAB0reVgEzPd1ypH5KVTt4HzAkBw=="
}
}
}
]
},
"client-authentication": {
"users": {
"user": [
{
"name": "admin",
"public-keys": {
"inline-definition": {
"public-key": [
{
"name": "admin_key.pub",
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
"public-key": "AAAAC3NzaC1lZDI1NTE5AAAAIOr46rptg6BsWhO1JMomuh3cuCYmeuO6JfOUPs/YO35w"
}
]
}
}
}
]
}
}
}
}
}
]
}
}
}
}

41
examples/example.h.in Normal file
View file

@ -0,0 +1,41 @@
/**
* @file example.h
* @author Roman Janota <xjanot04@fit.vutbr.cz>
* @brief libnetconf2 example header
*
* @copyright
* Copyright (c) 2022 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_
#include <stdio.h>
/* directory with library YANG modules */
#define MODULES_DIR "@CMAKE_SOURCE_DIR@/modules"
/* directory with examples source code and this header */
#define EXAMPLES_DIR "@CMAKE_SOURCE_DIR@/examples"
/* SSH listening IP address */
#define SSH_ADDRESS "127.0.0.1"
/* SSH 'password' authentication exptected username and password */
#define SSH_USERNAME "admin"
/* time in microseconds to sleep for if there are no new RPCs and no new sessions */
#define BACKOFF_TIMEOUT_USECS 100
#define ERR_MSG_CLEANUP(msg) \
rc = 1; \
fprintf(stderr, "%s", msg); \
goto cleanup
#endif

380
examples/server.c Normal file
View file

@ -0,0 +1,380 @@
/**
* @file server.c
* @author Roman Janota <xjanot04@fit.vutbr.cz>
* @brief libnetconf2 server example
*
* @copyright
* Copyright (c) 2022 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include "example.h"
#include <assert.h>
#include <getopt.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libyang/libyang.h>
#include "log.h"
#include "messages_server.h"
#include "netconf.h"
#include "server_config.h"
#include "session_server.h"
#include "session_server_ch.h"
volatile int exit_application = 0;
struct lyd_node *tree;
static void
sigint_handler(int signum)
{
(void) signum;
/* notify the main loop if we should exit */
exit_application = 1;
}
static struct nc_server_reply *
get_rpc(struct lyd_node *rpc, struct nc_session *session)
{
const struct ly_ctx *ctx;
const char *xpath;
struct lyd_node *root = NULL, *root2 = NULL, *duplicate = NULL;
struct lyd_node *filter, *err;
struct lyd_meta *m, *type = NULL, *select = NULL;
struct ly_set *set = NULL;
LY_ERR ret;
ctx = nc_session_get_ctx(session);
/* load the ietf-yang-library data of the session, which represent this server's state data */
if (ly_ctx_get_yanglib_data(ctx, &root, "%u", ly_ctx_get_change_count(ctx))) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
/* search for the optional filter in the RPC */
ret = lyd_find_path(rpc, "filter", 0, &filter);
if (ret && (ret != LY_ENOTFOUND)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
if (filter) {
/* look for the expected filter attributes type and select */
LY_LIST_FOR(filter->meta, m) {
if (!strcmp(m->name, "type")) {
type = m;
}
if (!strcmp(m->name, "select")) {
select = m;
}
}
/* only XPath filter is supported */
if (!type || strcmp(lyd_get_meta_value(type), "xpath") || !select) {
err = nc_err(ctx, NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_APP);
goto error;
}
xpath = lyd_get_meta_value(select);
/* find all the subtrees matching the filter */
if (lyd_find_xpath(root, xpath, &set)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
root2 = NULL;
for (uint32_t i = 0; i < set->count; i++) {
/* create a copy of the subtree with its parent nodes */
if (lyd_dup_single(set->dnodes[i], NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS, &duplicate)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
/* merge another top-level filtered subtree into the result */
while (duplicate->parent) {
duplicate = lyd_parent(duplicate);
}
if (lyd_merge_tree(&root2, duplicate, LYD_MERGE_DESTRUCT)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
duplicate = NULL;
}
/* replace the original full data with only the filtered data */
lyd_free_siblings(root);
root = root2;
root2 = NULL;
}
/* duplicate the rpc node without its input nodes so the output nodes can be appended */
if (lyd_dup_single(rpc, NULL, 0, &duplicate)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
/* create the get RPC anyxml "data" output node with the requested data */
if (lyd_new_any(duplicate, NULL, "data", root, LYD_ANYDATA_DATATREE, LYD_NEW_ANY_USE_VALUE | LYD_NEW_VAL_OUTPUT, NULL)) {
err = nc_err(ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto error;
}
ly_set_free(set, NULL);
/* send data reply with the RPC output data */
return nc_server_reply_data(duplicate, NC_WD_UNKNOWN, NC_PARAMTYPE_FREE);
error:
ly_set_free(set, NULL);
lyd_free_siblings(root);
lyd_free_siblings(duplicate);
lyd_free_siblings(root2);
/* send error reply with the specific NETCONF error */
return nc_server_reply_err(err);
}
static struct nc_server_reply *
glob_rpc(struct lyd_node *rpc, struct nc_session *session)
{
struct lyd_node *iter;
struct lyd_meta *m;
printf("Received RPC:\n");
/* iterate over all the nodes in the RPC */
LYD_TREE_DFS_BEGIN(rpc, iter) {
/* if the node has a value, then print its name and value */
if (iter->schema->nodetype & (LYD_NODE_TERM | LYD_NODE_ANY)) {
printf(" %s = \"%s\"\n", LYD_NAME(iter), lyd_get_value(iter));
/* then iterate through all the metadata, which may include the XPath filter */
LY_LIST_FOR(iter->meta, m) {
printf(" %s = \"%s\"\n", m->name, lyd_get_meta_value(m));
}
/* else print just the name */
} else if (iter->schema->nodetype == LYS_RPC) {
printf(" %s\n", LYD_NAME(iter));
}
LYD_TREE_DFS_END(rpc, iter);
}
/* if close-session RPC is received, then call library's default function to properly close the session */
if (!strcmp(LYD_NAME(rpc), "close-session") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) {
return nc_clb_default_close_session(rpc, session);
}
/* if get-schema RPC is received, then use the library implementation of this RPC */
if (!strcmp(LYD_NAME(rpc), "get-schema") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf-monitoring")) {
return nc_clb_default_get_schema(rpc, session);
}
if (!strcmp(LYD_NAME(rpc), "get") && !strcmp(lyd_owner_module(rpc)->name, "ietf-netconf")) {
return get_rpc(rpc, session);
}
/* return an okay reply to every other RPC */
return nc_server_reply_ok();
}
static void
help_print()
{
printf("Example usage:\n"
" server -u ./unix_socket\n"
"\n"
" Available options:\n"
" -h, --help\t \tPrint usage help.\n"
" -u, --unix\t<path>\tCreate a UNIX socket endpoint at the place specified by <path>.\n\n");
}
static int
init(const char *unix_socket_path, struct ly_ctx **context, struct nc_pollsession **ps)
{
int rc = 0;
struct lyd_node *config = NULL;
/* create a libyang context that will determine which YANG modules will be supported by the server */
rc = ly_ctx_new(MODULES_DIR, 0, context);
if (rc) {
ERR_MSG_CLEANUP("Error while creating a new context.\n");
}
/* implement the base NETCONF modules */
rc = nc_server_init_ctx(context);
if (rc) {
ERR_MSG_CLEANUP("Error while initializing context.\n");
}
/* load all required modules for configuration, so the configuration of the server can be done */
rc = nc_server_config_load_modules(context);
if (rc) {
ERR_MSG_CLEANUP("Error loading modules required for configuration of the server.\n");
}
/* apply the YANG data stored in config.json */
rc = nc_server_config_setup_path(*context, EXAMPLES_DIR "/config.json");
if (rc) {
ERR_MSG_CLEANUP("Application of configuration data failed.\n");
}
/* initialize the server */
if (nc_server_init()) {
ERR_MSG_CLEANUP("Error occurred while initializing the server.\n");
}
/* create unix socket endpoint if path was set */
if (unix_socket_path) {
rc = nc_server_add_endpt_unix_socket_listen("unix-socket-endpt", unix_socket_path, -1, -1, -1);
if (rc) {
ERR_MSG_CLEANUP("Creating UNIX socket endpoint failed.\n");
}
}
/* create a new poll session structure, which is used for polling RPCs sent by clients */
*ps = nc_ps_new();
if (!*ps) {
ERR_MSG_CLEANUP("Couldn't create a poll session\n");
}
/* set the global RPC callback, which is called every time a new RPC is received */
nc_set_global_rpc_clb(glob_rpc);
/* upon receiving SIGINT the handler will notify the program that is should terminate */
signal(SIGINT, sigint_handler);
cleanup:
lyd_free_all(config);
return rc;
}
int
main(int argc, char **argv)
{
int r, opt, no_new_sessions, rc = 0;
struct ly_ctx *context = NULL;
struct nc_session *session, *new_session;
struct nc_pollsession *ps = NULL;
const char *unix_socket_path = NULL;
struct option options[] = {
{"help", no_argument, NULL, 'h'},
{"debug", no_argument, NULL, 'd'},
{"unix", required_argument, NULL, 'u'},
{NULL, 0, NULL, 0}
};
opterr = 0;
while ((opt = getopt_long(argc, argv, "hdu:", options, NULL)) != -1) {
switch (opt) {
case 'h':
help_print();
goto cleanup;
case 'd':
nc_verbosity(NC_VERB_DEBUG);
break;
case 'u':
unix_socket_path = optarg;
break;
default:
ERR_MSG_CLEANUP("Invalid option or missing argument\n");
}
}
/* initialize the server */
r = init(unix_socket_path, &context, &ps);
if (r) {
ERR_MSG_CLEANUP("Initializing the server failed.");
}
printf("Listening for new connections!\n");
while (!exit_application) {
no_new_sessions = 0;
/* try to accept new NETCONF sessions on all configured endpoints */
r = nc_accept(0, context, &session);
switch (r) {
/* session accepted and its hello message received */
case NC_MSG_HELLO:
printf("Connection established\n");
/* add the new session to the poll structure */
if (nc_ps_add_session(ps, session)) {
ERR_MSG_CLEANUP("Couldn't add session to poll\n");
}
break;
/* there were no new sessions */
case NC_MSG_WOULDBLOCK:
no_new_sessions = 1;
break;
/* session accepted, but its hello message was invalid */
case NC_MSG_BAD_HELLO:
printf("Parsing client hello message error.\n");
break;
/* something else went wrong */
case NC_MSG_ERROR:
/* accepting a session failed, but the server should continue handling RPCs on established sessions */
printf("Error while accepting a hello message.\n");
rc = 1;
break;
}
/* poll all the sessions in the structure and process a single event on a session which is then returned,
* in case it is a new RPC then the global RPC callback is also called */
r = nc_ps_poll(ps, 0, &new_session);
/* a fatal error occurred */
if (r & NC_PSPOLL_ERROR) {
ERR_MSG_CLEANUP("Error polling RPCs\n");
}
/* a session was terminated, so remove it from the ps structure and free it */
if (r & NC_PSPOLL_SESSION_TERM) {
r = nc_ps_del_session(ps, new_session);
assert(!r);
nc_session_free(new_session, NULL);
}
/* there were no new sessions and no new events on any established sessions,
* prevent active waiting by sleeping for a short period of time */
if (no_new_sessions && (r & (NC_PSPOLL_TIMEOUT | NC_PSPOLL_NOSESSIONS))) {
usleep(BACKOFF_TIMEOUT_USECS);
}
/* other set bits of the return value of nc_ps_poll() are not interesting in this example */
}
cleanup:
/* free all the remaining sessions in the ps structure before destroying the context */
if (ps) {
nc_ps_clear(ps, 1, NULL);
}
nc_ps_free(ps);
nc_server_destroy();
lyd_free_all(tree);
ly_ctx_destroy(context);
return rc;
}

View file

@ -8,5 +8,5 @@ Version: @LIBNETCONF2_VERSION@
Libs: -L${libdir} -lnetconf2
Cflags: -I${includedir}
LNC2_MAX_THREAD_COUNT=@MAX_PSPOLL_THREAD_COUNT@
LNC2_SCHEMAS_DIR=@SCHEMAS_DIR@
LN2_MAX_THREAD_COUNT=@MAX_PSPOLL_THREAD_COUNT@
LN2_SCHEMAS_DIR=@YANG_MODULE_DIR@

View file

@ -0,0 +1,124 @@
module iana-crypt-hash {
namespace "urn:ietf:params:xml:ns:yang:iana-crypt-hash";
prefix ianach;
organization "IANA";
contact
" Internet Assigned Numbers Authority
Postal: ICANN
4676 Admiralty Way, Suite 330
Marina del Rey, CA 90292
Tel: +1 310 823 9358
E-Mail: iana&iana.org";
description
"This YANG module defines a typedef for storing passwords
using a hash function, and features to indicate which hash
functions are supported by an implementation.
The latest revision of this YANG module can be obtained from
the IANA web site.
Requests for new values should be made to IANA via
email (iana&iana.org).
Copyright (c) 2014 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Simplified BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(http://trustee.ietf.org/license-info).
The initial version of this YANG module is part of RFC XXXX;
see the RFC itself for full legal notices.";
// RFC Ed.: replace XXXX with actual RFC number and remove this
// note.
// RFC Ed.: update the date below with the date of RFC publication
// and remove this note.
revision 2014-04-04 {
description
"Initial revision.";
reference
"RFC XXXX: A YANG Data Model for System Management";
}
typedef crypt-hash {
type string {
pattern
'$0$.*'
+ '|$1$[a-zA-Z0-9./]{1,8}$[a-zA-Z0-9./]{22}'
+ '|$5$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{43}'
+ '|$6$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{86}';
}
description
"The crypt-hash type is used to store passwords using
a hash function. The algorithms for applying the hash
function and encoding the result are implemented in
various UNIX systems as the function crypt(3).
A value of this type matches one of the forms:
$0$<clear text password>
$<id>$<salt>$<password hash>
$<id>$<parameter>$<salt>$<password hash>
The '$0$' prefix signals that the value is clear text. When
such a value is received by the server, a hash value is
calculated, and the string '$<id>$<salt>$' or
$<id>$<parameter>$<salt>$ is prepended to the result. This
value is stored in the configuration data store.
If a value starting with '$<id>$', where <id> is not '0', is
received, the server knows that the value already represents a
hashed value, and stores it as is in the data store.
When a server needs to verify a password given by a user, it
finds the stored password hash string for that user, extracts
the salt, and calculates the hash with the salt and given
password as input. If the calculated hash value is the same
as the stored value, the password given by the client is
accepted.
This type defines the following hash functions:
id | hash function | feature
---+---------------+-------------------
1 | MD5 | crypt-hash-md5
5 | SHA-256 | crypt-hash-sha-256
6 | SHA-512 | crypt-hash-sha-512
The server indicates support for the different hash functions
by advertising the corresponding feature.";
reference
"IEEE Std 1003.1-2008 - crypt() function
RFC 1321: The MD5 Message-Digest Algorithm
FIPS.180-3.2008: Secure Hash Standard";
}
feature crypt-hash-md5 {
description
"Indicates that the device supports the MD5
hash function in 'crypt-hash' values";
reference "RFC 1321: The MD5 Message-Digest Algorithm";
}
feature crypt-hash-sha-256 {
description
"Indicates that the device supports the SHA-256
hash function in 'crypt-hash' values";
reference "FIPS.180-3.2008: Secure Hash Standard";
}
feature crypt-hash-sha-512 {
description
"Indicates that the device supports the SHA-512
hash function in 'crypt-hash' values";
reference "FIPS.180-3.2008: Secure Hash Standard";
}
}

View file

@ -0,0 +1,389 @@
module iana-ssh-encryption-algs {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:iana-ssh-encryption-algs";
prefix sshea;
organization
"Internet Assigned Numbers Authority (IANA)";
contact
"Postal: ICANN
12025 Waterfront Drive, Suite 300
Los Angeles, CA 90094-2536
United States of America
Tel: +1 310 301 5800
Email: iana@iana.org";
description
"This module defines identities for the encryption algorithms
defined in the 'Encryption Algorithm Names' sub-registry of the
'Secure Shell (SSH) Protocol Parameters' registry maintained
by IANA.
Copyright (c) 2022 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
The initial version of this YANG module is part of RFC EEEE
(https://www.rfc-editor.org/info/rfcEEEE); see the RFC
itself for full legal notices.";
revision 2022-06-16 {
description
"Reflects contents of the encryption algorithms registry
on June 16, 2022.";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
// Typedefs
typedef encryption-algorithm-ref {
type identityref {
base "encryption-alg-base";
}
description
"A reference to a SSH encryption algorithm identifier.";
}
// Identities
identity encryption-alg-base {
description
"Base identity used to identify encryption algorithms.";
}
identity triple-des-cbc { // YANG IDs cannot begin with a number
base encryption-alg-base;
description
"3DES-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity blowfish-cbc {
base encryption-alg-base;
description
"BLOWFISH-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity twofish256-cbc {
base encryption-alg-base;
description
"TWOFISH256-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity twofish-cbc {
base encryption-alg-base;
description
"TWOFISH-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity twofish192-cbc {
base encryption-alg-base;
description
"TWOFISH192-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity twofish128-cbc {
base encryption-alg-base;
description
"TWOFISH128-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity aes256-cbc {
base encryption-alg-base;
description
"AES256-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity aes192-cbc {
base encryption-alg-base;
description
"AES192-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity aes128-cbc {
base encryption-alg-base;
status deprecated;
description
"AES128-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity serpent256-cbc {
base encryption-alg-base;
description
"SERPENT256-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity serpent192-cbc {
base encryption-alg-base;
description
"SERPENT192-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity serpent128-cbc {
base encryption-alg-base;
description
"SERPENT128-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity arcfour {
base encryption-alg-base;
status obsolete;
description
"ARCFOUR";
reference
"RFC 8758:
Deprecating RC4 in Secure Shell (SSH)";
}
identity idea-cbc {
base encryption-alg-base;
description
"IDEA-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity cast128-cbc {
base encryption-alg-base;
description
"CAST128-CBC";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity none {
base encryption-alg-base;
description
"NONE";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity des-cbc {
base encryption-alg-base;
status obsolete;
description
"DES-CBC";
reference
"FIPS 46-3:
Data Encryption Standard (DES)";
}
identity arcfour128 {
base encryption-alg-base;
status obsolete;
description
"ARCFOUR128";
reference
"RFC 8758:
Deprecating RC4 in Secure Shell (SSH)";
}
identity arcfour256 {
base encryption-alg-base;
status obsolete;
description
"ARCFOUR256";
reference
"RFC 8758:
Deprecating RC4 in Secure Shell (SSH)";
}
identity aes128-ctr {
base encryption-alg-base;
status deprecated;
description
"AES128-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity aes192-ctr {
base encryption-alg-base;
description
"AES192-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity aes256-ctr {
base encryption-alg-base;
description
"AES256-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity triple-des-ctr { // YANG IDs cannot begin with a number
base encryption-alg-base;
description
"3DES-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity blowfish-ctr {
base encryption-alg-base;
description
"BLOWFISH-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity twofish128-ctr {
base encryption-alg-base;
description
"TWOFISH128-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity twofish192-ctr {
base encryption-alg-base;
description
"TWOFISH192-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity twofish256-ctr {
base encryption-alg-base;
description
"TWOFISH256-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity serpent128-ctr {
base encryption-alg-base;
description
"SERPENT128-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity serpent192-ctr {
base encryption-alg-base;
description
"SERPENT192-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity serpent256-ctr {
base encryption-alg-base;
description
"SERPENT256-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity idea-ctr {
base encryption-alg-base;
description
"IDEA-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity cast128-ctr {
base encryption-alg-base;
description
"CAST128-CTR";
reference
"RFC 4344:
The Secure Shell (SSH) Transport Layer Encryption Modes";
}
identity aead-aes-128-gcm {
base encryption-alg-base;
description
"AEAD_AES_128_GCM";
reference
"RFC 5647:
AES Galois Counter Mode for the
Secure Shell Transport Layer Protocol";
}
identity aead-aes-256-gcm {
base encryption-alg-base;
description
"AEAD_AES_256_GCM";
reference
"RFC 5647:
AES Galois Counter Mode for the
Secure Shell Transport Layer Protocol";
}
// Protocol-accessible Nodes
container supported-algorithms {
config false;
description
"A container for a list of encryption algorithms
supported by the server.";
leaf-list supported-algorithm {
type encryption-algorithm-ref;
description
"A encryption algorithm supported by the server.";
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
module iana-ssh-mac-algs {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:iana-ssh-mac-algs";
prefix sshma;
organization
"Internet Assigned Numbers Authority (IANA)";
contact
"Postal: ICANN
12025 Waterfront Drive, Suite 300
Los Angeles, CA 90094-2536
United States of America
Tel: +1 310 301 5800
Email: iana@iana.org";
description
"This module defines identities for the MAC algorithms
defined in the 'MAC Algorithm Names' sub-registry of the
'Secure Shell (SSH) Protocol Parameters' registry maintained
by IANA.
Copyright (c) 2022 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
The initial version of this YANG module is part of RFC EEEE
(https://www.rfc-editor.org/info/rfcEEEE); see the RFC
itself for full legal notices.";
revision 2022-06-16 {
description
"Reflects contents of the MAC algorithms registry on
June 16, 2022.";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
// Typedefs
typedef mac-algorithm-ref {
type identityref {
base "mac-alg-base";
}
description
"A reference to a SSH mac algorithm identifier.";
}
// Identities
identity mac-alg-base {
description
"Base identity used to identify message authentication
code (MAC) algorithms.";
}
identity hmac-sha1 {
base mac-alg-base;
description
"HMAC-SHA1";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity hmac-sha1-96 {
base mac-alg-base;
description
"HMAC-SHA1-96";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity hmac-md5 {
base mac-alg-base;
description
"HMAC-MD5";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity hmac-md5-96 {
base mac-alg-base;
description
"HMAC-MD5-96";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity none {
base mac-alg-base;
description
"NONE";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity aead-aes-128-gcm {
base mac-alg-base;
description
"AEAD_AES_128_GCM";
reference
"RFC 5647:
AES Galois Counter Mode for the
Secure Shell Transport Layer Protocol";
}
identity aead-aes-256-gcm {
base mac-alg-base;
description
"AEAD_AES_256_GCM";
reference
"RFC 5647:
AES Galois Counter Mode for the
Secure Shell Transport Layer Protocol";
}
identity hmac-sha2-256 {
base mac-alg-base;
description
"HMAC-SHA2-256";
reference
"RFC 6668:
SHA-2 Data Integrity Verification for the
Secure Shell (SSH) Transport Layer Protocol";
}
identity hmac-sha2-512 {
base mac-alg-base;
description
"HMAC-SHA2-512";
reference
"RFC 6668:
SHA-2 Data Integrity Verification for the
Secure Shell (SSH) Transport Layer Protocol";
}
// Protocol-accessible Nodes
container supported-algorithms {
config false;
description
"A container for a list of MAC algorithms
supported by the server.";
leaf-list supported-algorithm {
type mac-algorithm-ref;
description
"A MAC algorithm supported by the server.";
}
}
}

View file

@ -0,0 +1,436 @@
module iana-ssh-public-key-algs {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:iana-ssh-public-key-algs";
prefix sshpka;
organization
"Internet Assigned Numbers Authority (IANA)";
contact
"Postal: ICANN
12025 Waterfront Drive, Suite 300
Los Angeles, CA 90094-2536
United States of America
Tel: +1 310 301 5800
Email: iana@iana.org";
description
"This module defines identities for the public key algorithms
defined in the 'Public Key Algorithm Names' sub-registry of the
'Secure Shell (SSH) Protocol Parameters' registry maintained
by IANA.
Copyright (c) 2022 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
The initial version of this YANG module is part of RFC EEEE
(https://www.rfc-editor.org/info/rfcEEEE); see the RFC
itself for full legal notices.";
revision 2022-06-16 {
description
"Reflects contents of the public key algorithms registry
on June 16, 2022.";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
// Typedefs
typedef public-key-algorithm-ref {
type identityref {
base "public-key-alg-base";
}
description
"A reference to a SSH public key algorithm identifier.";
}
// Identities
identity public-key-alg-base {
description
"Base identity used to identify public key algorithms.";
}
identity ssh-dss {
base public-key-alg-base;
description
"SSH-DSS";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity ssh-rsa {
base public-key-alg-base;
description
"SSH-RSA";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity rsa-sha2-256 {
base public-key-alg-base;
description
"RSA-SHA2-256";
reference
"RFC 8332:
Use of RSA Keys with SHA-256 and SHA-512
in the Secure Shell (SSH) Protocol";
}
identity rsa-sha2-512 {
base public-key-alg-base;
description
"RSA-SHA2-512";
reference
"RFC 8332:
Use of RSA Keys with SHA-256 and SHA-512
in the Secure Shell (SSH) Protocol";
}
identity spki-sign-rsa {
base public-key-alg-base;
description
"SPKI-SIGN-RSA";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity spki-sign-dss {
base public-key-alg-base;
description
"SPKI-SIGN-DSS";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity pgp-sign-rsa {
base public-key-alg-base;
description
"PGP-SIGN-RSA";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity pgp-sign-dss {
base public-key-alg-base;
description
"PGP-SIGN-DSS";
reference
"RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity null {
base public-key-alg-base;
description
"NULL";
reference
"RFC 4462:
Generic Security Service Application Program Interface
(GSS-API) Authentication and Key Exchange for the
Secure Shell (SSH) Protocol";
}
identity ecdsa-sha2-nistp256 {
base public-key-alg-base;
status deprecated;
description
"ECDSA-SHA2-NISTP256 (secp256r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-nistp384 {
base public-key-alg-base;
description
"ECDSA-SHA2-NISTP384 (secp384r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-nistp521 {
base public-key-alg-base;
description
"ECDSA-SHA2-NISTP521 (secp521r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.1 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.1 (nistk163, sect163k1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.2.840.10045.3.1.1 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.2.840.10045.3.1.1 (nistp192, secp192r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.33 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.33 (nistp224, secp224r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.26 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.26 (nistk233, sect233k1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.27 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.27 (nistb233, sect233r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.16 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.16 (nistk283, sect283k1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.36 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.36 (nistk409, sect409k1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.37 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.37 (nistb409, sect409r1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity ecdsa-sha2-1.3.132.0.38 {
base public-key-alg-base;
description
"ECDSA-SHA2-1.3.132.0.38 (nistt571, sect571k1)";
reference
"RFC 5656:
Elliptic Curve Algorithm Integration in the
Secure Shell Transport Layer";
}
identity x509v3-ssh-dss {
base public-key-alg-base;
description
"X509V3-SSH-DSS";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ssh-rsa {
base public-key-alg-base;
description
"X509V3-SSH-RSA";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-rsa2048-sha256 {
base public-key-alg-base;
status deprecated;
description
"X509V3-RSA2048-SHA256";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-nistp256 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-NISTP256 (secp256r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-nistp384 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-NISTP384 (secp384r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-nistp521 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-NISTP521 (secp521r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.1 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.1 (nistk163, sect163k1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.2.840.10045.3.1.1 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.2.840.10045.3.1.1 (nistp192, secp192r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.33 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.33 (nistp224, secp224r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.26 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.26 (nistk233, sect233k1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.27 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.27 (nistb233, sect233r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.16 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.16 (nistk283, sect283k1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.36 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.36 (nistk409, sect409k1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.37 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.37 (nistb409, sect409r1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity x509v3-ecdsa-sha2-1.3.132.0.38 {
base public-key-alg-base;
description
"X509V3-ECDSA-SHA2-1.3.132.0.38 (nistt571, sect571k1)";
reference
"RFC 6187:
X.509v3 Certificates for Secure Shell Authentication";
}
identity ssh-ed25519 {
base public-key-alg-base;
description
"SSH-ED25519";
reference
"RFC 8709:
Ed25519 and Ed448 Public Key Algorithms for the
Secure Shell (SSH) Protocol";
}
identity ssh-ed448 {
base public-key-alg-base;
description
"SSH-ED448";
reference
"RFC 8709:
Ed25519 and Ed448 Public Key Algorithms for the
Secure Shell (SSH) Protocol";
}
// Protocol-accessible Nodes
container supported-algorithms {
config false;
description
"A container for a list of public key algorithms
supported by the server.";
leaf-list supported-algorithm {
type public-key-algorithm-ref;
description
"A public key algorithm supported by the server.";
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,407 @@
module ietf-keystore {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-keystore";
prefix ks;
import ietf-netconf-acm {
prefix nacm;
reference
"RFC 8341: Network Configuration Access Control Model";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
WG List: NETCONF WG list <mailto:netconf@ietf.org>
Author: Kent Watsen <mailto:kent+ietf@watsen.net>";
description
"This module defines a 'keystore' to centralize management
of security credentials.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC CCCC
(https://www.rfc-editor.org/info/rfcCCCC); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC CCCC: A YANG Data Model for a Keystore";
}
/****************/
/* Features */
/****************/
feature central-keystore-supported {
description
"The 'central-keystore-supported' feature indicates that
the server supports the central keystore (i.e., fully
implements the 'ietf-keystore' module).";
}
feature inline-definitions-supported {
description
"The 'inline-definitions-supported' feature indicates that
the server supports locally-defined keys.";
}
feature asymmetric-keys {
description
"The 'asymmetric-keys' feature indicates that the server
implements the /keystore/asymmetric-keys subtree.";
}
feature symmetric-keys {
description
"The 'symmetric-keys' feature indicates that the server
implements the /keystore/symmetric-keys subtree.";
}
/****************/
/* Typedefs */
/****************/
typedef symmetric-key-ref {
type leafref {
path "/ks:keystore/ks:symmetric-keys/ks:symmetric-key"
+ "/ks:name";
}
description
"This typedef enables modules to easily define a reference
to a symmetric key stored in the central keystore.";
}
typedef asymmetric-key-ref {
type leafref {
path "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key"
+ "/ks:name";
}
description
"This typedef enables modules to easily define a reference
to an asymmetric key stored in the central keystore.";
}
/*****************/
/* Groupings */
/*****************/
grouping encrypted-by-grouping {
description
"A grouping that defines a 'choice' statement that can be
augmented into the 'encrypted-by' node, present in the
'symmetric-key-grouping' and 'asymmetric-key-pair-grouping'
groupings defined in RFC AAAA, enabling references to keys
in the central keystore.";
choice encrypted-by {
nacm:default-deny-write;
mandatory true;
description
"A choice amongst other symmetric or asymmetric keys.";
case symmetric-key-ref {
if-feature "central-keystore-supported";
if-feature "symmetric-keys";
leaf symmetric-key-ref {
type ks:symmetric-key-ref;
description
"Identifies the symmetric key used to encrypt the
associated key.";
}
}
case asymmetric-key-ref {
if-feature "central-keystore-supported";
if-feature "asymmetric-keys";
leaf asymmetric-key-ref {
type ks:asymmetric-key-ref;
description
"Identifies the asymmetric key whose public key
encrypted the associated key.";
}
}
}
}
// *-ref groupings
grouping asymmetric-key-certificate-ref-grouping {
description
"Grouping for the reference to a certificate associated
with an asymmetric key stored in the central keystore.";
leaf asymmetric-key {
nacm:default-deny-write;
if-feature "central-keystore-supported";
if-feature "asymmetric-keys";
type ks:asymmetric-key-ref;
must '../certificate';
description
"A reference to an asymmetric key in the keystore.";
}
leaf certificate {
nacm:default-deny-write;
type leafref {
path "/ks:keystore/ks:asymmetric-keys/ks:asymmetric-key"
+ "[ks:name = current()/../asymmetric-key]/"
+ "ks:certificates/ks:certificate/ks:name";
}
must '../asymmetric-key';
description
"A reference to a specific certificate of the
asymmetric key in the keystore.";
}
}
// inline-or-keystore-* groupings
grouping inline-or-keystore-symmetric-key-grouping {
description
"A grouping for the configuration of a symmetric key. The
symmetric key may be defined inline or as a reference to
a symmetric key stored in the central keystore.
Servers that do not define the 'central-keystore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate keystore locations.";
choice inline-or-keystore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the keystore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"Container to hold the local key definition.";
uses ct:symmetric-key-grouping;
}
}
case central-keystore {
if-feature "central-keystore-supported";
if-feature "symmetric-keys";
leaf central-keystore-reference {
type ks:symmetric-key-ref;
description
"A reference to an symmetric key that exists in
the central keystore.";
}
}
}
}
grouping inline-or-keystore-asymmetric-key-grouping {
description
"A grouping for the configuration of an asymmetric key. The
asymmetric key may be defined inline or as a reference to
an asymmetric key stored in the central keystore.
Servers that do not define the 'central-keystore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate keystore locations.";
choice inline-or-keystore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the keystore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"Container to hold the local key definition.";
uses ct:asymmetric-key-pair-grouping;
}
}
case central-keystore {
if-feature "central-keystore-supported";
if-feature "asymmetric-keys";
leaf central-keystore-reference {
type ks:asymmetric-key-ref;
description
"A reference to an asymmetric key that exists in
the central keystore. The intent is to reference
just the asymmetric key without any regard for
any certificates that may be associated with it.";
}
}
}
}
grouping inline-or-keystore-asymmetric-key-with-certs-grouping {
description
"A grouping for the configuration of an asymmetric key and
its associated certificates. The asymmetric key and its
associated certificates may be defined inline or as a
reference to an asymmetric key (and its associated
certificates) in the central keystore.
Servers that do not define the 'central-keystore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate keystore locations.";
choice inline-or-keystore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the keystore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"Container to hold the local key definition.";
uses ct:asymmetric-key-pair-with-certs-grouping;
}
}
case central-keystore {
if-feature "central-keystore-supported";
if-feature "asymmetric-keys";
leaf central-keystore-reference {
type ks:asymmetric-key-ref;
description
"A reference to an asymmetric-key (and all of its
associated certificates) in the keystore, when
this module is implemented.";
}
}
}
}
grouping inline-or-keystore-end-entity-cert-with-key-grouping {
description
"A grouping for the configuration of an asymmetric key and
its associated end-entity certificate. The asymmetric key
and its associated end-entity certificate may be defined
inline or as a reference to an asymmetric key (and its
associated end-entity certificate) in the central keystore.
Servers that do not define the 'central-keystore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate keystore locations.";
choice inline-or-keystore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the keystore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"Container to hold the local key definition.";
uses ct:asymmetric-key-pair-with-cert-grouping;
}
}
case central-keystore {
if-feature "central-keystore-supported";
if-feature "asymmetric-keys";
container central-keystore-reference {
uses asymmetric-key-certificate-ref-grouping;
description
"A reference to a specific certificate associated with
an asymmetric key stored in the central keystore.";
}
}
}
}
// the keystore grouping
grouping keystore-grouping {
description
"Grouping definition enables use in other contexts. If ever
done, implementations MUST augment new 'case' statements
into the various inline-or-keystore 'choice' statements to
supply leafrefs to the model-specific location(s).";
container asymmetric-keys {
nacm:default-deny-write;
if-feature "asymmetric-keys";
description
"A list of asymmetric keys.";
list asymmetric-key {
key "name";
description
"An asymmetric key.";
leaf name {
type string;
description
"An arbitrary name for the asymmetric key.";
}
uses ct:asymmetric-key-pair-with-certs-grouping;
}
}
container symmetric-keys {
nacm:default-deny-write;
if-feature "symmetric-keys";
description
"A list of symmetric keys.";
list symmetric-key {
key "name";
description
"A symmetric key.";
leaf name {
type string;
description
"An arbitrary name for the symmetric key.";
}
uses ct:symmetric-key-grouping;
}
}
}
/*********************************/
/* Protocol accessible nodes */
/*********************************/
container keystore {
if-feature central-keystore-supported;
description
"A central keystore containing a list of symmetric keys and
a list of asymmetric keys.";
nacm:default-deny-write;
uses keystore-grouping {
augment "symmetric-keys/symmetric-key/key-type/encrypted-key/"
+ "encrypted-key/encrypted-by" {
description
"Augments in a choice statement enabling the encrypting
key to be any other symmetric or asymmetric key in the
central keystore.";
uses encrypted-by-grouping;
}
augment "asymmetric-keys/asymmetric-key/private-key-type/"
+ "encrypted-private-key/encrypted-private-key/"
+ "encrypted-by" {
description
"Augments in a choice statement enabling the encrypting
key to be any other symmetric or asymmetric key in the
central keystore.";
uses encrypted-by-grouping;
}
}
}
}

View file

@ -0,0 +1,685 @@
module ietf-netconf-server {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-server";
prefix ncs;
import ietf-yang-types {
prefix yang;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-x509-cert-to-name {
prefix x509c2n;
reference
"RFC 7407: A YANG Data Model for SNMP Configuration";
}
import ietf-tcp-client {
prefix tcpc;
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
import ietf-tcp-server {
prefix tcps;
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
import ietf-ssh-common {
prefix sshcmn;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import ietf-ssh-server {
prefix sshs;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import ietf-tls-server {
prefix tlss;
reference
"RFC FFFF: YANG Groupings for TLS Clients and TLS Servers";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
WG List: NETCONF WG list <mailto:netconf@ietf.org>
Author: Kent Watsen <mailto:kent+ietf@watsen.net>";
description
"This module contains a collection of YANG definitions
for configuring NETCONF servers.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC HHHH
(https://www.rfc-editor.org/info/rfcHHHH); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC HHHH: NETCONF Client and Server Models";
}
// Features
feature ssh-listen {
description
"The 'ssh-listen' feature indicates that the NETCONF server
supports opening a port to accept NETCONF over SSH
client connections.";
reference
"RFC 6242:
Using the NETCONF Protocol over Secure Shell (SSH)";
}
feature tls-listen {
description
"The 'tls-listen' feature indicates that the NETCONF server
supports opening a port to accept NETCONF over TLS
client connections.";
reference
"RFC 7589: Using the NETCONF Protocol over Transport
Layer Security (TLS) with Mutual X.509
Authentication";
}
feature ssh-call-home {
description
"The 'ssh-call-home' feature indicates that the NETCONF
server supports initiating a NETCONF over SSH call
home connection to NETCONF clients.";
reference
"RFC 8071: NETCONF Call Home and RESTCONF Call Home";
}
feature tls-call-home {
description
"The 'tls-call-home' feature indicates that the NETCONF
server supports initiating a NETCONF over TLS call
home connection to NETCONF clients.";
reference
"RFC 8071: NETCONF Call Home and RESTCONF Call Home";
}
feature central-netconf-server-supported {
description
"The 'central-netconf-server-supported' feature indicates
that the server supports the top-level 'netconf-server'
node.
This feature is needed as some servers may want to use
features defined in this module, which requires this
module to be implemented, without having to support
the top-level 'netconf-server' node.";
}
// Groupings
grouping netconf-server-grouping {
description
"A reusable grouping for configuring a NETCONF server
without any consideration for how underlying transport
sessions are established.
Note that this grouping uses a fairly typical descendant
node name such that a stack of 'uses' statements will
have name conflicts. It is intended that the consuming
data model will resolve the issue by wrapping the 'uses'
statement in a container called, e.g.,
'netconf-server-parameters'. This model purposely does
not do this itself so as to provide maximum flexibility
to consuming models.";
container client-identity-mappings {
description
"Specifies mappings through which NETCONF client X.509
certificates are used to determine a NETCONF username,
per RFC 7407.
For TLS-based transports, if no matching and valid
cert-to-name list entry can be found, then the NETCONF
server MUST close the connection, and MUST NOT accept
NETCONF messages over it, per Section 7 in RFC 7589.
For SSH-based transports, a matching cert-to-name
entry overrides the username provided by the SSH
implementation, consistent with the second paragraph
of Section 3 in RFC 6242.";
reference
"RFC 6242:
Using the NETCONF Protocol over Secure Shell (SSH)
RFC 7589:
Using the NETCONF Protocol over Transport Layer
Security (TLS) with Mutual X.509 Authentication";
uses x509c2n:cert-to-name {
refine "cert-to-name/fingerprint" {
mandatory false;
description
"A 'fingerprint' value does not need to be specified
when the 'cert-to-name' mapping is independent of
fingerprint matching. A 'cert-to-name' having no
fingerprint value will match any client certificate
and therefore should only be present at the end of
the user-ordered 'cert-to-name' list.";
}
}
}
}
grouping netconf-server-listen-stack-grouping {
description
"A reusable grouping for configuring a NETCONF server
'listen' protocol stack for listening on a single port.";
choice transport {
mandatory true;
description
"Selects between available transports.";
case ssh {
if-feature "ssh-listen";
container ssh {
description
"TCP, SSH, and NETCONF configuration to listen
for NETCONF over SSH connections.";
container tcp-server-parameters {
description
"TCP-level server parameters to listen
for NETCONF over SSH connections.";
uses tcps:tcp-server-grouping {
refine "local-port" {
default "830";
description
"The NETCONF server will listen on the
IANA-assigned well-known port value
for 'netconf-ssh' (830) if no value
is specified.";
}
}
}
container ssh-server-parameters {
description
"SSH-level server parameters to listen
for NETCONF over SSH connections.";
uses sshs:ssh-server-grouping;
}
container netconf-server-parameters {
description
"NETCONF-level server parameters to listen
for NETCONF over SSH connections.";
uses ncs:netconf-server-grouping {
refine "client-identity-mappings" {
if-feature "sshcmn:ssh-x509-certs";
description
"Adds in an 'if-feature' statement
ensuring the 'client-identity-mappings'
descendant is enabled only when SSH
supports X.509 certificates.";
}
augment "client-identity-mappings" {
description
"Adds a flag indicating if a cert-to-name
is required.";
leaf mapping-required {
type boolean;
description
"Indicates that the cert-to-name mapping
is required (i.e., the SSH-level username
is ignored).";
}
}
}
}
}
}
case tls {
if-feature "tls-listen";
container tls {
description
"TCP, TLS, and NETCONF configuration to listen
for NETCONF over TLS connections.";
container tcp-server-parameters {
description
"TCP-level server parameters to listen
for NETCONF over TLS connections.";
uses tcps:tcp-server-grouping {
refine "local-port" {
default "6513";
description
"The NETCONF server will listen on the
IANA-assigned well-known port value
for 'netconf-tls' (6513) if no value
is specified.";
}
}
}
container tls-server-parameters {
description
"TLS-level server parameters to listen
for NETCONF over TLS connections.";
uses tlss:tls-server-grouping {
refine "client-authentication" {
must 'ca-certs or ee-certs';
description
"NETCONF/TLS servers MUST validate client
certificates. This configures certificates
at the socket-level (i.e. bags). More
discriminating client-certificate checks
SHOULD be implemented by the application.";
reference
"RFC 7589:
Using the NETCONF Protocol over Transport Layer
Security (TLS) with Mutual X.509 Authentication";
}
}
}
container netconf-server-parameters {
description
"NETCONF-level server parameters to listen
for NETCONF over TLS connections.";
uses ncs:netconf-server-grouping {
refine "client-identity-mappings/cert-to-name" {
min-elements 1;
description
"The TLS transport requires a mapping.";
}
}
}
}
}
}
}
grouping netconf-server-callhome-stack-grouping {
description
"A reusable grouping for configuring a NETCONF server
'call-home' protocol stack, for a single outbound
connection.";
choice transport {
mandatory true;
description
"Selects between available transports.";
case ssh {
if-feature "ssh-call-home";
container ssh {
description
"TCP, SSH, and NETCONF configuration to initiate
a NETCONF over SSH Call Home connection.";
container tcp-client-parameters {
description
"TCP-level client parameters to initiate a
NETCONF over SSH Call Home connection.";
uses tcpc:tcp-client-grouping {
refine "remote-port" {
default "4334";
description
"The NETCONF server will attempt to connect
to the IANA-assigned well-known port for
'netconf-ch-ssh' (4334) if no value is
specified.";
}
}
}
container ssh-server-parameters {
description
"SSH-level server parameters to initiate a
NETCONF over SSH Call Home connection.";
uses sshs:ssh-server-grouping;
}
container netconf-server-parameters {
description
"NETCONF-level server parameters to initiate a
NETCONF over SSH Call Home connection.";
uses ncs:netconf-server-grouping {
refine "client-identity-mappings" {
if-feature "sshcmn:ssh-x509-certs";
description
"Adds in an 'if-feature' statement
ensuring the 'client-identity-mappings'
descendant is enabled only when SSH
supports X.509 certificates.";
}
augment "client-identity-mappings" {
description
"Adds a flag indicating if a cert-to-name
is required.";
leaf mapping-required {
type boolean;
description
"Indicates that the cert-to-name mapping
is required (i.e., the SSH-level username
is ignored).";
}
}
}
}
}
}
case tls {
if-feature "tls-call-home";
container tls {
description
"TCP, TLS, and NETCONF configuration to initiate
a NETCONF over TLS Call Home connection.";
container tcp-client-parameters {
description
"TCP-level client parameters to initiate a
NETCONF over TLS Call Home connection.";
uses tcpc:tcp-client-grouping {
refine "remote-port" {
default "4335";
description
"The NETCONF server will attempt to connect
to the IANA-assigned well-known port for
'netconf-ch-tls' (4335) if no value is
specified.";
}
}
}
container tls-server-parameters {
description
"TLS-level server parameters to initiate a
NETCONF over TLS Call Home connection.";
uses tlss:tls-server-grouping {
refine "client-authentication" {
must 'ca-certs or ee-certs';
description
"NETCONF/TLS servers MUST validate client
certificates. This configures certificates
at the socket-level (i.e. bags). More
discriminating client-certificate checks
SHOULD be implemented by the application.";
reference
"RFC 7589:
Using the NETCONF Protocol over Transport Layer
Security (TLS) with Mutual X.509 Authentication";
}
}
}
container netconf-server-parameters {
description
"NETCONF-level server parameters to initiate a
NETCONF over TLS Call Home connection.";
uses ncs:netconf-server-grouping {
refine "client-identity-mappings/cert-to-name" {
min-elements 1;
description
"The TLS transport requires a mapping.";
}
}
}
}
}
}
}
grouping netconf-server-app-grouping {
description
"A reusable grouping for configuring a NETCONF server
application that supports both 'listen' and 'call-home'
protocol stacks for a multiplicity of connections.";
container listen {
if-feature "ssh-listen or tls-listen";
presence
"Indicates that server-listening ports have been configured.
This statement is present so the mandatory descendant
nodes do not imply that this node must be configured.";
description
"Configures listen behavior";
leaf idle-timeout {
type uint16;
units "seconds";
default "180"; // three minutes
description
"Specifies the maximum number of seconds that a NETCONF
session may remain idle. A NETCONF session will be
dropped if it is idle for an interval longer than this
number of seconds. If set to zero, then the server
will never drop a session because it is idle.";
}
container endpoints {
description
"Container for a list of endpoints.";
list endpoint {
key "name";
min-elements 1;
description
"List of endpoints to listen for NETCONF connections.";
leaf name {
type string;
description
"An arbitrary name for the NETCONF listen endpoint.";
}
uses netconf-server-listen-stack-grouping;
}
}
}
container call-home {
if-feature "ssh-call-home or tls-call-home";
presence
"Indicates that server-initiated call home connections have
been configured. This statement is present so the mandatory
descendant nodes do not imply that this node must be
configured.";
description
"Configures the NETCONF server to initiate the underlying
transport connection to NETCONF clients.";
list netconf-client {
key "name";
min-elements 1;
description
"List of NETCONF clients the NETCONF server is to
maintain simultaneous call-home connections with.";
leaf name {
type string;
description
"An arbitrary name for the remote NETCONF client.";
}
container endpoints {
description
"Container for the list of endpoints.";
list endpoint {
key "name";
min-elements 1;
ordered-by user;
description
"A non-empty user-ordered list of endpoints for this
NETCONF server to try to connect to in sequence.
Defining more than one enables high-availability.";
leaf name {
type string;
description
"An arbitrary name for this endpoint.";
}
uses netconf-server-callhome-stack-grouping;
}
}
container connection-type {
description
"Indicates the NETCONF server's preference for how the
NETCONF connection is maintained.";
choice connection-type {
mandatory true;
description
"Selects between available connection types.";
case persistent-connection {
container persistent {
presence
"Indicates that a persistent connection is to be
maintained.";
description
"Maintain a persistent connection to the NETCONF
client. If the connection goes down, immediately
start trying to reconnect to the NETCONF client,
using the reconnection strategy.
This connection type minimizes any NETCONF client
to NETCONF server data-transfer delay, albeit at
the expense of holding resources longer.";
}
}
case periodic-connection {
container periodic {
presence "Indicates that a periodic connection is
to be maintained.";
description
"Periodically connect to the NETCONF client.
This connection type decreases resource
utilization, albeit with increased delay in
NETCONF client to NETCONF server interactions.
The NETCONF client SHOULD gracefully close the
connection using <close-session> upon completing
planned activities. If the NETCONF session is
not closed gracefully, the NETCONF server MUST
immediately attempt to reestablish the connection.
Connections are established at the same start
time regardless how long the previous connection
stayed open.
In the case that the previous connection is still
active (i.e., the NETCONF client has not closed
it yet), establishing a new connection is NOT
RECOMMENDED.";
leaf period {
type uint16;
units "minutes";
default "60";
description
"Duration of time between periodic connections.";
}
leaf anchor-time {
type yang:date-and-time {
// constrained to minute-level granularity
pattern '[0-9]{4}-(1[0-2]|0[1-9])-(0[1-9]|[1-2]'
+ '[0-9]|3[0-1])T(0[0-9]|1[0-9]|2[0-3]):['
+ '0-5][0-9]:00(Z|[\+\-]((1[0-3]|0[0-9]):'
+ '([0-5][0-9])|14:00))?';
}
description
"Designates a timestamp before or after which a
series of periodic connections are determined.
The periodic connections occur at a whole
multiple interval from the anchor time.
If an 'anchor-time' is not provided, then the
server may implicitly set it to the time when
this configuraton is applied (e.g., on boot).
For example, for an anchor time is 15 minutes
past midnight and a period interval of 24 hours,
then a periodic connection will occur 15 minutes
past midnight everyday.";
}
leaf idle-timeout {
type uint16;
units "seconds";
default "180"; // three minutes
description
"Specifies the maximum number of seconds that
a NETCONF session may remain idle. A NETCONF
session will be dropped if it is idle for an
interval longer than this number of seconds.
If set to zero, then the server will never
drop a session because it is idle.";
}
}
} // case periodic-connection
} // choice connection-type
} // container connection-type
container reconnect-strategy {
description
"The reconnection strategy directs how a NETCONF server
reconnects to a NETCONF client, after discovering its
connection to the client has dropped, even if due to a
reboot. The NETCONF server starts with the specified
endpoint and tries to connect to it max-attempts times
before trying the next endpoint in the list (round
robin).";
leaf start-with {
type enumeration {
enum first-listed {
description
"Indicates that reconnections should start with
the first endpoint listed.";
}
enum last-connected {
description
"Indicates that reconnections should start with
the endpoint last connected to. If no previous
connection has ever been established, then the
first endpoint configured is used. NETCONF
servers SHOULD be able to remember the last
endpoint connected to across reboots.";
}
enum random-selection {
description
"Indicates that reconnections should start with
a random endpoint.";
}
}
default "first-listed";
description
"Specifies which of the NETCONF client's endpoints
the NETCONF server should start with when trying
to connect to the NETCONF client.";
}
leaf max-wait {
type uint16 {
range "1..max";
}
units "seconds";
default "5";
description
"Specifies the amount of time in seconds after which,
if the connection is not established, an endpoint
connection attempt is considered unsuccessful.";
}
leaf max-attempts {
type uint8 {
range "1..max";
}
default "3";
description
"Specifies the number times the NETCONF server tries
to connect to a specific endpoint before moving on
to the next endpoint in the list (round robin).";
}
} // container reconnect-strategy
} // list netconf-client
} // container call-home
} // grouping netconf-server-app-grouping
// Protocol accessible node for servers that implement this module.
container netconf-server {
if-feature central-netconf-server-supported;
uses netconf-server-app-grouping;
description
"Top-level container for NETCONF server configuration.";
}
}

View file

@ -0,0 +1,261 @@
module ietf-ssh-common {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-ssh-common";
prefix sshcmn;
import iana-ssh-encryption-algs {
prefix sshea;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import iana-ssh-key-exchange-algs {
prefix sshkea;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import iana-ssh-mac-algs {
prefix sshma;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import iana-ssh-public-key-algs {
prefix sshpka;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
import ietf-keystore {
prefix ks;
reference
"RFC CCCC: A YANG Data Model for a Keystore";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
WG List: NETCONF WG list <mailto:netconf@ietf.org>
Author: Kent Watsen <mailto:kent+ietf@watsen.net>
Author: Gary Wu <mailto:garywu@cisco.com>";
description
"This module defines a common features and groupings for
Secure Shell (SSH).
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC EEEE
(https://www.rfc-editor.org/info/rfcEEEE); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
// Features
feature ssh-x509-certs {
description
"X.509v3 certificates are supported for SSH.";
reference
"RFC 6187: X.509v3 Certificates for Secure Shell
Authentication";
}
feature transport-params {
description
"SSH transport layer parameters are configurable.";
}
feature public-key-generation {
description
"Indicates that the server implements the
'generate-public-key' RPC.";
}
// Groupings
grouping transport-params-grouping {
description
"A reusable grouping for SSH transport parameters.";
reference
"RFC 4253: The Secure Shell (SSH) Transport Layer Protocol";
container host-key {
description
"Parameters regarding host key.";
leaf-list host-key-alg {
type identityref {
base sshpka:public-key-alg-base;
}
ordered-by user;
description
"Acceptable host key algorithms in order of decreasing
preference.
If this leaf-list is not configured (has zero elements)
the acceptable host key algorithms are implementation-
defined.";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
}
container key-exchange {
description
"Parameters regarding key exchange.";
leaf-list key-exchange-alg {
type identityref {
base sshkea:key-exchange-alg-base;
}
ordered-by user;
description
"Acceptable key exchange algorithms in order of decreasing
preference.
If this leaf-list is not configured (has zero elements)
the acceptable key exchange algorithms are implementation
defined.";
}
}
container encryption {
description
"Parameters regarding encryption.";
leaf-list encryption-alg {
type identityref {
base sshea:encryption-alg-base;
}
ordered-by user;
description
"Acceptable encryption algorithms in order of decreasing
preference.
If this leaf-list is not configured (has zero elements)
the acceptable encryption algorithms are implementation
defined.";
}
}
container mac {
description
"Parameters regarding message authentication code (MAC).";
leaf-list mac-alg {
type identityref {
base sshma:mac-alg-base;
}
ordered-by user;
description
"Acceptable MAC algorithms in order of decreasing
preference.
If this leaf-list is not configured (has zero elements)
the acceptable MAC algorithms are implementation-
defined.";
}
}
}
// Protocol-accessible Nodes
rpc generate-public-key {
if-feature "public-key-generation";
description
"Requests the device to generate an public key using
the specified key algorithm.";
input {
leaf algorithm {
type sshpka:public-key-algorithm-ref;
mandatory true;
description
"The algorithm to be used when generating the key.";
}
leaf num-bits {
type uint16;
description
"Specifies the number of bits in the key to create.
For RSA keys, the minimum size is 1024 bits and
the default is 3072 bits. Generally, 3072 bits is
considered sufficient. DSA keys must be exactly 1024
bits as specified by FIPS 186-6. For ECDSA keys, the
'num-bits' value determines the key length by selecting
from one of three elliptic curve sizes: 256, 384 or
521 bits. Attempting to use bit lengths other than
these three values for ECDSA keys will fail. ECDSA-SK,
Ed25519 and Ed25519-SK keys have a fixed length and
thus the 'num-bits' value is not specified.";
reference
"FIPS 186-6: Digital Signature Standard (DSS)";
}
container private-key-encoding {
description
"Indicates how the private key is to be encoded.";
choice private-key-encoding {
mandatory true;
description
"A choice amongst optional private key handling.";
case cleartext {
if-feature "ct:cleartext-private-keys";
leaf cleartext {
type empty;
description
"Indicates that the private key is to be returned
as a cleartext value.";
}
}
case encrypted {
if-feature "ct:encrypted-private-keys";
container encrypted {
description
"Indicates that the private key is to be encrypted
using the specified symmetric or asymmetric key.";
uses ks:encrypted-by-grouping;
}
}
case hidden {
if-feature "ct:hidden-private-keys";
leaf hidden {
type empty;
description
"Indicates that the private key is to be hidden.
Unlike the 'cleartext' and 'encrypt' options, the
key returned is a placeholder for an internally
stored key. See the 'Support for Built-in Keys'
section in RFC CCCC for information about hidden
keys.";
}
}
}
}
}
output {
uses ct:asymmetric-key-pair-grouping;
}
} // end generate-public-key
}

View file

@ -0,0 +1,425 @@
module ietf-ssh-server {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-ssh-server";
prefix sshs;
import iana-crypt-hash {
prefix ianach;
reference
"RFC 7317: A YANG Data Model for System Management";
}
import ietf-netconf-acm {
prefix nacm;
reference
"RFC 8341: Network Configuration Access Control Model";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
import ietf-truststore {
prefix ts;
reference
"RFC BBBB: A YANG Data Model for a Truststore";
}
import ietf-keystore {
prefix ks;
reference
"RFC CCCC: A YANG Data Model for a Keystore";
}
import ietf-ssh-common {
prefix sshcmn;
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
WG List: NETCONF WG list <mailto:netconf@ietf.org>
Author: Kent Watsen <mailto:kent+ietf@watsen.net>";
description
"This module defines a reusable grouping for SSH servers that
can be used as a basis for specific SSH server instances.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC EEEE
(https://www.rfc-editor.org/info/rfcEEEE); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC EEEE: YANG Groupings for SSH Clients and SSH Servers";
}
// Features
feature ssh-server-keepalives {
description
"Per socket SSH keepalive parameters are configurable for
SSH servers on the server implementing this feature.";
}
feature local-users-supported {
description
"Indicates that the configuration for users can be
configured herein, as opposed to in an application
specific location.";
}
feature local-user-auth-publickey {
if-feature "local-users-supported";
description
"Indicates that the 'publickey' authentication type,
per RFC 4252, is supported for locally-defined users.
The 'publickey' authentication type is required by
RFC 4252, but common implementations allow it to
be disabled.";
reference
"RFC 4252:
The Secure Shell (SSH) Authentication Protocol";
}
feature local-user-auth-password {
if-feature "local-users-supported";
description
"Indicates that the 'password' authentication type,
per RFC 4252, is supported for locally-defined users.";
reference
"RFC 4252:
The Secure Shell (SSH) Authentication Protocol";
}
feature local-user-auth-hostbased {
if-feature "local-users-supported";
description
"Indicates that the 'hostbased' authentication type,
per RFC 4252, is supported for locally-defined users.";
reference
"RFC 4252:
The Secure Shell (SSH) Authentication Protocol";
}
feature local-user-auth-none {
if-feature "local-users-supported";
description
"Indicates that the 'none' authentication type, per
RFC 4252, is supported. It is NOT RECOMMENDED to
enable this feature.";
reference
"RFC 4252:
The Secure Shell (SSH) Authentication Protocol";
}
// Groupings
grouping ssh-server-grouping {
description
"A reusable grouping for configuring a SSH server without
any consideration for how underlying TCP sessions are
established.
Note that this grouping uses fairly typical descendant
node names such that a nesting of 'uses' statements will
have name conflicts. It is intended that the consuming
data model will resolve the issue (e.g., by wrapping
the 'uses' statement in a container called
'ssh-server-parameters'). This model purposely does
not do this itself so as to provide maximum flexibility
to consuming models.";
container server-identity {
nacm:default-deny-write;
description
"The list of host keys the SSH server will present when
establishing a SSH connection.";
list host-key {
key "name";
min-elements 1;
ordered-by user;
description
"An ordered list of host keys (see RFC 4251) the SSH
server will use to construct its ordered list of
algorithms, when sending its SSH_MSG_KEXINIT message,
as defined in Section 7.1 of RFC 4253.";
reference
"RFC 4251: The Secure Shell (SSH) Protocol Architecture
RFC 4253: The Secure Shell (SSH) Transport Layer
Protocol";
leaf name {
type string;
description
"An arbitrary name for this host key";
}
choice host-key-type {
mandatory true;
description
"The type of host key being specified";
container public-key {
description
"A locally-defined or referenced asymmetric key pair
to be used for the SSH server's host key.";
reference
"RFC CCCC: A YANG Data Model for a Keystore";
uses ks:inline-or-keystore-asymmetric-key-grouping {
refine "inline-or-keystore/inline/inline-definition" {
must 'not(public-key-format) or derived-from-or-self'
+ '(public-key-format, "ct:ssh-public-key-format")';
}
refine "inline-or-keystore/central-keystore/"
+ "central-keystore-reference" {
must 'not(deref(.)/../ks:public-key-format) or '
+ 'derived-from-or-self(deref(.)/../ks:public-'
+ 'key-format, "ct:ssh-public-key-format")';
}
}
}
container certificate {
if-feature "sshcmn:ssh-x509-certs";
description
"A locally-defined or referenced end-entity
certificate to be used for the SSH server's
host key.";
reference
"RFC CCCC: A YANG Data Model for a Keystore";
uses
ks:inline-or-keystore-end-entity-cert-with-key-grouping{
refine "inline-or-keystore/inline/inline-definition" {
must 'not(public-key-format) or derived-from-or-self'
+ '(public-key-format, "ct:subject-public-key-'
+ 'info-format")';
}
refine "inline-or-keystore/central-keystore/"
+ "central-keystore-reference/asymmetric-key" {
must 'not(deref(.)/../ks:public-key-format) or '
+ 'derived-from-or-self(deref(.)/../ks:public-key'
+ '-format, "ct:subject-public-key-info-format")';
}
}
}
}
}
} // container server-identity
container client-authentication {
nacm:default-deny-write;
description
"Specifies how the SSH server can be configured to
authenticate SSH clients. See RFC 4252 for a general
discussion about SSH authentication.";
reference
"RFC 4252: The Secure Shell (SSH) Transport Layer";
container users {
if-feature "local-users-supported";
description
"A list of locally configured users.";
list user {
key "name";
description
"A locally configured user.
The server SHOULD derive the list of authentication
'method names' returned to the SSH client from the
descendant nodes configured herein, per Sections
5.1 and 5.2 in RFC 4252.
The authentication methods are unordered. Clients
must authenticate to all configured methods.
Whenever a choice amongst methods arises,
implementations SHOULD use a default ordering
that prioritizes automation over human-interaction.";
leaf name {
type string;
description
"The 'user name' for the SSH client, as defined in
the SSH_MSG_USERAUTH_REQUEST message in RFC 4253.";
reference
"RFC 4253: The Secure Shell (SSH) Transport Layer
Protocol";
}
container public-keys {
if-feature "local-user-auth-publickey";
presence
"Indicates that public keys have been configured.
This statement is present so the mandatory descendant
nodes do not imply that this node must be
configured.";
description
"A set of SSH public keys may be used by the SSH
server to authenticate this user. A user is
authenticated if its public key is an exact
match to a configured public key.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-public-keys-grouping {
refine "inline-or-truststore/inline/inline-definition/"
+ "public-key" {
must 'derived-from-or-self(public-key-format,'
+ ' "ct:ssh-public-key-format")';
}
refine "inline-or-truststore/central-truststore/"
+ "central-truststore-reference" {
must 'not(deref(.)/../ts:public-key/ts:public-key-'
+ 'format[not(derived-from-or-self(., "ct:ssh-'
+ 'public-key-format"))])';
}
}
}
leaf password {
if-feature "local-user-auth-password";
type ianach:crypt-hash;
description
"The password for this user.";
}
container hostbased {
if-feature "local-user-auth-hostbased";
presence
"Indicates that hostbased [RFC4252] keys have been
configured. This statement is present so the
mandatory descendant nodes do not imply that this
node must be configured.";
description
"A set of SSH host keys used by the SSH server to
authenticate this user's host. A user's host is
authenticated if its host key is an exact match
to a configured host key.";
reference
"RFC 4252: The Secure Shell (SSH) Transport Layer
RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-public-keys-grouping {
refine "inline-or-truststore/inline/inline-definition/"
+ "public-key" {
must 'derived-from-or-self(public-key-format,'
+ ' "ct:ssh-public-key-format")';
}
refine "inline-or-truststore/central-truststore/"
+ "central-truststore-reference" {
must 'not(deref(.)/../ts:public-key/ts:public-key-'
+ 'format[not(derived-from-or-self(., "ct:ssh-'
+ 'public-key-format"))])';
}
}
}
leaf none {
if-feature "local-user-auth-none";
type empty;
description
"Indicates that the 'none' method is configured
for this user.";
reference
"RFC 4252: The Secure Shell (SSH) Authentication
Protocol.";
}
}
} // users
container ca-certs {
if-feature "sshcmn:ssh-x509-certs";
presence
"Indicates that CA certificates have been configured.
This statement is present so the mandatory descendant
nodes do not imply this node must be configured.";
description
"A set of certificate authority (CA) certificates used by
the SSH server to authenticate SSH client certificates.
A client certificate is authenticated if it has a valid
chain of trust to a configured CA certificate.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-certs-grouping;
}
container ee-certs {
if-feature "sshcmn:ssh-x509-certs";
presence
"Indicates that EE certificates have been configured.
This statement is present so the mandatory descendant
nodes do not imply this node must be configured.";
description
"A set of client certificates (i.e., end entity
certificates) used by the SSH server to authenticate
the certificates presented by SSH clients. A client
certificate is authenticated if it is an exact match
to a configured end-entity certificate.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-certs-grouping;
}
} // container client-authentication
container transport-params {
nacm:default-deny-write;
if-feature "sshcmn:transport-params";
description
"Configurable parameters of the SSH transport layer.";
uses sshcmn:transport-params-grouping;
} // container transport-params
container keepalives {
nacm:default-deny-write;
if-feature "ssh-server-keepalives";
presence
"Indicates that the SSH server proactively tests the
aliveness of the remote SSH client.";
description
"Configures the keep-alive policy, to proactively test
the aliveness of the SSH client. An unresponsive SSH
client is dropped after approximately max-wait *
max-attempts seconds. Per Section 4 of RFC 4254,
the SSH server SHOULD send an SSH_MSG_GLOBAL_REQUEST
message with a purposely nonexistent 'request name'
value (e.g., keepalive@ietf.org) and the 'want reply'
value set to '1'.";
reference
"RFC 4254: The Secure Shell (SSH) Connection Protocol";
leaf max-wait {
type uint16 {
range "1..max";
}
units "seconds";
default "30";
description
"Sets the amount of time in seconds after which
if no data has been received from the SSH client,
a SSH-level message will be sent to test the
aliveness of the SSH client.";
}
leaf max-attempts {
type uint8;
default "3";
description
"Sets the maximum number of sequential keep-alive
messages that can fail to obtain a response from
the SSH client before assuming the SSH client is
no longer alive.";
}
}
} // grouping ssh-server-grouping
}

View file

@ -0,0 +1,326 @@
module ietf-tcp-client {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-tcp-client";
prefix tcpc;
import ietf-inet-types {
prefix inet;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
import ietf-tcp-common {
prefix tcpcmn;
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
organization
"IETF NETCONF (Network Configuration) Working Group and the
IETF TCP Maintenance and Minor Extensions (TCPM) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
https://datatracker.ietf.org/wg/tcpm
WG List: NETCONF WG list <mailto:netconf@ietf.org>
TCPM WG list <mailto:tcpm@ietf.org>
Authors: Kent Watsen <mailto:kent+ietf@watsen.net>
Michael Scharf
<mailto:michael.scharf@hs-esslingen.de>";
description
"This module defines reusable groupings for TCP clients that
can be used as a basis for specific TCP client instances.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC DDDD
(https://www.rfc-editor.org/info/rfcDDDD); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
// Features
feature local-binding-supported {
description
"Indicates that the server supports configuring local
bindings (i.e., the local address and local port) for
TCP clients.";
}
feature tcp-client-keepalives {
description
"Per socket TCP keepalive parameters are configurable for
TCP clients on the server implementing this feature.";
reference
"RFC 9293: Transmission Control Protocol (TCP)";
}
feature proxy-connect {
description
"Proxy connection configuration is configurable for
TCP clients on the server implementing this feature.
Currently supports SOCKS 4, SOCKS 4a, and SOCKS 5.";
reference
"SOCKS Proceedings:
1992 Usenix Security Symposium.
OpenSSH message:
SOCKS 4A: A Simple Extension to SOCKS 4 Protocol
https://www.openssh.com/txt/socks4a.protocol
RFC 1928:
SOCKS Protocol Version 5";
}
feature socks5-gss-api {
description
"Indicates that the server, when acting as a TCP-client,
supports authenticating to a SOCKS Version 5 proxy server
using GSSAPI credentials.";
reference
"RFC 1928: SOCKS Protocol Version 5";
}
feature socks5-username-password {
description
"Indicates that the server, when acting as a TCP-client,
supports authenticating to a SOCKS Version 5 proxy server
using 'username' and 'password' credentials.";
reference
"RFC 1928: SOCKS Protocol Version 5";
}
// Groupings
grouping tcp-client-grouping {
description
"A reusable grouping for configuring a TCP client.
Note that this grouping uses fairly typical descendant
node names such that a stack of 'uses' statements will
have name conflicts. It is intended that the consuming
data model will resolve the issue (e.g., by wrapping
the 'uses' statement in a container called
'tcp-client-parameters'). This model purposely does
not do this itself so as to provide maximum flexibility
to consuming models.";
leaf remote-address {
type inet:host;
mandatory true;
description
"The IP address or hostname of the remote peer to
establish a connection with. If a domain name is
configured, then the DNS resolution should happen on
each connection attempt. If the DNS resolution
results in multiple IP addresses, the IP addresses
are tried according to local preference order until
a connection has been established or until all IP
addresses have failed.";
}
leaf remote-port {
type inet:port-number;
default "0";
description
"The IP port number for the remote peer to establish a
connection with. An invalid default value is used
so that importing modules may 'refine' it with the
appropriate default port number value.";
}
leaf local-address {
if-feature "local-binding-supported";
type inet:ip-address;
description
"The local IP address/interface to bind to for when
connecting to the remote peer. INADDR_ANY ('0.0.0.0') or
INADDR6_ANY ('0:0:0:0:0:0:0:0' a.k.a. '::') MAY be used to
explicitly indicate the implicit default, that the server
can bind to any IPv4 or IPv6 addresses, respectively.";
}
leaf local-port {
if-feature "local-binding-supported";
type inet:port-number;
default "0";
description
"The local IP port number to bind to for when connecting
to the remote peer. The port number '0', which is the
default value, indicates that any available local port
number may be used.";
}
container proxy-server {
if-feature "proxy-connect";
presence
"Indicates that a proxy connection has been configured.
Present so that the mandatory descendant nodes do not
imply that this node must be configured.";
choice proxy-type {
mandatory true;
description
"Selects a proxy connection protocol.";
case socks4 {
container socks4-parameters {
leaf remote-address {
type inet:ip-address;
mandatory true;
description
"The IP address of the proxy server.";
}
leaf remote-port {
type inet:port-number;
default "1080";
description
"The IP port number for the proxy server.";
}
description
"Parameters for connecting to a TCP-based proxy
server using the SOCKS4 protocol.";
reference
"SOCKS, Proceedings: 1992 Usenix Security Symposium.";
}
}
case socks4a {
container socks4a-parameters {
leaf remote-address {
type inet:host;
mandatory true;
description
"The IP address or hostname of the proxy server.";
}
leaf remote-port {
type inet:port-number;
default "1080";
description
"The IP port number for the proxy server.";
}
description
"Parameters for connecting to a TCP-based proxy
server using the SOCKS4a protocol.";
reference
"SOCKS Proceedings:
1992 Usenix Security Symposium.
OpenSSH message:
SOCKS 4A: A Simple Extension to SOCKS 4 Protocol
https://www.openssh.com/txt/socks4a.protocol";
}
}
case socks5 {
container socks5-parameters {
leaf remote-address {
type inet:host;
mandatory true;
description
"The IP address or hostname of the proxy server.";
}
leaf remote-port {
type inet:port-number;
default "1080";
description
"The IP port number for the proxy server.";
}
container authentication-parameters {
presence
"Indicates that an authentication mechanism
has been configured. Present so that the
mandatory descendant nodes do not imply that
this node must be configured.";
description
"A container for SOCKS Version 5 authentication
mechanisms.
A complete list of methods is defined at:
https://www.iana.org/assignments/socks-methods
/socks-methods.xhtml.";
reference
"RFC 1928: SOCKS Protocol Version 5";
choice auth-type {
mandatory true;
description
"A choice amongst supported SOCKS Version 5
authentication mechanisms.";
case gss-api {
if-feature "socks5-gss-api";
container gss-api {
description
"Contains GSS-API configuration. Defines
as an empty container to enable specific
GSS-API configuration to be augmented in
by future modules.";
reference
"RFC 1928: SOCKS Protocol Version 5
RFC 2743: Generic Security Service
Application Program Interface
Version 2, Update 1";
}
}
case username-password {
if-feature "socks5-username-password";
container username-password {
leaf username {
type string;
mandatory true;
description
"The 'username' value to use for client
identification.";
}
uses ct:password-grouping {
description
"The password to be used for client
authentication.";
}
description
"Contains Username/Password configuration.";
reference
"RFC 1929: Username/Password Authentication
for SOCKS V5";
}
}
}
}
description
"Parameters for connecting to a TCP-based proxy server
using the SOCKS5 protocol.";
reference
"RFC 1928: SOCKS Protocol Version 5";
}
}
}
description
"Proxy server settings.";
}
uses tcpcmn:tcp-common-grouping {
refine "keepalives" {
if-feature "tcp-client-keepalives";
description
"Add an if-feature statement so that implementations
can choose to support TCP client keepalives.";
}
}
}
}

View file

@ -0,0 +1,117 @@
module ietf-tcp-common {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-tcp-common";
prefix tcpcmn;
organization
"IETF NETCONF (Network Configuration) Working Group and the
IETF TCP Maintenance and Minor Extensions (TCPM) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
https://datatracker.ietf.org/wg/tcpm
WG List: NETCONF WG list <mailto:netconf@ietf.org>
TCPM WG list <mailto:tcpm@ietf.org>
Authors: Kent Watsen <mailto:kent+ietf@watsen.net>
Michael Scharf
<mailto:michael.scharf@hs-esslingen.de>";
description
"This module define a reusable 'grouping' that is common
to both TCP-clients and TCP-servers. This grouping statement
is used by both the 'ietf-tcp-client' and 'ietf-tcp-server'
modules.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC DDDD
(https://www.rfc-editor.org/info/rfcDDDD); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
// Features
feature keepalives-supported {
description
"Indicates that keepalives are supported.";
}
// Groupings
grouping tcp-common-grouping {
description
"A reusable grouping for configuring TCP parameters common
to TCP connections as well as the operating system as a
whole.";
container keepalives {
if-feature "keepalives-supported";
description
"Configures the keep-alive policy, to proactively test the
aliveness of the TCP peer. An unresponsive TCP peer is
dropped after approximately (idle-time + max-probes *
probe-interval) seconds. Further guidance can be found
in Section 2.1.5 of RFC DDDD.";
reference
"RFC 9293:
Transmission Control Protocol (TCP), Section 3.8.4..";
leaf idle-time {
type uint16 {
range "1..max";
}
units "seconds";
default 7200;
description
"Sets the amount of time after which if no data has been
received from the TCP peer, a TCP-level probe message
will be sent to test the aliveness of the TCP peer.
Two hours (7200 seconds) is safe value, per RFC 1122.";
reference
"RFC 1122:
Requirements for Internet Hosts -- Communication Layers";
}
leaf max-probes {
type uint16 {
range "1..max";
}
default 9;
description
"Sets the maximum number of sequential keep-alive probes
that can fail to obtain a response from the TCP peer
before assuming the TCP peer is no longer alive.";
}
leaf probe-interval {
type uint16 {
range "1..max";
}
units "seconds";
default 75;
description
"Sets the time interval between failed probes. The interval
SHOULD be significantly longer than one second in order to
avoid harm on a congested link.";
}
} // container keepalives
} // grouping tcp-common-grouping
}

View file

@ -0,0 +1,116 @@
module ietf-tcp-server {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-tcp-server";
prefix tcps;
import ietf-inet-types {
prefix inet;
reference
"RFC 6991: Common YANG Data Types";
}
import ietf-tcp-common {
prefix tcpcmn;
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
organization
"IETF NETCONF (Network Configuration) Working Group and the
IETF TCP Maintenance and Minor Extensions (TCPM) Working Group";
contact
"WG Web: https://datatracker.ietf.org/wg/netconf
https://datatracker.ietf.org/wg/tcpm
WG List: NETCONF WG list <mailto:netconf@ietf.org>
TCPM WG list <mailto:tcpm@ietf.org>
Authors: Kent Watsen <mailto:kent+ietf@watsen.net>
Michael Scharf
<mailto:michael.scharf@hs-esslingen.de>";
description
"This module defines reusable groupings for TCP servers that
can be used as a basis for specific TCP server instances.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC DDDD
(https://www.rfc-editor.org/info/rfcDDDD); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC DDDD: YANG Groupings for TCP Clients and TCP Servers";
}
// Features
feature tcp-server-keepalives {
description
"Per socket TCP keepalive parameters are configurable for
TCP servers on the server implementing this feature.";
reference
"RFC 9293: Transmission Control Protocol (TCP)";
}
// Groupings
grouping tcp-server-grouping {
description
"A reusable grouping for configuring a TCP server.
Note that this grouping uses fairly typical descendant
node names such that a stack of 'uses' statements will
have name conflicts. It is intended that the consuming
data model will resolve the issue (e.g., by wrapping
the 'uses' statement in a container called
'tcp-server-parameters'). This model purposely does
not do this itself so as to provide maximum flexibility
to consuming models.";
leaf local-address {
type inet:ip-address;
mandatory true;
description
"The local IP address to listen on for incoming
TCP client connections. INADDR_ANY (0.0.0.0) or
INADDR6_ANY (0:0:0:0:0:0:0:0 a.k.a. ::) MUST be
used when the server is to listen on all IPv4 or
IPv6 addresses, respectively.";
}
leaf local-port {
type inet:port-number;
default "0";
description
"The local port number to listen on for incoming TCP
client connections. An invalid default value (0)
is used (instead of 'mandatory true') so that an
application level data model may 'refine' it with
an application specific default port number value.";
}
uses tcpcmn:tcp-common-grouping {
refine "keepalives" {
if-feature "tcp-server-keepalives";
description
"Add an if-feature statement so that implementations
can choose to support TCP server keepalives.";
}
}
}
}

View file

@ -0,0 +1,316 @@
module ietf-tls-common {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-tls-common";
prefix tlscmn;
import iana-tls-cipher-suite-algs {
prefix tlscsa;
reference
"RFC FFFF: YANG Groupings for TLS Clients and SSH Servers";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
import ietf-keystore {
prefix ks;
reference
"RFC CCCC: A YANG Data Model for a Keystore";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG List: NETCONF WG list <mailto:netconf@ietf.org>
WG Web: https://datatracker.ietf.org/wg/netconf
Author: Kent Watsen <mailto:kent+ietf@watsen.net>
Author: Jeff Hartley <mailto:jeff.hartley@commscope.com>
Author: Gary Wu <mailto:garywu@cisco.com>";
description
"This module defines a common features and groupings for
Transport Layer Security (TLS).
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC FFFF
(https://www.rfc-editor.org/info/rfcFFFF); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC FFFF: YANG Groupings for TLS Clients and TLS Servers";
}
// Features
feature tls10 {
status "obsolete";
description
"TLS Protocol Version 1.0 is supported. TLS 1.0 is obsolete
and thus it is NOT RECOMMENDED to enable this feature.";
reference
"RFC 2246: The TLS Protocol Version 1.0";
}
feature tls11 {
status "obsolete";
description
"TLS Protocol Version 1.1 is supported. TLS 1.1 is obsolete
and thus it is NOT RECOMMENDED to enable this feature.";
reference
"RFC 4346: The Transport Layer Security (TLS) Protocol
Version 1.1";
}
feature tls12 {
status "deprecated";
description
"TLS Protocol Version 1.2 is supported. TLS 1.2 is obsolete
and thus it is NOT RECOMMENDED to enable this feature.";
reference
"RFC 5246: The Transport Layer Security (TLS) Protocol
Version 1.2";
}
feature tls13 {
description
"TLS Protocol Version 1.3 is supported.";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3";
}
feature hello-params {
description
"TLS hello message parameters are configurable.";
}
feature public-key-generation {
description
"Indicates that the server implements the
'generate-public-key' RPC.";
}
// Identities
identity tls-version-base {
description
"Base identity used to identify TLS protocol versions.";
}
identity tls10 {
if-feature "tls10";
base tls-version-base;
status "obsolete";
description
"TLS Protocol Version 1.0.";
reference
"RFC 2246: The TLS Protocol Version 1.0";
}
identity tls11 {
if-feature "tls11";
base tls-version-base;
status "obsolete";
description
"TLS Protocol Version 1.1.";
reference
"RFC 4346: The Transport Layer Security (TLS) Protocol
Version 1.1";
}
identity tls12 {
if-feature "tls12";
base tls-version-base;
status "deprecated";
description
"TLS Protocol Version 1.2.";
reference
"RFC 5246: The Transport Layer Security (TLS) Protocol
Version 1.2";
}
identity tls13 {
if-feature "tls13";
base tls-version-base;
description
"TLS Protocol Version 1.3.";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3";
}
// Typedefs
typedef epsk-supported-hash {
type enumeration {
enum sha-256 {
description
"The SHA-256 Hash.";
}
enum sha-384 {
description
"The SHA-384 Hash.";
}
}
description
"As per Section 4.2.11 of RFC 8446, the hash algorithm
supported by an instance of an External Pre-Shared
Key (EPSK).";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3";
}
// Groupings
grouping hello-params-grouping {
description
"A reusable grouping for TLS hello message parameters.";
reference
"RFC 5246: The Transport Layer Security (TLS) Protocol
Version 1.2
RFC 8446: The Transport Layer Security (TLS) Protocol
Version 1.3";
container tls-versions {
description
"Parameters regarding TLS versions.";
leaf-list tls-version {
type identityref {
base tls-version-base;
}
ordered-by user;
description
"Acceptable TLS protocol versions.
If this leaf-list is not configured (has zero elements)
the acceptable TLS protocol versions are implementation-
defined.";
}
}
container cipher-suites {
description
"Parameters regarding cipher suites.";
leaf-list cipher-suite {
type identityref {
base tlscsa:cipher-suite-alg-base;
}
ordered-by user;
description
"Acceptable cipher suites in order of descending
preference. The configured host key algorithms should
be compatible with the algorithm used by the configured
private key. Please see Section 5 of RFC FFFF for
valid combinations.
If this leaf-list is not configured (has zero elements)
the acceptable cipher suites are implementation-
defined.";
reference
"RFC FFFF: YANG Groupings for TLS Clients and TLS Servers";
}
}
} // hello-params-grouping
rpc generate-public-key {
if-feature "public-key-generation";
description
"Requests the device to generate an public key using
the specified key algorithm.";
input {
leaf algorithm {
type tlscsa:cipher-suite-algorithm-ref;
mandatory true;
description
"The cipher suite algorithm that the generated key is
to work with. Implementations derive the public key
algorithm from the cipher suite algorithm. Example:
cipher suite 'tls-rsa-with-aes-256-cbc-sha256' maps
to the RSA public key.";
}
leaf num-bits {
type uint16;
description
"Specifies the number of bits in the key to create.
For RSA keys, the minimum size is 1024 bits and
the default is 3072 bits. Generally, 3072 bits is
considered sufficient. DSA keys must be exactly 1024
bits as specified by FIPS 186-2. For elliptical
keys, the 'num-bits' value determines the key length
of the curve (e.g., 256, 384 or 521), where valid
values supported by the server are conveyed via an
unspecified mechanism. For some public algorithms,
the keys have a fixed length and thus the 'num-bits'
value is not specified.";
}
container private-key-encoding {
description
"Indicates how the private key is to be encoded.";
choice private-key-encoding {
mandatory true;
description
"A choice amongst optional private key handling.";
case cleartext {
if-feature "ct:cleartext-private-keys";
leaf cleartext {
type empty;
description
"Indicates that the private key is to be returned
as a cleartext value.";
}
}
case encrypted {
if-feature "ct:encrypted-private-keys";
container encrypted {
description
"Indicates that the key is to be encrypted using
the specified symmetric or asymmetric key.";
uses ks:encrypted-by-grouping;
}
}
case hidden {
if-feature "ct:hidden-private-keys";
leaf hidden {
type empty;
description
"Indicates that the private key is to be hidden.
Unlike the 'cleartext' and 'encrypt' options, the
key returned is a placeholder for an internally
stored key. See the 'Support for Built-in Keys'
section in RFC CCCC for information about hidden
keys.";
}
}
}
}
}
output {
uses ct:asymmetric-key-pair-grouping;
}
} // end generate-public-key
}

View file

@ -0,0 +1,527 @@
module ietf-tls-server {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-tls-server";
prefix tlss;
import ietf-netconf-acm {
prefix nacm;
reference
"RFC 8341: Network Configuration Access Control Model";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
import ietf-truststore {
prefix ts;
reference
"RFC BBBB: A YANG Data Model for a Truststore";
}
import ietf-keystore {
prefix ks;
reference
"RFC CCCC: A YANG Data Model for a Keystore";
}
import ietf-tls-common {
prefix tlscmn;
reference
"RFC FFFF: YANG Groupings for TLS Clients and TLS Servers";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG List: NETCONF WG list <mailto:netconf@ietf.org>
WG Web: https://datatracker.ietf.org/wg/netconf
Author: Kent Watsen <mailto:kent+ietf@watsen.net>
Author: Jeff Hartley <mailto:jeff.hartley@commscope.com>";
description
"This module defines reusable groupings for TLS servers that
can be used as a basis for specific TLS server instances.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC FFFF
(https://www.rfc-editor.org/info/rfcFFFF); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC FFFF: YANG Groupings for TLS Clients and TLS Servers";
}
// Features
feature tls-server-keepalives {
description
"Per socket TLS keepalive parameters are configurable for
TLS servers on the server implementing this feature.";
}
feature server-ident-x509-cert {
description
"Indicates that the server supports identifying itself
using X.509 certificates.";
reference
"RFC 5280:
Internet X.509 Public Key Infrastructure Certificate
and Certificate Revocation List (CRL) Profile";
}
feature server-ident-raw-public-key {
description
"Indicates that the server supports identifying itself
using raw public keys.";
reference
"RFC 7250:
Using Raw Public Keys in Transport Layer Security (TLS)
and Datagram Transport Layer Security (DTLS)";
}
feature server-ident-tls12-psk {
if-feature "tlscmn:tls12";
description
"Indicates that the server supports identifying itself
using TLS-1.2 PSKs (pre-shared or pairwise-symmetric keys).";
reference
"RFC 4279:
Pre-Shared Key Ciphersuites for Transport Layer Security
(TLS)";
}
feature server-ident-tls13-epsk {
if-feature "tlscmn:tls13";
description
"Indicates that the server supports identifying itself
using TLS-1.3 External PSKs (pre-shared keys).";
reference
"RFC 8446:
The Transport Layer Security (TLS) Protocol Version 1.3";
}
feature client-auth-supported {
description
"Indicates that the configuration for how to authenticate
clients can be configured herein. TLS-level client
authentication may not be needed when client authentication
is expected to occur only at another protocol layer.";
}
feature client-auth-x509-cert {
description
"Indicates that the server supports authenticating clients
using X.509 certificates.";
reference
"RFC 5280:
Internet X.509 Public Key Infrastructure Certificate
and Certificate Revocation List (CRL) Profile";
}
feature client-auth-raw-public-key {
description
"Indicates that the server supports authenticating clients
using raw public keys.";
reference
"RFC 7250:
Using Raw Public Keys in Transport Layer Security (TLS)
and Datagram Transport Layer Security (DTLS)";
}
feature client-auth-tls12-psk {
description
"Indicates that the server supports authenticating clients
using PSKs (pre-shared or pairwise-symmetric keys).";
reference
"RFC 4279:
Pre-Shared Key Ciphersuites for Transport Layer Security
(TLS)";
}
feature client-auth-tls13-epsk {
description
"Indicates that the server supports authenticating clients
using TLS-1.3 External PSKs (pre-shared keys).";
reference
"RFC 8446:
The Transport Layer Security (TLS) Protocol Version 1.3";
}
// Groupings
grouping tls-server-grouping {
description
"A reusable grouping for configuring a TLS server without
any consideration for how underlying TCP sessions are
established.
Note that this grouping uses fairly typical descendant
node names such that a stack of 'uses' statements will
have name conflicts. It is intended that the consuming
data model will resolve the issue (e.g., by wrapping
the 'uses' statement in a container called
'tls-server-parameters'). This model purposely does
not do this itself so as to provide maximum flexibility
to consuming models.";
container server-identity {
nacm:default-deny-write;
description
"A locally-defined or referenced end-entity certificate,
including any configured intermediate certificates, the
TLS server will present when establishing a TLS connection
in its Certificate message, as defined in Section 7.4.2
in RFC 5246 and Section 4.4.2 in RFC 8446.";
reference
"RFC 5246: The Transport Layer Security (TLS) Protocol
Version 1.2
RFC 8446: The Transport Layer Security (TLS) Protocol
Version 1.3
RFC CCCC: A YANG Data Model for a Keystore";
choice auth-type {
mandatory true;
description
"A choice amongst authentication types, of which one must
be enabled (via its associated 'feature') and selected.";
case certificate {
if-feature "server-ident-x509-cert";
container certificate {
description
"Specifies the server identity using a certificate.";
uses
"ks:inline-or-keystore-end-entity-cert-with-key-"
+ "grouping" {
refine "inline-or-keystore/inline/inline-definition" {
must 'not(public-key-format) or derived-from-or-self'
+ '(public-key-format,' + ' "ct:subject-public-'
+ 'key-info-format")';
}
refine "inline-or-keystore/central-keystore/"
+ "central-keystore-reference/asymmetric-key" {
must 'not(deref(.)/../ks:public-key-format) or '
+ 'derived-from-or-self(deref(.)/../ks:public-key'
+ '-format, "ct:subject-public-key-info-format")';
}
}
}
}
case raw-private-key {
if-feature "server-ident-raw-public-key";
container raw-private-key {
description
"Specifies the server identity using a raw
private key.";
uses ks:inline-or-keystore-asymmetric-key-grouping {
refine "inline-or-keystore/inline/inline-definition" {
must 'not(public-key-format) or derived-from-or-self'
+ '(public-key-format,' + ' "ct:subject-public-'
+ 'key-info-format")';
}
refine "inline-or-keystore/central-keystore/"
+ "central-keystore-reference" {
must 'not(deref(.)/../ks:public-key-format) or '
+ 'derived-from-or-self(deref(.)/../ks:public-key'
+ '-format, "ct:subject-public-key-info-format")';
}
}
}
}
case tls12-psk {
if-feature "server-ident-tls12-psk";
container tls12-psk {
description
"Specifies the server identity using a PSK (pre-shared
or pairwise-symmetric key).";
uses ks:inline-or-keystore-symmetric-key-grouping;
leaf id-hint {
type string;
description
"The key 'psk_identity_hint' value used in the TLS
'ServerKeyExchange' message.";
reference
"RFC 4279: Pre-Shared Key Ciphersuites for
Transport Layer Security (TLS)";
}
}
}
case tls13-epsk {
if-feature "server-ident-tls13-epsk";
container tls13-epsk {
description
"An External Pre-Shared Key (EPSK) is established
or provisioned out-of-band, i.e., not from a TLS
connection. An EPSK is a tuple of (Base Key,
External Identity, Hash). External PSKs MUST
NOT be imported for (D)TLS 1.2 or prior versions.
When PSKs are provisioned out of band, the PSK
identity and the KDF hash algorithm to be used
with the PSK MUST also be provisioned.
The structure of this container is designed to
satisfy the requirements of RFC 8446 Section
4.2.11, the recommendations from Section 6 in
RFC 9257, and the EPSK input fields detailed in
Section 5.1 in RFC 9258. The base-key is based
upon ks:inline-or-keystore-symmetric-key-grouping
in order to provide users with flexible and
secure storage options.";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3
RFC 9257: Guidance for External Pre-Shared Key
(PSK) Usage in TLS
RFC 9258: Importing External Pre-Shared Keys
(PSKs) for TLS 1.3";
uses ks:inline-or-keystore-symmetric-key-grouping;
leaf external-identity {
type string;
mandatory true;
description
"As per Section 4.2.11 of RFC 8446, and Section 4.1
of RFC 9257, a sequence of bytes used to identify
an EPSK. A label for a pre-shared key established
externally.";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3
RFC 9257: Guidance for External Pre-Shared Key
(PSK) Usage in TLS";
}
leaf hash {
type tlscmn:epsk-supported-hash;
default sha-256;
description
"As per Section 4.2.11 of RFC 8446, for externally
established PSKs, the Hash algorithm MUST be set
when the PSK is established or default to SHA-256
if no such algorithm is defined. The server MUST
ensure that it selects a compatible PSK (if any)
and cipher suite. Each PSK MUST only be used
with a single hash function.";
reference
"RFC 8446: The Transport Layer Security (TLS)
Protocol Version 1.3";
}
leaf context {
type string;
description
"Per Section 5.1 of RFC 9258, context MUST include
the context used to determine the EPSK, if
any exists. For example, context may include
information about peer roles or identities
to mitigate Selfie-style reflection attacks.
Since the EPSK is a key derived from an external
protocol or sequence of protocols, context MUST
include a channel binding for the deriving
protocols [RFC5056]. The details of this
binding are protocol specfic and out of scope
for this document.";
reference
"RFC 9258: Importing External Pre-Shared Keys
(PSKs) for TLS 1.3";
}
leaf target-protocol {
type uint16;
description
"As per Section 3.1 of RFC 9258, the protocol
for which a PSK is imported for use.";
reference
"RFC 9258: Importing External Pre-Shared Keys
(PSKs) for TLS 1.3";
}
leaf target-kdf {
type uint16;
description
"As per Section 3 of RFC 9258, the KDF for
which a PSK is imported for use.";
reference
"RFC 9258: Importing External Pre-Shared Keys
(PSKs) for TLS 1.3";
}
}
}
}
} // container server-identity
container client-authentication {
if-feature "client-auth-supported";
nacm:default-deny-write;
must 'ca-certs or ee-certs or raw-public-keys or tls12-psks
or tls13-epsks';
presence
"Indicates that client authentication is supported (i.e.,
that the server will request clients send certificates).
If not configured, the TLS server SHOULD NOT request the
TLS clients provide authentication credentials.";
description
"Specifies how the TLS server can authenticate TLS clients.
Any combination of credentials is additive and unordered.
Note that no configuration is required for PSK (pre-shared
or pairwise-symmetric key) based authentication as the key
is necessarily the same as configured in the '../server-
identity' node.";
container ca-certs {
if-feature "client-auth-x509-cert";
presence
"Indicates that CA certificates have been configured.
This statement is present so the mandatory descendant
nodes do not imply that this node must be configured.";
description
"A set of certificate authority (CA) certificates used by
the TLS server to authenticate TLS client certificates.
A client certificate is authenticated if it has a valid
chain of trust to a configured CA certificate.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-certs-grouping;
}
container ee-certs {
if-feature "client-auth-x509-cert";
presence
"Indicates that EE certificates have been configured.
This statement is present so the mandatory descendant
nodes do not imply that this node must be configured.";
description
"A set of client certificates (i.e., end entity
certificates) used by the TLS server to authenticate
certificates presented by TLS clients. A client
certificate is authenticated if it is an exact
match to a configured client certificate.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-certs-grouping;
}
container raw-public-keys {
if-feature "client-auth-raw-public-key";
presence
"Indicates that raw public keys have been configured.
This statement is present so the mandatory descendant
nodes do not imply that this node must be configured.";
description
"A set of raw public keys used by the TLS server to
authenticate raw public keys presented by the TLS
client. A raw public key is authenticated if it
is an exact match to a configured raw public key.";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
uses ts:inline-or-truststore-public-keys-grouping {
refine "inline-or-truststore/inline/inline-definition/"
+ "public-key" {
must 'derived-from-or-self(public-key-format,'
+ ' "ct:subject-public-key-info-format")';
}
refine "inline-or-truststore/central-truststore/"
+ "central-truststore-reference" {
must 'not(deref(.)/../ts:public-key/ts:public-key-'
+ 'format[not(derived-from-or-self(., "ct:subject-'
+ 'public-key-info-format"))])';
}
}
}
leaf tls12-psks {
if-feature "client-auth-tls12-psk";
type empty;
description
"Indicates that the TLS server can authenticate TLS clients
using configured PSKs (pre-shared or pairwise-symmetric
keys).
No configuration is required since the PSK value is the
same as PSK value configured in the 'server-identity'
node.";
}
leaf tls13-epsks {
if-feature "client-auth-tls13-epsk";
type empty;
description
"Indicates that the TLS 1.3 server can authenticate TLS
clients using configured external PSKs (pre-shared keys).
No configuration is required since the PSK value is the
same as PSK value configured in the 'server-identity'
node.";
}
} // container client-authentication
container hello-params {
nacm:default-deny-write;
if-feature "tlscmn:hello-params";
uses tlscmn:hello-params-grouping;
description
"Configurable parameters for the TLS hello message.";
} // container hello-params
container keepalives {
nacm:default-deny-write;
if-feature "tls-server-keepalives";
description
"Configures the keepalive policy for the TLS server.";
leaf peer-allowed-to-send {
type empty;
description
"Indicates that the remote TLS client is allowed to send
HeartbeatRequest messages, as defined by RFC 6520
to this TLS server.";
reference
"RFC 6520: Transport Layer Security (TLS) and Datagram
Transport Layer Security (DTLS) Heartbeat Extension";
}
container test-peer-aliveness {
presence
"Indicates that the TLS server proactively tests the
aliveness of the remote TLS client.";
description
"Configures the keep-alive policy to proactively test
the aliveness of the TLS client. An unresponsive
TLS client is dropped after approximately max-wait
* max-attempts seconds.";
leaf max-wait {
type uint16 {
range "1..max";
}
units "seconds";
default "30";
description
"Sets the amount of time in seconds after which if
no data has been received from the TLS client, a
TLS-level message will be sent to test the
aliveness of the TLS client.";
}
leaf max-attempts {
type uint8;
default "3";
description
"Sets the maximum number of sequential keep-alive
messages that can fail to obtain a response from
the TLS client before assuming the TLS client is
no longer alive.";
}
}
} // container keepalives
} // grouping tls-server-grouping
}

View file

@ -0,0 +1,391 @@
module ietf-truststore {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:ietf-truststore";
prefix ts;
import ietf-netconf-acm {
prefix nacm;
reference
"RFC 8341: Network Configuration Access Control Model";
}
import ietf-crypto-types {
prefix ct;
reference
"RFC AAAA: YANG Data Types and Groupings for Cryptography";
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web : https://datatracker.ietf.org/wg/netconf
WG List : NETCONF WG list <mailto:netconf@ietf.org>
Author : Kent Watsen <kent+ietf@watsen.net>";
description
"This module defines a 'truststore' to centralize management
of trust anchors including certificates and public keys.
Copyright (c) 2023 IETF Trust and the persons identified
as authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with
or without modification, is permitted pursuant to, and
subject to the license terms contained in, the Revised
BSD License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC BBBB
(https://www.rfc-editor.org/info/rfcBBBB); see the RFC
itself for full legal notices.
The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL',
'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED',
'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document
are to be interpreted as described in BCP 14 (RFC 2119)
(RFC 8174) when, and only when, they appear in all
capitals, as shown here.";
revision 2023-12-28 {
description
"Initial version";
reference
"RFC BBBB: A YANG Data Model for a Truststore";
}
/****************/
/* Features */
/****************/
feature central-truststore-supported {
description
"The 'central-truststore-supported' feature indicates that
the server supports the truststore (i.e., implements the
'ietf-truststore' module).";
}
feature inline-definitions-supported {
description
"The 'inline-definitions-supported' feature indicates that
the server supports locally-defined trust anchors.";
}
feature certificates {
description
"The 'certificates' feature indicates that the server
implements the /truststore/certificate-bags subtree.";
}
feature public-keys {
description
"The 'public-keys' feature indicates that the server
implements the /truststore/public-key-bags subtree.";
}
/****************/
/* Typedefs */
/****************/
typedef certificate-bag-ref {
type leafref {
path "/ts:truststore/ts:certificate-bags/"
+ "ts:certificate-bag/ts:name";
}
description
"This typedef defines a reference to a certificate bag
in the central truststore.";
}
typedef certificate-ref {
type leafref {
path "/ts:truststore/ts:certificate-bags/ts:certificate-bag"
+ "[ts:name = current()/../certificate-bag]/"
+ "ts:certificate/ts:name";
}
description
"This typedef defines a reference to a specific certificate
in a certificate bag in the central truststore. This typedef
requires that there exist a sibling 'leaf' node called
'certificate-bag' that SHOULD have the typedef
'certificate-bag-ref'.";
}
typedef public-key-bag-ref {
type leafref {
path "/ts:truststore/ts:public-key-bags/"
+ "ts:public-key-bag/ts:name";
}
description
"This typedef defines a reference to a public key bag
in the central truststore.";
}
typedef public-key-ref {
type leafref {
path "/ts:truststore/ts:public-key-bags/ts:public-key-bag"
+ "[ts:name = current()/../public-key-bag]/"
+ "ts:public-key/ts:name";
}
description
"This typedef defines a reference to a specific public key
in a public key bag in the truststore. This typedef
requires that there exist a sibling 'leaf' node called
'public-key-bag' that SHOULD have the typedef
'public-key-bag-ref'.";
}
/*****************/
/* Groupings */
/*****************/
// *-ref groupings
grouping certificate-ref-grouping {
description
"Grouping for the reference to a certificate in a
certificate-bag in the central truststore.";
leaf certificate-bag {
nacm:default-deny-write;
if-feature "central-truststore-supported";
if-feature "certificates";
type ts:certificate-bag-ref;
must "../certificate";
description
"Reference to a certificate-bag in the truststore.";
}
leaf certificate {
nacm:default-deny-write;
type ts:certificate-ref;
must "../certificate-bag";
description
"Reference to a specific certificate in the
referenced certificate-bag.";
}
}
grouping public-key-ref-grouping {
description
"Grouping for the reference to a public key in a
public-key-bag in the central truststore.";
leaf public-key-bag {
nacm:default-deny-write;
if-feature "central-truststore-supported";
if-feature "public-keys";
type ts:public-key-bag-ref;
description
"Reference of a public key bag in the truststore inlucding
the certificate to authenticate the TLS client.";
}
leaf public-key {
nacm:default-deny-write;
type ts:public-key-ref;
description
"Reference to a specific public key in the
referenced public-key-bag.";
}
}
// inline-or-truststore-* groupings
grouping inline-or-truststore-certs-grouping {
description
"A grouping for the configuration of a list of certificates.
The list of certificate may be defined inline or as a
reference to a certificate bag in the central truststore.
Servers that do not define the 'central-truststore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate truststore locations.";
choice inline-or-truststore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the truststore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"A container for locally configured trust anchor
certificates.";
list certificate {
key "name";
min-elements 1;
description
"A trust anchor certificate.";
leaf name {
type string;
description
"An arbitrary name for this certificate.";
}
uses ct:trust-anchor-cert-grouping {
refine "cert-data" {
mandatory true;
}
}
}
}
}
case central-truststore {
if-feature "central-truststore-supported";
if-feature "certificates";
leaf central-truststore-reference {
type ts:certificate-bag-ref;
description
"A reference to a certificate bag that exists in the
central truststore.";
}
}
}
}
grouping inline-or-truststore-public-keys-grouping {
description
"A grouping that allows the public keys to be either
configured locally, within the using data model, or be a
reference to a public key bag stored in the truststore.
Servers that do not define the 'central-truststore-supported'
feature SHOULD augment in custom 'case' statements enabling
references to alternate truststore locations.";
choice inline-or-truststore {
nacm:default-deny-write;
mandatory true;
description
"A choice between an inlined definition and a definition
that exists in the truststore.";
case inline {
if-feature "inline-definitions-supported";
container inline-definition {
description
"A container to hold local public key definitions.";
list public-key {
key "name";
description
"A public key definition.";
leaf name {
type string;
description
"An arbitrary name for this public key.";
}
uses ct:public-key-grouping;
}
}
}
case central-truststore {
if-feature "central-truststore-supported";
if-feature "public-keys";
leaf central-truststore-reference {
type ts:public-key-bag-ref;
description
"A reference to a bag of public keys that exists
in the central truststore.";
}
}
}
}
// the truststore grouping
grouping truststore-grouping {
description
"A grouping definition that enables use in other contexts.
Where used, implementations MUST augment new 'case'
statements into the various inline-or-truststore 'choice'
statements to supply leafrefs to the model-specific
location(s).";
container certificate-bags {
nacm:default-deny-write;
if-feature "certificates";
description
"A collection of certificate bags.";
list certificate-bag {
key "name";
description
"A bag of certificates. Each bag of certificates SHOULD
be for a specific purpose. For instance, one bag could
be used to authenticate a specific set of servers, while
another could be used to authenticate a specific set of
clients.";
leaf name {
type string;
description
"An arbitrary name for this bag of certificates.";
}
leaf description {
type string;
description
"A description for this bag of certificates. The
intended purpose for the bag SHOULD be described.";
}
list certificate {
key "name";
description
"A trust anchor certificate.";
leaf name {
type string;
description
"An arbitrary name for this certificate.";
}
uses ct:trust-anchor-cert-grouping {
refine "cert-data" {
mandatory true;
}
}
}
}
}
container public-key-bags {
nacm:default-deny-write;
if-feature "public-keys";
description
"A collection of public key bags.";
list public-key-bag {
key "name";
description
"A bag of public keys. Each bag of keys SHOULD be for
a specific purpose. For instance, one bag could be used
authenticate a specific set of servers, while another
could be used to authenticate a specific set of clients.";
leaf name {
type string;
description
"An arbitrary name for this bag of public keys.";
}
leaf description {
type string;
description
"A description for this bag public keys. The
intended purpose for the bag SHOULD be described.";
}
list public-key {
key "name";
description
"A public key.";
leaf name {
type string;
description
"An arbitrary name for this public key.";
}
uses ct:public-key-grouping;
}
}
}
}
/*********************************/
/* Protocol accessible nodes */
/*********************************/
container truststore {
if-feature central-truststore-supported;
nacm:default-deny-write;
description
"The truststore contains bags of certificates and
public keys.";
uses truststore-grouping;
}
}

View file

@ -0,0 +1,314 @@
module ietf-x509-cert-to-name {
yang-version 1;
namespace
"urn:ietf:params:xml:ns:yang:ietf-x509-cert-to-name";
prefix x509c2n;
import ietf-yang-types {
prefix yang;
}
organization
"IETF NETMOD (NETCONF Data Modeling Language) Working Group";
contact
"WG Web: <http://tools.ietf.org/wg/netmod/>
WG List: <mailto:netmod@ietf.org>
WG Chair: Thomas Nadeau
<mailto:tnadeau@lucidvision.com>
WG Chair: Juergen Schoenwaelder
<mailto:j.schoenwaelder@jacobs-university.de>
Editor: Martin Bjorklund
<mailto:mbj@tail-f.com>
Editor: Juergen Schoenwaelder
<mailto:j.schoenwaelder@jacobs-university.de>";
description
"This module contains a collection of YANG definitions for
extracting a name from an X.509 certificate.
The algorithm used to extract a name from an X.509 certificate
was first defined in RFC 6353.
Copyright (c) 2014 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Simplified BSD License
set forth in Section 4.c of the IETF Trust's Legal Provisions
Relating to IETF Documents
(http://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 7407; see
the RFC itself for full legal notices.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model for
the Simple Network Management Protocol (SNMP)";
revision "2014-12-10" {
description "Initial revision.";
reference
"RFC 7407: A YANG Data Model for SNMP Configuration";
}
typedef tls-fingerprint {
type yang:hex-string {
pattern
'([0-9a-fA-F]){2}(:([0-9a-fA-F]){2}){0,254}';
}
description
"A fingerprint value that can be used to uniquely reference
other data of potentially arbitrary length.
A tls-fingerprint value is composed of a 1-octet hashing
algorithm identifier followed by the fingerprint value. The
first octet value identifying the hashing algorithm is taken
from the IANA 'TLS HashAlgorithm Registry' (RFC 5246). The
remaining octets are filled using the results of the hashing
algorithm.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.SnmpTLSFingerprint";
}
identity cert-to-name {
description
"Base identity for algorithms to derive a name from a
certificate.";
}
identity specified {
base cert-to-name;
description
"Directly specifies the name to be used for the certificate.
The value of the leaf 'name' in the cert-to-name list is
used.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertSpecified";
}
identity san-rfc822-name {
base cert-to-name;
description
"Maps a subjectAltName's rfc822Name to a name. The local part
of the rfc822Name is passed unaltered, but the host-part of
the name must be passed in lowercase. For example, the
rfc822Name field FooBar@Example.COM is mapped to name
FooBar@example.com.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertSANRFC822Name";
}
identity san-dns-name {
base cert-to-name;
description
"Maps a subjectAltName's dNSName to a name after first
converting it to all lowercase (RFC 5280 does not specify
converting to lowercase, so this involves an extra step).
This mapping results in a 1:1 correspondence between
subjectAltName dNSName values and the name values.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertSANDNSName";
}
identity san-ip-address {
base cert-to-name;
description
"Maps a subjectAltName's iPAddress to a name by
transforming the binary-encoded address as follows:
1) for IPv4, the value is converted into a
decimal-dotted quad address (e.g., '192.0.2.1').
2) for IPv6 addresses, the value is converted into a
32-character, all-lowercase hexadecimal string
without any colon separators.
This mapping results in a 1:1 correspondence between
subjectAltName iPAddress values and the name values.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertSANIpAddress";
}
identity san-any {
base cert-to-name;
description
"Maps any of the following fields using the corresponding
mapping algorithms:
+------------+-----------------+
| Type | Algorithm |
|------------+-----------------|
| rfc822Name | san-rfc822-name |
| dNSName | san-dns-name |
| iPAddress | san-ip-address |
+------------+-----------------+
The first matching subjectAltName value found in the
certificate of the above types MUST be used when deriving
the name. The mapping algorithm specified in the
'Algorithm' column MUST be used to derive the name.
This mapping results in a 1:1 correspondence between
subjectAltName values and name values. The three sub-mapping
algorithms produced by this combined algorithm cannot produce
conflicting results between themselves.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertSANAny";
}
identity common-name {
base cert-to-name;
description
"Maps a certificate's CommonName to a name after converting
it to a UTF-8 encoding. The usage of CommonNames is
deprecated, and users are encouraged to use subjectAltName
mapping methods instead. This mapping results in a 1:1
correspondence between certificate CommonName values and name
values.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertCommonName";
}
grouping cert-to-name {
description
"Defines nodes for mapping certificates to names. Modules
that use this grouping should describe how the resulting
name is used.";
list cert-to-name {
key "id";
description
"This list defines how certificates are mapped to names.
The name is derived by considering each cert-to-name
list entry in order. The cert-to-name entry's fingerprint
determines whether the list entry is a match:
1) If the cert-to-name list entry's fingerprint value
matches that of the presented certificate, then consider
the list entry a successful match.
2) If the cert-to-name list entry's fingerprint value
matches that of a locally held copy of a trusted CA
certificate, and that CA certificate was part of the CA
certificate chain to the presented certificate, then
consider the list entry a successful match.
Once a matching cert-to-name list entry has been found, the
map-type is used to determine how the name associated with
the certificate should be determined. See the map-type
leaf's description for details on determining the name value.
If it is impossible to determine a name from the cert-to-name
list entry's data combined with the data presented in the
certificate, then additional cert-to-name list entries MUST
be searched to look for another potential match.
Security administrators are encouraged to make use of
certificates with subjectAltName fields that can be mapped to
names so that a single root CA certificate can allow all
child certificates' subjectAltName fields to map directly to
a name via a 1:1 transformation.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol (SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertToTSNEntry";
leaf id {
type uint32;
description
"The id specifies the order in which the entries in the
cert-to-name list are searched. Entries with lower
numbers are searched first.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol
(SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertToTSNID";
}
leaf fingerprint {
type tls-fingerprint;
mandatory true;
description
"Specifies a value with which the fingerprint of the
full certificate presented by the peer is compared. If
the fingerprint of the full certificate presented by the
peer does not match the fingerprint configured, then the
entry is skipped, and the search for a match continues.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol
(SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertToTSNFingerprint";
}
leaf map-type {
type identityref {
base cert-to-name;
}
mandatory true;
description
"Specifies the algorithm used to map the certificate
presented by the peer to a name.
Mappings that need additional configuration objects should
use the 'when' statement to make them conditional based on
the map-type.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol
(SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertToTSNMapType";
}
leaf name {
when
"../map-type = 'x509c2n:specified'";
type string;
mandatory true;
description
"Directly specifies the NETCONF username when the
map-type is 'specified'.";
reference
"RFC 6353: Transport Layer Security (TLS) Transport Model
for the Simple Network Management Protocol
(SNMP).
SNMP-TLS-TM-MIB.snmpTlstmCertToTSNData";
}
} // list cert-to-name
} // grouping cert-to-name
} // module ietf-x509-cert-to-name

View file

@ -0,0 +1,475 @@
module libnetconf2-netconf-server {
yang-version 1.1;
namespace "urn:cesnet:libnetconf2-netconf-server";
prefix np2;
import ietf-netconf-server {
prefix ncs;
}
import ietf-crypto-types {
prefix ct;
}
import iana-ssh-public-key-algs {
prefix sshpka;
}
import iana-ssh-key-exchange-algs {
prefix sshkea;
}
import iana-ssh-encryption-algs {
prefix sshea;
}
import iana-ssh-mac-algs {
prefix sshma;
}
import ietf-tls-server {
prefix tlss;
}
revision "2024-07-09" {
description "Second revision.";
}
// Identities
/*
identity ed25519-private-key-format {
base ct:private-key-format;
description
"This identity would indicate that the
private key is encoded in a ED25519PrivateKey
format. However no such format is currently
standardized or even exists.
If you wish to use a private key that uses
an ED25519 algorithm, you need to pick either
the private-key-info-format or
openssh-private-key-format identity.";
}
*/
identity private-key-info-format {
base ct:private-key-format;
description
"Indicates that the private key is encoded
as a PrivateKeyInfo structure (from RFC 5208).
The expected header of the private key:
-----BEGIN PRIVATE KEY-----
The expected footer of the private key:
-----END PRIVATE KEY-----
Supported private key algorithms to use with
this format are: RSA, EC and ED25519.
Commonly used public key format for this
type of private key is represented by the
SubjectPublicKeyInfo identity.";
reference
"RFC 5208: PKCS #8: Private-Key Information
Syntax Specification Version 1.2";
}
identity openssh-private-key-format {
base ct:private-key-format;
description
"Indicates that the private key is encoded
in the OpenSSH format.
The expected header of the private key:
-----BEGIN OPENSSH PRIVATE KEY-----
The expected footer of the private key:
-----END OPENSSH PRIVATE KEY-----
Supported private key algorithms to use with
this format are: RSA, EC and ED25519.
Commonly used public key format for this
type of private key is either the
SSH2 public key format (from RFC 4716)
or the Public key format defined in RFC 4253,
Section 6.6.";
reference
"The OpenSSH Private Key Format:
https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
RFC 4716:
The Secure Shell (SSH) Public Key File Format
RFC 4253:
The Secure Shell (SSH) Transport Layer Protocol";
}
identity openssh-ssh-ed25519-cert-v01 {
base sshpka:public-key-alg-base;
description
"SSH-ED25519-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-ecdsa-sha2-nistp521-cert-v01 {
base sshpka:public-key-alg-base;
description
"ECDSA-SHA2-NISTP521-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-ecdsa-sha2-nistp384-cert-v01 {
base sshpka:public-key-alg-base;
description
"ECDSA-SHA2-NISTP384-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-ecdsa-sha2-nistp256-cert-v01 {
base sshpka:public-key-alg-base;
description
"ECDSA-SHA2-NISTP256-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-rsa-sha2-512-cert-v01 {
base sshpka:public-key-alg-base;
description
"RSA-SHA2-512-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-rsa-sha2-256-cert-v01 {
base sshpka:public-key-alg-base;
description
"RSA-SHA2-256-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-ssh-rsa-cert-v01 {
base sshpka:public-key-alg-base;
description
"SSH-RSA-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity openssh-ssh-dss-cert-v01 {
base sshpka:public-key-alg-base;
description
"SSH-DSS-CERT-V01@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.certkeys:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD";
}
identity libssh-curve25519-sha256 {
base sshkea:key-exchange-alg-base;
description
"CURVE25519-SHA256@LIBSSH.ORG";
reference
"curve25519-sha256@libssh.org specification:
https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt";
}
identity openssh-chacha20-poly1305 {
base sshea:encryption-alg-base;
description
"CHACHA20-POLY1305@OPENSSH.COM";
reference
"OpenSSH PROTOCOL.chacha20poly1305:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD";
}
identity openssh-aes256-gcm {
base sshea:encryption-alg-base;
description
"AES256-GCM@OPENSSH.COM";
reference
"OpenSSH PROTOCOL, Section 1.6:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD";
}
identity openssh-aes128-gcm {
base sshea:encryption-alg-base;
description
"AES128-GCM@OPENSSH.COM";
reference
"OpenSSH PROTOCOL, Section 1.6:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD";
}
identity openssh-hmac-sha2-256-etm {
base sshma:mac-alg-base;
description
"HMAC-SHA2-256-ETM@OPENSSH.COM";
reference
"OpenSSH PROTOCOL:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD";
}
identity openssh-hmac-sha2-512-etm {
base sshma:mac-alg-base;
description
"HMAC-SHA2-512-ETM@OPENSSH.COM";
reference
"OpenSSH PROTOCOL:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD";
}
identity openssh-hmac-sha1-etm {
base sshma:mac-alg-base;
description
"HMAC-SHA1-ETM@OPENSSH.COM";
reference
"OpenSSH PROTOCOL:
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL?annotate=HEAD";
}
// Typedefs
typedef time-period {
type string {
pattern '(1[0-2]|[1-9])m|[1-4]w|[1-7]d|(2[0-4]|1[0-9]|[1-9])h';
}
description
"The time-period type allows to specify time in either months, weeks, days, or hours.
Its purpose is to create time intervals for the certificate expiration notifications.";
}
// Groupings
grouping ssh-authentication-params-grouping {
description
"Grouping for SSH authentication parameters.";
leaf auth-timeout {
type uint16;
default 30;
units "seconds";
description
"Represents the maximum amount of seconds an authentication can go on for.";
}
}
grouping ssh-server-banner-grouping {
description
"Grouping for the SSH server banner.";
leaf banner {
type string {
length "1..247";
}
description
"The banner that will be sent to the client when connecting to the server.
If not set, the libnetconf2 default with its version will be used.";
reference
"RFC 4253: The Secure Shell (SSH) Transport Layer Protocol, section 4.2.";
}
}
grouping system-auth-public-keys-grouping {
description
"Grouping for using the system configured keys in the SSH public key authentication method.";
container use-system-keys {
presence
"Indicates that the given user will be authenticated using the system's configured public keys.";
description
"Authentication is done using the system's mechanisms.";
reference
"libnetconf2 documentation:
Section SSH";
}
}
grouping keyboard-interactive-grouping {
description
"Grouping for the SSH Keyboard interactive authentication method.";
container keyboard-interactive {
presence "Indicates that the given client supports the SSH Keyboard Interactive authentication method.";
description
"Keyboard interactive SSH authentication method.";
reference
"RFC 4256:
Generic Message Exchange Authentication for
the Secure Shell Protocol (SSH)";
choice method {
mandatory true;
description
"Method to perform the authentication with.";
container use-system-auth {
presence
"Indicates that the system will handle the authentication.";
description
"Authentication is done using the system's mechanisms.";
reference
"libnetconf2 documentation:
Section SSH";
}
}
}
}
grouping endpoint-reference-grouping {
description
"Grouping for the endpoint reference.";
leaf endpoint-reference {
type leafref {
path "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:name";
}
description
"Reference to another endpoint. The purpose is to use the referenced endpoint's authentication mechanisms.
If a connection occurs on an endpoint, the connecting user will be tried to be authenticated
using the given endpoint's defined methods. If the user wasn't authenticated and the endpoint
references another endpoint, the authentication will be tried again. However, this time
using the referenced endpoint's mechanisms. The references can be
multiple, however there must not be a cycle.";
}
}
// Augments
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh" +
"/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication" {
uses ssh-authentication-params-grouping;
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints" +
"/ncs:endpoint/ncs:transport/ncs:ssh/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication" {
uses ssh-authentication-params-grouping;
}
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh" +
"/ncs:ssh/ncs:ssh-server-parameters/ncs:server-identity" {
uses ssh-server-banner-grouping;
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints" +
"/ncs:endpoint/ncs:transport/ncs:ssh/ncs:ssh/ncs:ssh-server-parameters/ncs:server-identity" {
uses ssh-authentication-params-grouping;
}
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh/ncs:ssh/ncs:ssh-server-parameters" +
"/ncs:client-authentication/ncs:users/ncs:user/ncs:public-keys/ncs:inline-or-truststore" {
case system-auth-public-keys {
uses system-auth-public-keys-grouping;
}
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh" +
"/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication/ncs:users/ncs:user/ncs:public-keys/ncs:inline-or-truststore" {
case system-auth-public-keys {
uses system-auth-public-keys-grouping;
}
}
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh" +
"/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication/ncs:users/ncs:user" {
uses keyboard-interactive-grouping;
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints" +
"/ncs:endpoint/ncs:transport/ncs:ssh/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication/ncs:users/ncs:user" {
uses keyboard-interactive-grouping;
}
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:ssh" +
"/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication" {
uses endpoint-reference-grouping;
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints" +
"/ncs:endpoint/ncs:transport/ncs:ssh/ncs:ssh/ncs:ssh-server-parameters/ncs:client-authentication" {
uses endpoint-reference-grouping;
}
augment "/ncs:netconf-server/ncs:listen/ncs:endpoints/ncs:endpoint/ncs:transport/ncs:tls" +
"/ncs:tls/ncs:tls-server-parameters/ncs:client-authentication" {
uses endpoint-reference-grouping;
}
augment "/ncs:netconf-server/ncs:call-home/ncs:netconf-client/ncs:endpoints" +
"/ncs:endpoint/ncs:transport/ncs:tls/ncs:tls/ncs:tls-server-parameters/ncs:client-authentication" {
uses endpoint-reference-grouping;
}
// Protocol-accessible Nodes
container ln2-netconf-server {
container certificate-expiration-notif-intervals {
if-feature "ct:certificate-expiration-notification";
description
"Container for the certificate expiration notification intervals.
Its child nodes describe the ability to set the time intervals for the certificate
expiration notifications. These intervals are given in the form of an anchor and a period.
By default, these notifications are generated 3, 2, and 1 month; 2 weeks; 7, 6, 5, 4, 3, 2 and 1 day before a certificate expires.
Additionally, notifications are generated on the day of expiration and every day thereafter.
Simplified example of YANG data that describe the default intervals:
Anchor Period
3m ... 1m
2w ... 1w
7d ... 1d
";
list interval {
key "anchor period";
leaf anchor {
type time-period;
description
"The time anchor for the notification. The anchor is the time
before the certificate expiration when a notification will be sent.
It is essentially the lower bound of the given interval.";
}
leaf period {
type time-period;
// Require the period to be smaller than the anchor (only units are checked for simplicity)
must "(contains(., 'm') and contains(../anchor, 'm')) or
(contains(., 'w') and (contains(../anchor, 'm') or contains(../anchor, 'w'))) or
(contains(., 'd') and (contains(../anchor, 'm') or contains(../anchor, 'w') or contains(../anchor, 'd'))) or
contains(., 'h')" {
error-message
"Certificate expiration notification period must be smaller than the anchor.";
}
description
"The period of the notification. The period is the time
between two notifications within the given time interval.";
}
}
}
}
}

View file

@ -1,8 +1,9 @@
/**
* \file nc_client.h
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2's main public header for NETCONF clients.
* @file nc_client.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2's main public header for NETCONF clients.
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -15,13 +16,12 @@
#ifndef NC_CLIENT_H_
#define NC_CLIENT_H_
@SSH_MACRO@
@TLS_MACRO@
#ifdef __cplusplus
extern "C" {
#endif
@SSH_TLS_MACRO@
#include <libnetconf2/netconf.h>
#include <libnetconf2/log.h>
#include <libnetconf2/messages_client.h>

View file

@ -1,8 +1,9 @@
/**
* \file nc_server.h
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2's main public header for NETCONF servers.
* @file nc_server.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2's main public header for NETCONF servers.
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -15,16 +16,16 @@
#ifndef NC_SERVER_H_
#define NC_SERVER_H_
@SSH_MACRO@
@TLS_MACRO@
#ifdef __cplusplus
extern "C" {
#endif
@SSH_TLS_MACRO@
#include <libnetconf2/netconf.h>
#include <libnetconf2/log.h>
#include <libnetconf2/messages_server.h>
#include <libnetconf2/server_config.h>
#include <libnetconf2/session_server.h>
#include <libnetconf2/session_server_ch.h>

32
nc_version.h.in Normal file
View file

@ -0,0 +1,32 @@
/**
* @file nc_version.h
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 version information
*
* @copyright
* Copyright (c) 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef NC_VERSION_H_
#define NC_VERSION_H_
#ifdef __cplusplus
extern "C" {
#endif
#define NC_VERSION_MAJOR @LIBNETCONF2_MAJOR_SOVERSION@ /**< libnetconf2 major version number */
#define NC_VERSION_MINOR @LIBNETCONF2_MINOR_SOVERSION@ /**< libnetconf2 minor version number */
#define NC_VERSION_MICRO @LIBNETCONF2_MICRO_SOVERSION@ /**< libnetconf2 micro version number */
#define NC_VERSION "@LIBNETCONF2_SOVERSION_FULL@" /**< libnetconf2 version string */
#ifdef __cplusplus
}
#endif
#endif /* NC_VERSION_H_ */

View file

@ -1,9 +1,11 @@
/**
* \file config.h
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2 various configuration settings.
* @file config.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 various configuration settings.
*
* Copyright (c) 2015 - 2017 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -33,14 +35,29 @@
#cmakedefine HAVE_SHADOW
/*
* Support for crypt.h
* Support for terminal in/out
*/
#cmakedefine HAVE_CRYPT
#cmakedefine HAVE_TERMIOS
/*
* Location of installed basic YANG modules on the system
* Support for keyboard-interactive SSH authentication method
*/
#define NC_YANG_DIR "@YANG_MODULE_DIR@"
#cmakedefine HAVE_LIBPAM
/*
* Use MbedTLS as TLS back-end
*/
#cmakedefine HAVE_MBEDTLS
/*
* Location of installed YANG modules on the system
*/
#define NC_SERVER_SEARCH_DIR "@YANG_MODULE_DIR@"
/*
* Location of installed YANG modules on the system
*/
#define NC_CLIENT_SEARCH_DIR "@CLIENT_SEARCH_DIR@"
/*
* Inactive read timeout

391
src/io.c
View file

@ -1,9 +1,11 @@
/**
* \file io.c
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2 - input/output functions
* @file io.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 - input/output functions
*
* Copyright (c) 2015 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -13,27 +15,30 @@
*/
#define _GNU_SOURCE /* asprintf, signals */
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <poll.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#ifdef NC_ENABLED_TLS
# include <openssl/err.h>
#endif
#include <libyang/libyang.h>
#include "compat.h"
#include "libnetconf.h"
#include "config.h"
#include "log_p.h"
#include "messages_p.h"
#include "netconf.h"
#include "session.h"
#include "session_p.h"
#include "session_wrapper.h"
const char *nc_msgtype2str[] = {
"error",
@ -49,49 +54,13 @@ const char *nc_msgtype2str[] = {
#define BUFFERSIZE 512
#ifdef NC_ENABLED_TLS
static char *
nc_ssl_error_get_reasons(void)
{
unsigned int e;
int reason_size, reason_len;
char *reasons = NULL;
reason_size = 1;
reason_len = 0;
while ((e = ERR_get_error())) {
if (reason_len) {
/* add "; " */
reason_size += 2;
reasons = nc_realloc(reasons, reason_size);
if (!reasons) {
ERRMEM;
return NULL;
}
reason_len += sprintf(reasons + reason_len, "; ");
}
reason_size += strlen(ERR_reason_error_string(e));
reasons = nc_realloc(reasons, reason_size);
if (!reasons) {
ERRMEM;
return NULL;
}
reason_len += sprintf(reasons + reason_len, "%s", ERR_reason_error_string(e));
}
return reasons;
}
#endif
static ssize_t
nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_timeout, struct timespec *ts_act_timeout)
nc_read(struct nc_session *session, char *buf, uint32_t count, uint32_t inact_timeout, struct timespec *ts_act_timeout)
{
size_t readd = 0;
uint32_t readd = 0;
ssize_t r = -1;
int fd, interrupted;
struct timespec ts_cur, ts_inact_timeout;
struct timespec ts_inact_timeout;
assert(session);
assert(buf);
@ -104,8 +73,7 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_time
return 0;
}
nc_gettimespec_mono(&ts_inact_timeout);
nc_addtimespec(&ts_inact_timeout, inact_timeout);
nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
do {
interrupted = 0;
switch (session->ti_type) {
@ -139,8 +107,8 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_time
}
break;
#ifdef NC_ENABLED_SSH
case NC_TI_LIBSSH:
#ifdef NC_ENABLED_SSH_TLS
case NC_TI_SSH:
/* read via libssh */
r = ssh_channel_read(session->ti.libssh.channel, buf + readd, count - readd, 0);
if (r == SSH_AGAIN) {
@ -161,48 +129,15 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_time
break;
}
break;
#endif
#ifdef NC_ENABLED_TLS
case NC_TI_OPENSSL:
/* read via OpenSSL */
ERR_clear_error();
r = SSL_read(session->ti.tls, buf + readd, count - readd);
if (r <= 0) {
int e;
char *reasons;
switch (e = SSL_get_error(session->ti.tls, r)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
r = 0;
break;
case SSL_ERROR_ZERO_RETURN:
ERR(session, "Communication socket unexpectedly closed (OpenSSL).");
session->status = NC_STATUS_INVALID;
session->term_reason = NC_SESSION_TERM_DROPPED;
return -1;
case SSL_ERROR_SYSCALL:
ERR(session, "SSL socket error (%s).", errno ? strerror(errno) : "unexpected EOF");
session->status = NC_STATUS_INVALID;
session->term_reason = NC_SESSION_TERM_OTHER;
return -1;
case SSL_ERROR_SSL:
reasons = nc_ssl_error_get_reasons();
ERR(session, "SSL error (%s).", reasons);
free(reasons);
session->status = NC_STATUS_INVALID;
session->term_reason = NC_SESSION_TERM_OTHER;
return -1;
default:
ERR(session, "Unknown SSL error occured (err code %d).", e);
session->status = NC_STATUS_INVALID;
session->term_reason = NC_SESSION_TERM_OTHER;
return -1;
}
case NC_TI_TLS:
r = nc_tls_read_wrap(session, (unsigned char *)buf + readd, count - readd);
if (r < 0) {
/* non-recoverable error */
return r;
}
break;
#endif
#endif /* NC_ENABLED_SSH_TLS */
}
if (r == 0) {
@ -210,9 +145,8 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_time
if (!interrupted) {
usleep(NC_TIMEOUT_STEP);
}
nc_gettimespec_mono(&ts_cur);
if ((nc_difftimespec(&ts_cur, &ts_inact_timeout) < 1) || (nc_difftimespec(&ts_cur, ts_act_timeout) < 1)) {
if (nc_difftimespec(&ts_cur, &ts_inact_timeout) < 1) {
if ((nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) || (nc_timeouttime_cur_diff(ts_act_timeout) < 1)) {
if (nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) {
ERR(session, "Inactive read timeout elapsed.");
} else {
ERR(session, "Active read timeout elapsed.");
@ -226,8 +160,7 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_time
readd += r;
/* reset inactive timeout */
nc_gettimespec_mono(&ts_inact_timeout);
nc_addtimespec(&ts_inact_timeout, inact_timeout);
nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
}
} while (readd < count);
@ -249,10 +182,7 @@ nc_read_chunk(struct nc_session *session, size_t len, uint32_t inact_timeout, st
}
*chunk = malloc((len + 1) * sizeof **chunk);
if (!*chunk) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!*chunk, -1);
r = nc_read(session, *chunk, len, inact_timeout, ts_act_timeout);
if (r <= 0) {
@ -282,10 +212,7 @@ nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint
size = BUFFERSIZE;
}
chunk = malloc((size + 1) * sizeof *chunk);
if (!chunk) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!chunk, -1);
len = strlen(endtag);
while (1) {
@ -301,10 +228,7 @@ nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint
/* get more memory */
size = size + BUFFERSIZE;
chunk = nc_realloc(chunk, (size + 1) * sizeof *chunk);
if (!chunk) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!chunk, -1);
}
/* get another character */
@ -362,8 +286,7 @@ nc_read_msg_io(struct nc_session *session, int io_timeout, struct ly_in **msg, i
goto cleanup;
}
nc_gettimespec_mono(&ts_act_timeout);
nc_addtimespec(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000);
nc_timeouttime_get(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000);
if (!io_locked) {
/* SESSION IO LOCK */
@ -428,11 +351,7 @@ nc_read_msg_io(struct nc_session *session, int io_timeout, struct ly_in **msg, i
/* realloc message buffer, remember to count terminating null byte */
data = nc_realloc(data, len + chunk_len + 1);
if (!data) {
ERRMEM;
ret = -1;
goto cleanup;
}
NC_CHECK_ERRMEM_GOTO(!data, ret = -1, cleanup);
memcpy(data + len, chunk, chunk_len);
len += chunk_len;
data[len] = '\0';
@ -469,7 +388,6 @@ cleanup:
static int
nc_read_poll(struct nc_session *session, int io_timeout)
{
sigset_t sigmask, origmask;
int ret = -2;
struct pollfd fds;
@ -479,8 +397,13 @@ nc_read_poll(struct nc_session *session, int io_timeout)
}
switch (session->ti_type) {
#ifdef NC_ENABLED_SSH
case NC_TI_LIBSSH:
#ifdef NC_ENABLED_SSH_TLS
case NC_TI_SSH:
if (io_timeout == -1) {
/* BUG libssh 0.11.0 replaces timeout -1 with 0 for non-blocking sessions */
io_timeout = INT_MAX;
}
/* EINTR is handled, it resumes waiting */
ret = ssh_channel_poll_timeout(session->ti.libssh.channel, io_timeout, 0);
if (ret == SSH_ERROR) {
@ -501,10 +424,8 @@ nc_read_poll(struct nc_session *session, int io_timeout)
fds.revents = 0;
}
break;
#endif
#ifdef NC_ENABLED_TLS
case NC_TI_OPENSSL:
ret = SSL_pending(session->ti.tls);
case NC_TI_TLS:
ret = nc_tls_get_num_pending_bytes_wrap(session->ti.tls.session);
if (ret) {
/* some buffered TLS data available */
ret = 1;
@ -512,8 +433,8 @@ nc_read_poll(struct nc_session *session, int io_timeout)
break;
}
fds.fd = SSL_get_fd(session->ti.tls);
#endif
fds.fd = nc_tls_get_fd_wrap(session);
#endif /* NC_ENABLED_SSH_TLS */
/* fallthrough */
case NC_TI_FD:
case NC_TI_UNIX:
@ -526,11 +447,7 @@ nc_read_poll(struct nc_session *session, int io_timeout)
fds.events = POLLIN;
fds.revents = 0;
sigfillset(&sigmask);
pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
ret = poll(&fds, 1, io_timeout);
pthread_sigmask(SIG_SETMASK, &origmask, NULL);
ret = nc_poll(&fds, 1, io_timeout);
break;
default:
@ -600,7 +517,7 @@ nc_read_msg_poll_io(struct nc_session *session, int io_timeout, struct ly_in **m
/* does not really log, only fatal errors */
int
nc_session_is_connected(struct nc_session *session)
nc_session_is_connected(const struct nc_session *session)
{
int ret;
struct pollfd fds;
@ -612,15 +529,13 @@ nc_session_is_connected(struct nc_session *session)
case NC_TI_UNIX:
fds.fd = session->ti.unixsock.sock;
break;
#ifdef NC_ENABLED_SSH
case NC_TI_LIBSSH:
#ifdef NC_ENABLED_SSH_TLS
case NC_TI_SSH:
return ssh_is_connected(session->ti.libssh.session);
#endif
#ifdef NC_ENABLED_TLS
case NC_TI_OPENSSL:
fds.fd = SSL_get_fd(session->ti.tls);
case NC_TI_TLS:
fds.fd = nc_tls_get_fd_wrap(session);
break;
#endif
#endif /* NC_ENABLED_SSH_TLS */
default:
return 0;
}
@ -632,11 +547,8 @@ nc_session_is_connected(struct nc_session *session)
fds.events = POLLIN;
fds.revents = 0;
errno = 0;
while (((ret = poll(&fds, 1, 0)) == -1) && (errno == EINTR)) {}
ret = nc_poll(&fds, 1, 0);
if (ret == -1) {
ERR(session, "poll failed (%s).", strerror(errno));
return 0;
} else if ((ret > 0) && (fds.revents & (POLLHUP | POLLERR))) {
return 0;
@ -646,21 +558,26 @@ nc_session_is_connected(struct nc_session *session)
}
#define WRITE_BUFSIZE (2 * BUFFERSIZE)
struct wclb_arg {
struct nc_wclb_arg {
struct nc_session *session;
char buf[WRITE_BUFSIZE];
size_t len;
uint32_t len;
};
/**
* @brief Write to a NETCONF session.
*
* @param[in] session Session to write to.
* @param[in] buf Buffer to write.
* @param[in] count Count of bytes from @p buf to write.
* @return Number of bytes written.
* @return -1 on error.
*/
static int
nc_write(struct nc_session *session, const void *buf, size_t count)
nc_write(struct nc_session *session, const void *buf, uint32_t count)
{
int c, fd, interrupted;
size_t written = 0;
#ifdef NC_ENABLED_TLS
unsigned long e;
#endif
uint32_t written = 0;
if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
return -1;
@ -674,7 +591,7 @@ nc_write(struct nc_session *session, const void *buf, size_t count)
return -1;
}
DBG(session, "Sending message:\n%.*s\n", count, buf);
DBG(session, "Sending message:\n%.*s\n", (int)count, buf);
do {
interrupted = 0;
@ -689,13 +606,13 @@ nc_write(struct nc_session *session, const void *buf, size_t count)
c = 0;
interrupted = 1;
} else if (c < 0) {
ERR(session, "socket error (%s).", strerror(errno));
ERR(session, "Socket error (%s).", strerror(errno));
return -1;
}
break;
#ifdef NC_ENABLED_SSH
case NC_TI_LIBSSH:
#ifdef NC_ENABLED_SSH_TLS
case NC_TI_SSH:
if (ssh_channel_is_closed(session->ti.libssh.channel) || ssh_channel_is_eof(session->ti.libssh.channel)) {
if (ssh_channel_is_closed(session->ti.libssh.channel)) {
ERR(session, "SSH channel unexpectedly closed.");
@ -712,36 +629,14 @@ nc_write(struct nc_session *session, const void *buf, size_t count)
return -1;
}
break;
#endif
#ifdef NC_ENABLED_TLS
case NC_TI_OPENSSL:
c = SSL_write(session->ti.tls, (char *)(buf + written), count - written);
if (c < 1) {
char *reasons;
switch ((e = SSL_get_error(session->ti.tls, c))) {
case SSL_ERROR_ZERO_RETURN:
ERR(session, "SSL connection was properly closed.");
return -1;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
c = 0;
break;
case SSL_ERROR_SYSCALL:
ERR(session, "SSL socket error (%s).", strerror(errno));
return -1;
case SSL_ERROR_SSL:
reasons = nc_ssl_error_get_reasons();
ERR(session, "SSL error (%s).", reasons);
free(reasons);
return -1;
default:
ERR(session, "Unknown SSL error occured (err code %d).", e);
case NC_TI_TLS:
c = nc_tls_write_wrap(session, (const unsigned char *)(buf + written), count - written);
if (c < 0) {
/* possible client dc, or some socket/TLS communication error */
return -1;
}
}
break;
#endif
#endif /* NC_ENABLED_SSH_TLS */
default:
ERRINT;
return -1;
@ -758,30 +653,47 @@ nc_write(struct nc_session *session, const void *buf, size_t count)
return written;
}
/**
* @brief Write the start tag and the message part of a chunked-framing NETCONF message.
*
* @param[in] session Session to write to.
* @param[in] buf Message buffer to write.
* @param[in] count Count of bytes from @p buf to write.
* @return Number of bytes written.
* @return -1 on error.
*/
static int
nc_write_starttag_and_msg(struct nc_session *session, const void *buf, size_t count)
nc_write_starttag_and_msg(struct nc_session *session, const void *buf, uint32_t count)
{
int ret = 0, c;
int ret = 0, r;
char chunksize[24];
// warning: %zu directive writing between 4 and 20 bytes into a region of size 18 [-Wformat-overflow=]
if (session->version == NC_VERSION_11) {
sprintf(chunksize, "\n#%zu\n", count);
ret = nc_write(session, chunksize, strlen(chunksize));
if (ret == -1) {
r = sprintf(chunksize, "\n#%" PRIu32 "\n", count);
r = nc_write(session, chunksize, r);
if (r == -1) {
return -1;
}
ret += r;
}
c = nc_write(session, buf, count);
if (c == -1) {
r = nc_write(session, buf, count);
if (r == -1) {
return -1;
}
ret += c;
ret += r;
return ret;
}
/**
* @brief Write the end tag part of a chunked-framing NETCONF message.
*
* @param[in] session Session to write to.
* @return Number of bytes written.
* @return -1 on error.
*/
static int
nc_write_endtag(struct nc_session *session)
{
@ -796,8 +708,15 @@ nc_write_endtag(struct nc_session *session)
return ret;
}
/**
* @brief Flush all the data buffered for writing.
*
* @param[in] warg Write callback structure to flush.
* @return Number of written bytes.
* @return -1 on error.
*/
static int
nc_write_clb_flush(struct wclb_arg *warg)
nc_write_clb_flush(struct nc_wclb_arg *warg)
{
int ret = 0;
@ -810,12 +729,22 @@ nc_write_clb_flush(struct wclb_arg *warg)
return ret;
}
/**
* @brief Write callback buffering the data in a write structure.
*
* @param[in] arg Write structure used for buffering.
* @param[in] buf Buffer to write.
* @param[in] count Count of bytes to write from @p buf.
* @param[in] xmlcontent Whether the data are actually printed as part of an XML in which case they need to be encoded.
* @return Number of written bytes.
* @return -1 on error.
*/
static ssize_t
nc_write_clb(void *arg, const void *buf, size_t count, int xmlcontent)
nc_write_clb(void *arg, const void *buf, uint32_t count, int xmlcontent)
{
int ret = 0, c;
size_t l;
struct wclb_arg *warg = (struct wclb_arg *)arg;
ssize_t ret = 0, c;
uint32_t l;
struct nc_wclb_arg *warg = arg;
if (!buf) {
c = nc_write_clb_flush(warg);
@ -895,6 +824,9 @@ nc_write_clb(void *arg, const void *buf, size_t count, int xmlcontent)
return ret;
}
/**
* @brief Write print callback used by libyang.
*/
static ssize_t
nc_write_xmlclb(void *arg, const void *buf, size_t count)
{
@ -921,7 +853,7 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
struct nc_server_notif *notif;
struct nc_server_reply *reply;
char *buf;
struct wclb_arg arg;
struct nc_wclb_arg arg;
const char **capabilities;
uint32_t *sid = NULL, i, wd = 0;
LY_ERR lyrc;
@ -954,11 +886,7 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
/* <rpc> open */
count = asprintf(&buf, "<rpc xmlns=\"%s\" message-id=\"%" PRIu64 "\"%s>",
NC_NS_BASE, session->opts.client.msgid + 1, attrs ? attrs : "");
if (count == -1) {
ERRMEM;
ret = NC_MSG_ERROR;
goto cleanup;
}
NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
nc_write_clb((void *)&arg, buf, count, 0);
free(buf);
@ -969,7 +897,7 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
}
/* rpc data */
if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, op, LYD_XML, LYD_PRINT_SHRINK)) {
if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, op, LYD_XML, LYD_PRINT_SHRINK | LYD_PRINT_KEEPEMPTYCONT)) {
ret = NC_MSG_ERROR;
goto cleanup;
}
@ -991,28 +919,22 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
rpc_envp = va_arg(ap, struct lyd_node_opaq *);
reply = va_arg(ap, struct nc_server_reply *);
if (!rpc_envp) {
/* can be NULL if replying with a malformed-message error */
nc_write_clb((void *)&arg, "<rpc-reply xmlns=\"" NC_NS_BASE "\">", 18 + strlen(NC_NS_BASE) + 2, 0);
assert(reply->type == NC_RPL_ERROR);
if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, ((struct nc_server_reply_error *)reply)->err, LYD_XML,
LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)) {
ret = NC_MSG_ERROR;
goto cleanup;
}
nc_write_clb((void *)&arg, "</rpc-reply>", 12, 0);
break;
}
/* build a rpc-reply opaque node that can be simply printed */
if (rpc_envp) {
if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns,
&reply_envp)) {
ERRINT;
ret = NC_MSG_ERROR;
goto cleanup;
}
} else {
if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, NULL, NC_NS_BASE,
&reply_envp)) {
ERRINT;
ret = NC_MSG_ERROR;
goto cleanup;
}
}
switch (reply->type) {
case NC_RPL_OK:
@ -1060,7 +982,9 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
}
/* temporary */
if (rpc_envp) {
((struct lyd_node_opaq *)reply_envp)->attr = rpc_envp->attr;
}
/* print */
lyrc = lyd_print_clb(nc_write_xmlclb, (void *)&arg, reply_envp, LYD_XML, LYD_PRINT_SHRINK | wd);
@ -1117,11 +1041,7 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
sid = va_arg(ap, uint32_t *);
count = asprintf(&buf, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
if (count == -1) {
ERRMEM;
ret = NC_MSG_ERROR;
goto cleanup;
}
NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
nc_write_clb((void *)&arg, buf, count, 0);
free(buf);
for (i = 0; capabilities[i]; i++) {
@ -1130,12 +1050,8 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
nc_write_clb((void *)&arg, "</capability>", 13, 0);
}
if (sid) {
count = asprintf(&buf, "</capabilities><session-id>%u</session-id></hello>", *sid);
if (count == -1) {
ERRMEM;
ret = NC_MSG_ERROR;
goto cleanup;
}
count = asprintf(&buf, "</capabilities><session-id>%" PRIu32 "</session-id></hello>", *sid);
NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
nc_write_clb((void *)&arg, buf, count, 0);
free(buf);
} else {
@ -1179,7 +1095,7 @@ nc_realloc(void *ptr, size_t size)
}
struct passwd *
nc_getpwuid(uid_t uid, struct passwd *pwd_buf, char **buf, size_t *buf_size)
nc_getpw(uid_t uid, const char *username, struct passwd *pwd_buf, char **buf, size_t *buf_size)
{
struct passwd *pwd = NULL;
long sys_size;
@ -1197,16 +1113,21 @@ nc_getpwuid(uid_t uid, struct passwd *pwd_buf, char **buf, size_t *buf_size)
/* allocate some buffer */
*buf = nc_realloc(*buf, *buf_size);
if (!*buf) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!*buf, NULL);
if (username) {
ret = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd);
} else {
ret = getpwuid_r(uid, pwd_buf, *buf, *buf_size, &pwd);
}
} while (ret && (ret == ERANGE));
if (ret) {
ERR(NULL, "Retrieving UID \"%lu\" passwd entry failed (%s).", (unsigned long int)uid, strerror(ret));
if (username) {
ERR(NULL, "Retrieving username \"%s\" passwd entry failed (%s).", username, strerror(ret));
} else {
ERR(NULL, "Retrieving UID \"%lu\" passwd entry failed (%s).", (unsigned long)uid, strerror(ret));
}
}
return pwd;
}

113
src/log.c
View file

@ -1,9 +1,11 @@
/**
* \file log.c
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2 - log functions
* @file log.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 - log functions
*
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -12,31 +14,38 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE /* pthread_rwlock_t */
#include "log_p.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <libyang/libyang.h>
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
#include <libssh/libssh.h>
#endif
#endif /* NC_ENABLED_SSH_TLS */
#include "compat.h"
#include "libnetconf.h"
#include "config.h"
#include "log.h"
#include "session_p.h"
#define NC_MSG_SIZE 256
/**
* @brief libnetconf verbose level variable
*/
volatile uint8_t verbose_level = 0;
ATOMIC_T verbose_level = 0;
void (*depr_print_clb)(NC_VERB_LEVEL level, const char *msg);
void (*print_clb)(const struct nc_session *session, NC_VERB_LEVEL level, const char *msg);
API void
nc_verbosity(NC_VERB_LEVEL level)
{
verbose_level = level;
ATOMIC_STORE_RELAXED(verbose_level, level);
ly_log_level((LY_LOG_LEVEL)level);
}
@ -51,98 +60,100 @@ struct {
{NC_VERB_DEBUG_LOWLVL, "[DBL]"}
};
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
static void
nc_libssh_log_cb(int priority, const char *UNUSED(function), const char *buffer, void *UNUSED(userdata))
{
static char last_msg[NC_MSG_SIZE] = {0};
static struct timespec last_print = {0}, cur_time;
/* check for repeated messages and do not print them */
if (!strncmp(last_msg, buffer, NC_MSG_SIZE - 1)) {
nc_realtime_get(&cur_time);
if (last_print.tv_sec && (nc_time_diff(&cur_time, &last_print) < 1000)) {
/* print another repeated message only after 1s */
return;
}
last_print = cur_time;
} else {
/* store the last message */
strncpy(last_msg, buffer, NC_MSG_SIZE - 1);
memset(&last_print, 0, sizeof last_print);
}
/* print the message */
nc_log_printf(NULL, priority, "SSH: %s", buffer);
}
API void
nc_libssh_thread_verbosity(int level)
{
ssh_set_log_callback(nc_libssh_log_cb);
ssh_set_log_level(level);
}
#endif
#endif /* NC_ENABLED_SSH_TLS */
static void
prv_vprintf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, va_list args)
nc_log_vprintf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, va_list args)
{
#define PRV_MSG_INIT_SIZE 256
va_list args2;
char *prv_msg;
char *msg;
void *mem;
int req_len;
prv_msg = malloc(PRV_MSG_INIT_SIZE);
if (!prv_msg) {
msg = malloc(NC_MSG_SIZE);
if (!msg) {
return;
}
va_copy(args2, args);
req_len = vsnprintf(prv_msg, PRV_MSG_INIT_SIZE - 1, format, args);
req_len = vsnprintf(msg, NC_MSG_SIZE - 1, format, args);
if (req_len == -1) {
goto cleanup;
} else if (req_len >= PRV_MSG_INIT_SIZE - 1) {
} else if (req_len >= NC_MSG_SIZE - 1) {
/* the length is not enough */
++req_len;
mem = realloc(prv_msg, req_len);
mem = realloc(msg, req_len);
if (!mem) {
goto cleanup;
}
prv_msg = mem;
msg = mem;
/* now print the full message */
req_len = vsnprintf(prv_msg, req_len, format, args2);
req_len = vsnprintf(msg, req_len, format, args2);
if (req_len == -1) {
goto cleanup;
}
}
if (print_clb) {
print_clb(session, level, prv_msg);
} else if (depr_print_clb) {
depr_print_clb(level, prv_msg);
print_clb(session, level, msg);
} else if (session && session->id) {
fprintf(stderr, "Session %u %s: %s\n", session->id, verb[level].label, prv_msg);
fprintf(stderr, "Session %" PRIu32 " %s: %s\n", session->id, verb[level].label, msg);
} else {
fprintf(stderr, "%s: %s\n", verb[level].label, prv_msg);
fprintf(stderr, "%s: %s\n", verb[level].label, msg);
}
cleanup:
free(prv_msg);
#undef PRV_MSG_INIT_SIZE
free(msg);
}
void
prv_printf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, ...)
nc_log_printf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, ...)
{
va_list ap;
va_start(ap, format);
prv_vprintf(session, level, format, ap);
nc_log_vprintf(session, level, format, ap);
va_end(ap);
}
static void
nc_ly_log_clb(LY_LOG_LEVEL lvl, const char *msg, const char *UNUSED(path))
{
if (print_clb) {
print_clb(NULL, (NC_VERB_LEVEL)lvl, msg);
} else if (depr_print_clb) {
depr_print_clb((NC_VERB_LEVEL)lvl, msg);
}
}
API void
nc_set_print_clb(void (*clb)(NC_VERB_LEVEL, const char *))
{
print_clb = NULL;
depr_print_clb = clb;
ly_set_log_clb(nc_ly_log_clb, 1);
}
API void
nc_set_print_clb_session(void (*clb)(const struct nc_session *, NC_VERB_LEVEL, const char *))
{
print_clb = clb;
depr_print_clb = NULL;
ly_set_log_clb(nc_ly_log_clb, 1);
}

View file

@ -3,6 +3,7 @@
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2 logger
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -49,7 +50,7 @@ typedef enum NC_VERB_LEVEL {
*/
void nc_verbosity(NC_VERB_LEVEL level);
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Set libssh verbosity level.
@ -68,20 +69,13 @@ void nc_verbosity(NC_VERB_LEVEL level);
*/
void nc_libssh_thread_verbosity(int level);
#endif
/**
* @brief Deprecated, use ::nc_set_print_clb_session() instead.
*
* @param[in] clb Callback that is called for every message.
*/
void nc_set_print_clb(void (*clb)(NC_VERB_LEVEL, const char *));
#endif /* NC_ENABLED_SSH_TLS */
/**
* @brief Set libnetconf print callback.
*
* This callback is set for libnetconf2 and also libyang that is used internally. libyang
* callback can be set explicitly, but must be done so after calling this function.
* The callback is not set per-session, it is a global resource. It might be called with
* a NULL session parameter.
*
* @param[in] clb Callback that is called for every message.
*/

View file

@ -1,9 +1,11 @@
/**
* @file log.h
* @file log_p.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 logger
*
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -15,8 +17,9 @@
#ifndef NC_LOG_PRIVATE_H_
#define NC_LOG_PRIVATE_H_
#include <stdint.h>
#include <stdarg.h>
#include "compat.h"
#include "log.h"
/*
@ -30,25 +33,67 @@
* @param[in] level Verbose level
* @param[in] format Formatting string
*/
void prv_printf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, ...);
void nc_log_printf(const struct nc_session *session, NC_VERB_LEVEL level, const char *format, ...);
/**
* @brief Verbose level variable
*/
extern volatile uint8_t verbose_level;
extern ATOMIC_T verbose_level;
/*
* Verbose printing macros
*/
#define ERR(session, format, args ...) prv_printf(session,NC_VERB_ERROR,format,##args)
#define WRN(session, format, args ...) if(verbose_level>=NC_VERB_WARNING){prv_printf(session,NC_VERB_WARNING,format,##args);}
#define VRB(session, format, args ...) if(verbose_level>=NC_VERB_VERBOSE){prv_printf(session,NC_VERB_VERBOSE,format,##args);}
#define DBG(session, format, args ...) if(verbose_level>=NC_VERB_DEBUG){prv_printf(session,NC_VERB_DEBUG,format,##args);}
#define DBL(session, format, args ...) if(verbose_level>=NC_VERB_DEBUG_LOWLVL){prv_printf(session,NC_VERB_DEBUG_LOWLVL,format,##args);}
#define ERR(session, ...) nc_log_printf(session, NC_VERB_ERROR, __VA_ARGS__)
#define WRN(session, ...) if(ATOMIC_LOAD_RELAXED(verbose_level)>=NC_VERB_WARNING){nc_log_printf(session, NC_VERB_WARNING, __VA_ARGS__);}
#define VRB(session, ...) if(ATOMIC_LOAD_RELAXED(verbose_level)>=NC_VERB_VERBOSE){nc_log_printf(session, NC_VERB_VERBOSE, __VA_ARGS__);}
#define DBG(session, ...) if(ATOMIC_LOAD_RELAXED(verbose_level)>=NC_VERB_DEBUG){nc_log_printf(session, NC_VERB_DEBUG, __VA_ARGS__);}
#define DBL(session, ...) if(ATOMIC_LOAD_RELAXED(verbose_level)>=NC_VERB_DEBUG_LOWLVL){nc_log_printf(session, NC_VERB_DEBUG_LOWLVL, __VA_ARGS__);}
#define ERRMEM ERR(NULL, "%s: memory reallocation failed (%s:%d).", __func__, __FILE__, __LINE__)
#define ERRARG(arg) ERR(NULL, "%s: invalid argument (%s).", __func__, arg)
#define ERRINIT ERR(NULL, "%s: libnetconf2 not initialized.", __func__)
#define ERRINITSRV ERR(NULL, "%s: server not initialized.", __func__)
#define ERRINT ERR(NULL, "%s: internal error (%s:%d).", __func__, __FILE__, __LINE__)
#define ERRARG(session, ARG) ERR(session, "Invalid argument %s (%s()).", #ARG, __func__)
#define NC_CHECK_SRV_INIT_RET(RET) if (!ATOMIC_LOAD_RELAXED(server_opts.new_session_id)) {ERRINITSRV; return (RET);}
#define NC_CHECK_ERRMEM_RET(COND, RET) if ((COND)) {ERRMEM; return (RET);}
#define NC_CHECK_ERRMEM_GOTO(COND, RET, GOTO) if ((COND)) {ERRMEM; RET; goto GOTO;}
#define GETMACRO1(_1, NAME, ...) NAME
#define GETMACRO2(_1, _2, NAME, ...) NAME
#define GETMACRO3(_1, _2, _3, NAME, ...) NAME
#define GETMACRO4(_1, _2, _3, _4, NAME, ...) NAME
#define GETMACRO5(_1, _2, _3, _4, _5, NAME, ...) NAME
#define GETMACRO6(_1, _2, _3, _4, _5, _6, NAME, ...) NAME
#define GETMACRO7(_1, _2, _3, _4, _5, _6, _7, NAME, ...) NAME
#define GETMACRO8(_1, _2, _3, _4, _5, _6, _7, _8, NAME, ...) NAME
#define NC_CHECK_ARG_RET1(session, ARG, RETVAL) if (!(ARG)) {ERRARG(session, ARG);return RETVAL;}
#define NC_CHECK_ARG_RET2(session, ARG1, ARG2, RETVAL)\
NC_CHECK_ARG_RET1(session, ARG1, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG2, RETVAL)
#define NC_CHECK_ARG_RET3(session, ARG1, ARG2, ARG3, RETVAL)\
NC_CHECK_ARG_RET2(session, ARG1, ARG2, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG3, RETVAL)
#define NC_CHECK_ARG_RET4(session, ARG1, ARG2, ARG3, ARG4, RETVAL)\
NC_CHECK_ARG_RET3(session, ARG1, ARG2, ARG3, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG4, RETVAL)
#define NC_CHECK_ARG_RET5(session, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL)\
NC_CHECK_ARG_RET4(session, ARG1, ARG2, ARG3, ARG4, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG5, RETVAL)
#define NC_CHECK_ARG_RET6(session, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, RETVAL)\
NC_CHECK_ARG_RET5(session, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG6, RETVAL)
#define NC_CHECK_ARG_RET7(session, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, RETVAL)\
NC_CHECK_ARG_RET6(session, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, RETVAL);\
NC_CHECK_ARG_RET1(session, ARG7, RETVAL)
/**
* @brief Function's parameters checking macro
*
* @param session Session that is logged.
* @param ... Parameters of the function to check. The last parameter is the value that is returned on error.
*/
#define NC_CHECK_ARG_RET(session, ...) GETMACRO8(__VA_ARGS__, NC_CHECK_ARG_RET7, NC_CHECK_ARG_RET6, NC_CHECK_ARG_RET5,\
NC_CHECK_ARG_RET4, NC_CHECK_ARG_RET3, NC_CHECK_ARG_RET2, NC_CHECK_ARG_RET1, DUMMY) (session, __VA_ARGS__)
#endif /* NC_LOG_PRIVATE_H_ */

View file

@ -1,8 +1,9 @@
/**
* \file messages.c
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2 - NETCONF messages functions
* @file messages.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2 - NETCONF messages functions
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -12,15 +13,21 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE /* pthread_rwlock_t, strdup */
#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include "libnetconf.h"
#include "compat.h"
#include "config.h"
#include "log_p.h"
#include "messages_client.h"
#include "messages_p.h"
#include "netconf.h"
const char *rpcedit_dfltop2str[] = {NULL, "merge", "replace", "none"};
const char *rpcedit_testopt2str[] = {NULL, "test-then-set", "set", "test-only"};
@ -29,10 +36,7 @@ const char *rpcedit_erropt2str[] = {NULL, "stop-on-error", "continue-on-error",
API NC_RPC_TYPE
nc_rpc_get_type(const struct nc_rpc *rpc)
{
if (!rpc) {
ERRARG("rpc");
return 0;
}
NC_CHECK_ARG_RET(NULL, rpc, 0);
return rpc->type;
}
@ -42,16 +46,14 @@ nc_rpc_act_generic(const struct lyd_node *data, NC_PARAMTYPE paramtype)
{
struct nc_rpc_act_generic *rpc;
if (!data || data->next || (data->prev != data)) {
ERRARG("data");
NC_CHECK_ARG_RET(NULL, data, NULL);
if (data->next || (data->prev != data)) {
ERR(NULL, "nc_rpc_act_generic missing data");
return NULL;
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_ACT_GENERIC;
rpc->has_data = 1;
@ -73,16 +75,10 @@ nc_rpc_act_generic_xml(const char *xml_str, NC_PARAMTYPE paramtype)
{
struct nc_rpc_act_generic *rpc;
if (!xml_str) {
ERRARG("xml_str");
return NULL;
}
NC_CHECK_ARG_RET(NULL, xml_str, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_ACT_GENERIC;
rpc->has_data = 0;
@ -101,10 +97,7 @@ nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_WD_MODE wd_mode, NC
{
struct nc_rpc_getconfig *rpc;
if (!source) {
ERRARG("source");
return NULL;
}
NC_CHECK_ARG_RET(NULL, source, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is neither an XML subtree nor an XPath expression (invalid first char '%c').", filter[0]);
@ -112,10 +105,7 @@ nc_rpc_getconfig(NC_DATASTORE source, const char *filter, NC_WD_MODE wd_mode, NC
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_GETCONFIG;
rpc->source = source;
@ -136,13 +126,7 @@ nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TEST
{
struct nc_rpc_edit *rpc;
if (!target) {
ERRARG("target");
return NULL;
} else if (!edit_content) {
ERRARG("edit_content");
return NULL;
}
NC_CHECK_ARG_RET(NULL, target, edit_content, NULL);
if (edit_content[0] && (edit_content[0] != '<') && !isalpha(edit_content[0])) {
ERR(NULL, "<edit-config> content is neither a URL nor an XML config (invalid first char '%c').", edit_content[0]);
@ -150,10 +134,7 @@ nc_rpc_edit(NC_DATASTORE target, NC_RPC_EDIT_DFLTOP default_op, NC_RPC_EDIT_TEST
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_EDIT;
rpc->target = target;
@ -176,13 +157,7 @@ nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const
{
struct nc_rpc_copy *rpc;
if (!target) {
ERRARG("target");
return NULL;
} else if (!source) {
ERRARG("source");
return NULL;
}
NC_CHECK_ARG_RET(NULL, target, source, NULL);
if (url_or_config_src && url_or_config_src[0] && (url_or_config_src[0] != '<') && !isalpha(url_or_config_src[0])) {
ERR(NULL, "<copy-config> source is neither a URL nor an XML config (invalid first char '%c').", url_or_config_src[0]);
@ -190,10 +165,7 @@ nc_rpc_copy(NC_DATASTORE target, const char *url_trg, NC_DATASTORE source, const
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_COPY;
rpc->target = target;
@ -219,16 +191,10 @@ nc_rpc_delete(NC_DATASTORE target, const char *url, NC_PARAMTYPE paramtype)
{
struct nc_rpc_delete *rpc;
if (!target) {
ERRARG("target");
return NULL;
}
NC_CHECK_ARG_RET(NULL, target, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_DELETE;
rpc->target = target;
@ -247,16 +213,10 @@ nc_rpc_lock(NC_DATASTORE target)
{
struct nc_rpc_lock *rpc;
if (!target) {
ERRARG("target");
return NULL;
}
NC_CHECK_ARG_RET(NULL, target, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_LOCK;
rpc->target = target;
@ -269,16 +229,10 @@ nc_rpc_unlock(NC_DATASTORE target)
{
struct nc_rpc_lock *rpc;
if (!target) {
ERRARG("target");
return NULL;
}
NC_CHECK_ARG_RET(NULL, target, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_UNLOCK;
rpc->target = target;
@ -297,10 +251,7 @@ nc_rpc_get(const char *filter, NC_WD_MODE wd_mode, NC_PARAMTYPE paramtype)
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_GET;
if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
@ -319,16 +270,10 @@ nc_rpc_kill(uint32_t session_id)
{
struct nc_rpc_kill *rpc;
if (!session_id) {
ERRARG("session_id");
return NULL;
}
NC_CHECK_ARG_RET(NULL, session_id, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_KILL;
rpc->sid = session_id;
@ -343,10 +288,7 @@ nc_rpc_commit(int confirmed, uint32_t confirm_timeout, const char *persist, cons
struct nc_rpc_commit *rpc;
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_COMMIT;
rpc->confirmed = confirmed;
@ -372,10 +314,7 @@ nc_rpc_discard(void)
struct nc_rpc *rpc;
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_DISCARD;
@ -388,10 +327,7 @@ nc_rpc_cancel(const char *persist_id, NC_PARAMTYPE paramtype)
struct nc_rpc_cancel *rpc;
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_CANCEL;
if (persist_id && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
@ -409,10 +345,7 @@ nc_rpc_validate(NC_DATASTORE source, const char *url_or_config, NC_PARAMTYPE par
{
struct nc_rpc_validate *rpc;
if (!source) {
ERRARG("source");
return NULL;
}
NC_CHECK_ARG_RET(NULL, source, NULL);
if (url_or_config && url_or_config[0] && (url_or_config[0] != '<') && !isalpha(url_or_config[0])) {
ERR(NULL, "<validate> source is neither a URL nor an XML config (invalid first char '%c').", url_or_config[0]);
@ -420,10 +353,7 @@ nc_rpc_validate(NC_DATASTORE source, const char *url_or_config, NC_PARAMTYPE par
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_VALIDATE;
rpc->source = source;
@ -442,16 +372,10 @@ nc_rpc_getschema(const char *identifier, const char *version, const char *format
{
struct nc_rpc_getschema *rpc;
if (!identifier) {
ERRARG("identifier");
return NULL;
}
NC_CHECK_ARG_RET(NULL, identifier, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_GETSCHEMA;
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
@ -486,10 +410,7 @@ nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_SUBSCRIBE;
if (stream_name && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
@ -525,19 +446,15 @@ nc_rpc_getdata(const char *datastore, const char *filter, const char *config_fil
struct nc_rpc_getdata *rpc = NULL;
int i;
NC_CHECK_ARG_RET(NULL, datastore, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is neither an XML subtree nor an XPath expression (invalid first char '%c').", filter[0]);
return NULL;
} else if (!datastore) {
ERRARG("datastore");
return NULL;
}
rpc = calloc(1, sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1);
rpc->type = NC_RPC_GETDATA;
@ -558,16 +475,10 @@ nc_rpc_getdata(const char *datastore, const char *filter, const char *config_fil
}
if (origin_filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
rpc->origin_filter = malloc(origin_filter_count * sizeof *rpc->origin_filter);
if (!rpc->origin_filter) {
ERRMEM;
goto error;
}
NC_CHECK_ERRMEM_GOTO(!rpc->origin_filter, , error);
for (i = 0; i < origin_filter_count; ++i) {
rpc->origin_filter[i] = strdup(origin_filter[i]);
if (!rpc->origin_filter[i]) {
ERRMEM;
goto error;
}
NC_CHECK_ERRMEM_GOTO(!rpc->origin_filter[i], , error);
++rpc->origin_filter_count;
}
} else {
@ -591,13 +502,7 @@ nc_rpc_editdata(const char *datastore, NC_RPC_EDIT_DFLTOP default_op, const char
{
struct nc_rpc_editdata *rpc;
if (!datastore) {
ERRARG("datastore");
return NULL;
} else if (!edit_content) {
ERRARG("edit_content");
return NULL;
}
NC_CHECK_ARG_RET(NULL, datastore, edit_content, NULL);
if (edit_content[0] && (edit_content[0] != '<') && !isalpha(edit_content[0])) {
ERR(NULL, "<edit-data> content is neither a URL nor an XML config (invalid first char '%c').", edit_content[0]);
@ -605,10 +510,7 @@ nc_rpc_editdata(const char *datastore, NC_RPC_EDIT_DFLTOP default_op, const char
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_EDITDATA;
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
@ -633,10 +535,7 @@ nc_rpc_establishsub(const char *filter, const char *stream_name, const char *sta
{
struct nc_rpc_establishsub *rpc;
if (!stream_name) {
ERRARG("stream_name");
return NULL;
}
NC_CHECK_ARG_RET(NULL, stream_name, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -645,10 +544,7 @@ nc_rpc_establishsub(const char *filter, const char *stream_name, const char *sta
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_ESTABLISHSUB;
if (filter && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
@ -686,10 +582,7 @@ nc_rpc_modifysub(uint32_t id, const char *filter, const char *stop_time, NC_PARA
{
struct nc_rpc_modifysub *rpc;
if (!id) {
ERRARG("id");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -698,10 +591,7 @@ nc_rpc_modifysub(uint32_t id, const char *filter, const char *stop_time, NC_PARA
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_MODIFYSUB;
rpc->id = id;
@ -725,16 +615,10 @@ nc_rpc_deletesub(uint32_t id)
{
struct nc_rpc_deletesub *rpc;
if (!id) {
ERRARG("id");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_DELETESUB;
rpc->id = id;
@ -747,16 +631,10 @@ nc_rpc_killsub(uint32_t id)
{
struct nc_rpc_killsub *rpc;
if (!id) {
ERRARG("id");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_KILLSUB;
rpc->id = id;
@ -770,13 +648,7 @@ nc_rpc_establishpush_periodic(const char *datastore, const char *filter, const c
{
struct nc_rpc_establishpush *rpc;
if (!datastore) {
ERRARG("datastore");
return NULL;
} else if (!period) {
ERRARG("period");
return NULL;
}
NC_CHECK_ARG_RET(NULL, datastore, period, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -785,10 +657,7 @@ nc_rpc_establishpush_periodic(const char *datastore, const char *filter, const c
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_ESTABLISHPUSH;
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
@ -829,11 +698,9 @@ nc_rpc_establishpush_onchange(const char *datastore, const char *filter, const c
{
struct nc_rpc_establishpush *rpc;
uint32_t i;
void *tmp;
if (!datastore) {
ERRARG("datastore");
return NULL;
}
NC_CHECK_ARG_RET(NULL, datastore, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -842,10 +709,7 @@ nc_rpc_establishpush_onchange(const char *datastore, const char *filter, const c
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_ESTABLISHPUSH;
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
@ -874,7 +738,15 @@ nc_rpc_establishpush_onchange(const char *datastore, const char *filter, const c
if (excluded_change && (paramtype == NC_PARAMTYPE_DUP_AND_FREE)) {
rpc->excluded_change = NULL;
for (i = 0; excluded_change[i]; ++i) {
rpc->excluded_change = realloc(rpc->excluded_change, (i + 2) * sizeof *rpc->excluded_change);
tmp = realloc(rpc->excluded_change, (i + 2) * sizeof *rpc->excluded_change);
if (!tmp) {
/* in case we fail to alloc, just free all the excluded changes, but return the rpc anyways */
ERRMEM;
free(rpc->excluded_change);
rpc->excluded_change = NULL;
break;
}
rpc->excluded_change = tmp;
rpc->excluded_change[i] = strdup(excluded_change[i]);
rpc->excluded_change[i + 1] = NULL;
}
@ -892,13 +764,7 @@ nc_rpc_modifypush_periodic(uint32_t id, const char *datastore, const char *filte
{
struct nc_rpc_modifypush *rpc;
if (!id) {
ERRARG("id");
return NULL;
} else if (!datastore) {
ERRARG("datastore");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, datastore, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -907,10 +773,7 @@ nc_rpc_modifypush_periodic(uint32_t id, const char *datastore, const char *filte
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_MODIFYPUSH;
rpc->id = id;
@ -947,13 +810,7 @@ nc_rpc_modifypush_onchange(uint32_t id, const char *datastore, const char *filte
{
struct nc_rpc_modifypush *rpc;
if (!id) {
ERRARG("id");
return NULL;
} else if (!datastore) {
ERRARG("datastore");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, datastore, NULL);
if (filter && filter[0] && (filter[0] != '<') && (filter[0] != '/') && !isalpha(filter[0])) {
ERR(NULL, "Filter is not an XML subtree, an XPath expression, not a filter reference (invalid first char '%c').",
@ -962,10 +819,7 @@ nc_rpc_modifypush_onchange(uint32_t id, const char *datastore, const char *filte
}
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_MODIFYPUSH;
rpc->id = id;
@ -996,16 +850,10 @@ nc_rpc_resyncsub(uint32_t id)
{
struct nc_rpc_resyncsub *rpc;
if (!id) {
ERRARG("id");
return NULL;
}
NC_CHECK_ARG_RET(NULL, id, NULL);
rpc = malloc(sizeof *rpc);
if (!rpc) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!rpc, NULL);
rpc->type = NC_RPC_RESYNCSUB;
rpc->id = id;

View file

@ -4,6 +4,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2's public functions and structures of NETCONF client messages.
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").

View file

@ -3,6 +3,7 @@
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2's private functions and structures of NETCONF messages.
*
* @copyright
* Copyright (c) 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -15,10 +16,13 @@
#ifndef NC_MESSAGES_P_H_
#define NC_MESSAGES_P_H_
#include <stdint.h>
#include <libyang/libyang.h>
#include "messages_client.h"
#include "messages_server.h"
#include "netconf.h"
extern const char *rpcedit_dfltop2str[];
extern const char *rpcedit_testopt2str[];
@ -65,6 +69,7 @@ struct nc_rpc {
struct nc_rpc_act_generic {
NC_RPC_TYPE type; /**< NC_RPC_ACT_GENERIC */
int has_data; /**< 1 for content.data, 0 for content.xml_str */
union {
struct lyd_node *data; /**< parsed RPC data */
char *xml_str; /**< raw XML string */
@ -220,6 +225,7 @@ struct nc_rpc_establishpush {
char *stop;
char *encoding;
int periodic;
union {
struct {
uint32_t period;
@ -241,6 +247,7 @@ struct nc_rpc_modifypush {
char *filter; /**< XML subtree (starts with '<'), an XPath (starts with '/'), or reference (start with alpha) */
char *stop;
int periodic;
union {
struct {
uint32_t period;

View file

@ -1,8 +1,9 @@
/**
* \file messages_server.c
* \author Michal Vasko <mvasko@cesnet.cz>
* \brief libnetconf2 - server NETCONF messages functions
* @file messages_server.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 - server NETCONF messages functions
*
* @copyright
* Copyright (c) 2015 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -12,6 +13,8 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE /* pthread_rwlock_t, strdup */
#include <ctype.h>
#include <inttypes.h>
#include <stdarg.h>
@ -21,10 +24,11 @@
#include <libyang/libyang.h>
#include "compat.h"
#include "libnetconf.h"
#include "session_server.h"
extern struct nc_server_opts server_opts;
#include "config.h"
#include "log_p.h"
#include "messages_p.h"
#include "messages_server.h"
#include "netconf.h"
API struct nc_server_reply *
nc_server_reply_ok(void)
@ -32,10 +36,7 @@ nc_server_reply_ok(void)
struct nc_server_reply *ret;
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!ret, NULL);
ret->type = NC_RPL_OK;
return ret;
@ -46,16 +47,15 @@ nc_server_reply_data(struct lyd_node *data, NC_WD_MODE wd, NC_PARAMTYPE paramtyp
{
struct nc_server_reply_data *ret;
if (!data) {
ERRARG("data");
NC_CHECK_ARG_RET(NULL, data, NULL);
if (!(data->schema->nodetype & (LYS_RPC | LYS_ACTION))) {
ERR(NULL, "nc_server_reply_data bad data");
return NULL;
}
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!ret, NULL);
ret->type = NC_RPL_DATA;
ret->wd = wd;
@ -80,16 +80,10 @@ nc_server_reply_err(struct lyd_node *err)
{
struct nc_server_reply_error *ret;
if (!err) {
ERRARG("err");
return NULL;
}
NC_CHECK_ARG_RET(NULL, err, NULL);
ret = malloc(sizeof *ret);
if (!ret) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!ret, NULL);
ret->type = NC_RPL_ERROR;
ret->err = err;
@ -101,11 +95,10 @@ nc_server_reply_add_err(struct nc_server_reply *reply, struct lyd_node *err)
{
struct nc_server_reply_error *err_rpl;
if (!reply || (reply->type != NC_RPL_ERROR)) {
ERRARG("reply");
return -1;
} else if (!err) {
ERRARG("err");
NC_CHECK_ARG_RET(NULL, reply, err, -1);
if (reply->type != NC_RPL_ERROR) {
ERR(NULL, "nc_server_reply_add_err() bad reply type");
return -1;
}
@ -119,8 +112,10 @@ nc_server_reply_get_last_err(const struct nc_server_reply *reply)
{
struct nc_server_reply_error *err_rpl;
if (!reply || (reply->type != NC_RPL_ERROR)) {
ERRARG("reply");
NC_CHECK_ARG_RET(NULL, reply, NULL);
if (reply->type != NC_RPL_ERROR) {
ERR(NULL, "nc_server_reply_get_last_err() bad reply type");
return NULL;
}
@ -270,10 +265,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
const char *arg1, *arg2;
uint32_t sid;
if (!tag) {
ERRARG("tag");
return NULL;
}
NC_CHECK_ARG_RET(NULL, tag, NULL);
/* rpc-error */
if (lyd_new_opaq2(NULL, ctx, "rpc-error", NULL, NULL, NC_NS_BASE, &err)) {
@ -291,7 +283,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
case NC_ERR_OP_NOT_SUPPORTED:
type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
ERRARG(NULL, "type");
goto fail;
}
break;
@ -304,7 +296,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
case NC_ERR_UNKNOWN_ATTR:
type = (NC_ERR_TYPE)va_arg(ap, int);
if (type == NC_ERR_TYPE_TRAN) {
ERRARG("type");
ERRARG(NULL, "type");
goto fail;
}
break;
@ -313,14 +305,14 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
case NC_ERR_UNKNOWN_ELEM:
type = (NC_ERR_TYPE)va_arg(ap, int);
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
ERRARG(NULL, "type");
goto fail;
}
break;
case NC_ERR_UNKNOWN_NS:
type = (NC_ERR_TYPE)va_arg(ap, int);
if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) {
ERRARG("type");
ERRARG(NULL, "type");
goto fail;
}
break;
@ -334,7 +326,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
case NC_ERR_OP_FAILED:
type = (NC_ERR_TYPE)va_arg(ap, int);
if (type == NC_ERR_TYPE_TRAN) {
ERRARG("type");
ERRARG(NULL, "type");
goto fail;
}
break;
@ -342,7 +334,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
type = NC_ERR_TYPE_RPC;
break;
default:
ERRARG("tag");
ERRARG(NULL, "tag");
goto fail;
}
if (lyd_new_opaq2(err, NULL, "error-type", nc_err_type2str(type), NULL, NC_NS_BASE, NULL)) {
@ -419,7 +411,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
nc_err_set_msg(err, "A message could not be handled because it failed to be parsed correctly.", "en");
break;
default:
ERRARG("tag");
ERRARG(NULL, "tag");
goto fail;
}
@ -466,7 +458,7 @@ nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...)
nc_err_set_sid(err, sid);
break;
default:
ERRARG("tag");
ERRARG(NULL, "tag");
goto fail;
}
@ -484,10 +476,7 @@ nc_err_get_type(const struct lyd_node *err)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return 0;
}
NC_CHECK_ARG_RET(NULL, err, 0);
lyd_find_sibling_opaq_next(lyd_child(err), "error-type", &match);
if (match) {
@ -502,10 +491,7 @@ nc_err_get_tag(const struct lyd_node *err)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return 0;
}
NC_CHECK_ARG_RET(NULL, err, 0);
lyd_find_sibling_opaq_next(lyd_child(err), "error-tag", &match);
if (match) {
@ -520,13 +506,7 @@ nc_err_set_app_tag(struct lyd_node *err, const char *error_app_tag)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return -1;
} else if (!error_app_tag) {
ERRARG("error_app_tag");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, error_app_tag, -1);
/* remove previous node */
lyd_find_sibling_opaq_next(lyd_child(err), "error-app-tag", &match);
@ -546,10 +526,7 @@ nc_err_get_app_tag(const struct lyd_node *err)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return NULL;
}
NC_CHECK_ARG_RET(NULL, err, NULL);
lyd_find_sibling_opaq_next(lyd_child(err), "error-app-tag", &match);
if (match) {
@ -564,13 +541,7 @@ nc_err_set_path(struct lyd_node *err, const char *error_path)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return -1;
} else if (!error_path) {
ERRARG("error_path");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, error_path, -1);
/* remove previous node */
lyd_find_sibling_opaq_next(lyd_child(err), "error-path", &match);
@ -590,10 +561,7 @@ nc_err_get_path(const struct lyd_node *err)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return 0;
}
NC_CHECK_ARG_RET(NULL, err, NULL);
lyd_find_sibling_opaq_next(lyd_child(err), "error-path", &match);
if (match) {
@ -609,20 +577,15 @@ nc_err_set_msg(struct lyd_node *err, const char *error_message, const char *lang
struct lyd_node *match;
struct lyd_attr *attr;
if (!err) {
ERRARG("err");
return -1;
} else if (!error_message) {
ERRARG("error_message");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, error_message, -1);
/* remove previous message */
lyd_find_sibling_opaq_next(lyd_child(err), "error-message", &match);
if (match) {
lyd_free_tree(match);
/* Change the value of error-message and keep order of elements to comply with appendix-B in RFC 6241. */
lydict_remove(LYD_CTX(err), ((struct lyd_node_opaq *)match)->value);
lydict_insert(LYD_CTX(err), error_message, 0, &(((struct lyd_node_opaq *)match)->value));
return 0;
}
if (lyd_new_opaq2(err, NULL, "error-message", error_message, NULL, NC_NS_BASE, &match)) {
return -1;
}
@ -639,10 +602,7 @@ nc_err_get_msg(const struct lyd_node *err)
{
struct lyd_node *match;
if (!err) {
ERRARG("err");
return NULL;
}
NC_CHECK_ARG_RET(NULL, err, NULL);
lyd_find_sibling_opaq_next(lyd_child(err), "error-message", &match);
if (match) {
@ -658,10 +618,7 @@ nc_err_set_sid(struct lyd_node *err, uint32_t session_id)
struct lyd_node *match, *info;
char buf[22];
if (!err) {
ERRARG("err");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, -1);
/* find error-info */
lyd_find_sibling_opaq_next(lyd_child(err), "error-info", &info);
@ -688,13 +645,7 @@ nc_err_add_bad_attr(struct lyd_node *err, const char *attr_name)
{
struct lyd_node *info;
if (!err) {
ERRARG("err");
return -1;
} else if (!attr_name) {
ERRARG("attr_name");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, attr_name, -1);
/* find error-info */
lyd_find_sibling_opaq_next(lyd_child(err), "error-info", &info);
@ -714,13 +665,7 @@ nc_err_add_bad_elem(struct lyd_node *err, const char *elem_name)
{
struct lyd_node *info;
if (!err) {
ERRARG("err");
return -1;
} else if (!elem_name) {
ERRARG("elem_name");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, elem_name, -1);
/* find error-info */
lyd_find_sibling_opaq_next(lyd_child(err), "error-info", &info);
@ -740,13 +685,7 @@ nc_err_add_bad_ns(struct lyd_node *err, const char *ns_name)
{
struct lyd_node *info;
if (!err) {
ERRARG("err");
return -1;
} else if (!ns_name) {
ERRARG("ns_name");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, ns_name, -1);
/* find error-info */
lyd_find_sibling_opaq_next(lyd_child(err), "error-info", &info);
@ -766,13 +705,7 @@ nc_err_add_info_other(struct lyd_node *err, struct lyd_node *other)
{
struct lyd_node *info;
if (!err) {
ERRARG("err");
return -1;
} else if (!other) {
ERRARG("other");
return -1;
}
NC_CHECK_ARG_RET(NULL, err, other, -1);
/* find error-info */
lyd_find_sibling_opaq_next(lyd_child(err), "error-info", &info);
@ -837,13 +770,7 @@ nc_server_notif_new(struct lyd_node *event, char *eventtime, NC_PARAMTYPE paramt
struct lyd_node *elem;
int found;
if (!event) {
ERRARG("event");
return NULL;
} else if (!eventtime) {
ERRARG("eventtime");
return NULL;
}
NC_CHECK_ARG_RET(NULL, event, eventtime, NULL);
/* check that there is a notification */
found = 0;
@ -855,11 +782,13 @@ nc_server_notif_new(struct lyd_node *event, char *eventtime, NC_PARAMTYPE paramt
LYD_TREE_DFS_END(event, elem);
}
if (!found) {
ERRARG("event");
ERRARG(NULL, "event");
return NULL;
}
ntf = malloc(sizeof *ntf);
NC_CHECK_ERRMEM_RET(!ntf, NULL);
if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) {
ntf->eventtime = strdup(eventtime);
if (lyd_dup_single(event, NULL, LYD_DUP_RECURSIVE, &ntf->ntf)) {
@ -892,10 +821,7 @@ nc_server_notif_free(struct nc_server_notif *notif)
API const char *
nc_server_notif_get_time(const struct nc_server_notif *notif)
{
if (!notif) {
ERRARG("notif");
return NULL;
}
NC_CHECK_ARG_RET(NULL, notif, NULL);
return notif->eventtime;
}

View file

@ -3,6 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2's functions and structures of server NETCONF messages.
*
* @copyright
* Copyright (c) 2015-2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -19,9 +20,11 @@
extern "C" {
#endif
#include <libyang/libyang.h>
#include <stdarg.h>
#include <stdint.h>
#include <libyang/libyang.h>
#include "netconf.h"
#include "session.h"
@ -95,7 +98,7 @@ struct nc_server_reply *nc_server_reply_ok(void);
/**
* @brief Create a DATA rpc-reply object.
*
* @param[in] data Reply data tree. This tree must be valid according to
* @param[in] data Reply data tree pointing to the RPC/action itself. This tree must be valid according to
* the RPC output of the RPC this is a reply to.
* @param[in] wd with-default mode if applicable
* @param[in] paramtype Determines how the @p data parameter is treated.
@ -106,7 +109,7 @@ struct nc_server_reply *nc_server_reply_data(struct lyd_node *data, NC_WD_MODE w
/**
* @brief Create an ERROR rpc-reply object.
*
* @param[in] err Errors as opaque data node tree. It will be freed with the returned object.
* @param[in] err Errors created by nc_err(). It will be freed with the returned object.
* @return rpc-reply object, NULL on error.
*/
struct nc_server_reply *nc_server_reply_err(struct lyd_node *err);
@ -115,7 +118,7 @@ struct nc_server_reply *nc_server_reply_err(struct lyd_node *err);
* @brief Add another error opaque data node tree to an ERROR rpc-reply object.
*
* @param[in] reply ERROR reply to add to.
* @param[in] err Error as opaque data node tree. It will be freed with the returned object.
* @param[in] err Error created by nc_err(). It will be freed with the returned object.
* @return 0 on success, -1 on errror.
*/
int nc_server_reply_add_err(struct nc_server_reply *reply, struct lyd_node *err);
@ -165,6 +168,7 @@ const struct lyd_node *nc_server_reply_get_last_err(const struct nc_server_reply
* - #NC_ERR_DATA_MISSING
* - #NC_ERR_MALFORMED_MSG
* - no additional arguments
* @param[in] ... Additional arguments depending on the @p tag used.
* @return Opaque data node tree representing the error.
*/
struct lyd_node *nc_err(const struct ly_ctx *ctx, NC_ERR tag, ...);
@ -293,11 +297,11 @@ void nc_server_reply_free(struct nc_server_reply *reply);
* @brief Create Event Notification object to be sent to the subscribed client(s).
*
* @param[in] event Notification data tree (valid as LYD_OPT_NOTIF) from libyang. The tree is directly used in created
* object, so the caller is supposed to not free the tree on its own, but only via freeng the created object.
* object, so the caller is supposed to not free the tree on its own, but only via freeing the created object.
* @param[in] eventtime YANG dateTime format value of the time when the event was generated by the event source.
* Caller can use nc_timespec2datetime() to create the value from a timespec value.
* @param[in] paramtype How to further manage data parameters.
* @return Newly created structure of the Event Notification object to be sent to the clients via nc_server_send_notif()
* @return Newly created structure of the Event Notification object to be sent to the clients via nc_server_notif_send()
* and freed using nc_server_notif_free().
*/
struct nc_server_notif *nc_server_notif_new(struct lyd_node *event, char *eventtime, NC_PARAMTYPE paramtype);
@ -306,8 +310,8 @@ struct nc_server_notif *nc_server_notif_new(struct lyd_node *event, char *eventt
* @brief Send NETCONF Event Notification via the session.
*
* @param[in] session NETCONF session where the Event Notification will be written.
* @param[in] notif NETCOFN Notification object to send via specified session. Object can be created by
* nc_notif_new() function.
* @param[in] notif NETCONF Notification object to send via specified session. Object can be created by
* nc_server_notif_new() function.
* @param[in] timeout Timeout for writing in milliseconds. Use negative value for infinite
* waiting and 0 for return if data cannot be sent immediately.
* @return #NC_MSG_NOTIF on success,

View file

@ -3,6 +3,7 @@
* @author Radek Krejci <rkrejci@cesnet.cz>
* @brief libnetconf2's general public functions and structures definitions.
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -19,8 +20,6 @@
extern "C" {
#endif
#include <time.h>
/**
* @addtogroup misc
* @{

4707
src/server_config.c Normal file

File diff suppressed because it is too large Load diff

1442
src/server_config.h Normal file

File diff suppressed because it is too large Load diff

445
src/server_config_ks.c Normal file
View file

@ -0,0 +1,445 @@
/**
* @file server_config_ks.c
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 keystore configuration functions
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include "compat.h"
#include "log_p.h"
#include "server_config_p.h"
#include "session_p.h"
/**
* @brief Get the pointer to an asymmetric key structure based on node's location in the YANG data.
*
* @param[in] node Node from which the asymmetric key containing this node is derived.
* @param[out] askey Asymmetric key containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_asymmetric_key(const struct lyd_node *node, struct nc_asymmetric_key **askey)
{
uint16_t i;
const char *askey_name;
struct nc_keystore *ks;
const char *node_name = LYD_NAME(node);
assert(node && askey);
while (node) {
if (!strcmp(LYD_NAME(node), "asymmetric-key")) {
break;
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in an asymmetric-key subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
askey_name = lyd_get_value(node);
ks = &server_opts.keystore;
for (i = 0; i < ks->asym_key_count; i++) {
if (!strcmp(ks->asym_keys[i].name, askey_name)) {
*askey = &ks->asym_keys[i];
return 0;
}
}
ERR(NULL, "Asymmetric key \"%s\" was not found.", askey_name);
return 1;
}
/**
* @brief Get the pointer to a certificate structure based on node's location in the YANG data.
*
* @param[in] node Node from which the certificate containing this node is derived.
* @param[out] cert Certificate containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_certificate(const struct lyd_node *node, struct nc_certificate **cert)
{
uint16_t i;
const char *cert_name;
struct nc_asymmetric_key *askey;
const char *node_name = LYD_NAME(node);
assert(node && cert);
if (nc_server_config_get_asymmetric_key(node, &askey)) {
return 1;
}
while (node) {
if (!strcmp(LYD_NAME(node), "certificate")) {
break;
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in a certificate subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
cert_name = lyd_get_value(node);
for (i = 0; i < askey->cert_count; i++) {
if (!strcmp(askey->certs[i].name, cert_name)) {
*cert = &askey->certs[i];
return 0;
}
}
ERR(NULL, "Certificate \"%s\" was not found.", cert_name);
return 1;
}
static void
nc_server_config_ks_del_asymmetric_key_cert(struct nc_asymmetric_key *key, struct nc_certificate *cert)
{
free(cert->name);
free(cert->data);
key->cert_count--;
if (!key->cert_count) {
free(key->certs);
key->certs = NULL;
} else if (cert != &key->certs[key->cert_count]) {
memcpy(cert, &key->certs[key->cert_count], sizeof *key->certs);
}
}
static void
nc_server_config_ks_del_asymmetric_key(struct nc_asymmetric_key *key)
{
uint16_t i, cert_count;
struct nc_keystore *ks = &server_opts.keystore;
free(key->name);
free(key->pubkey_data);
free(key->privkey_data);
cert_count = key->cert_count;
for (i = 0; i < cert_count; i++) {
nc_server_config_ks_del_asymmetric_key_cert(key, &key->certs[i]);
}
ks->asym_key_count--;
if (!ks->asym_key_count) {
free(ks->asym_keys);
ks->asym_keys = NULL;
} else if (key != &ks->asym_keys[ks->asym_key_count]) {
memcpy(key, &ks->asym_keys[ks->asym_key_count], sizeof *ks->asym_keys);
}
}
static int
nc_server_config_ks_asymmetric_keys(const struct lyd_node *node, enum nc_operation op)
{
struct nc_keystore *ks = &server_opts.keystore;
uint16_t i, asym_key_count;
(void) node;
if (op == NC_OP_DELETE) {
asym_key_count = ks->asym_key_count;
for (i = 0; i < asym_key_count; i++) {
nc_server_config_ks_del_asymmetric_key(&ks->asym_keys[i]);
}
}
return 0;
}
int
nc_server_config_ks_keystore(const struct lyd_node *node, enum nc_operation op)
{
(void) node;
if (op == NC_OP_DELETE) {
nc_server_config_ks_asymmetric_keys(NULL, NC_OP_DELETE);
}
return 0;
}
static int
nc_server_config_ks_create_asymmetric_key(const struct lyd_node *node)
{
struct nc_keystore *ks = &server_opts.keystore;
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&ks->asym_keys, sizeof *ks->asym_keys, &ks->asym_key_count);
}
static int
nc_server_config_ks_asymmetric_key(const struct lyd_node *node, enum nc_operation op)
{
int ret = 0;
struct nc_asymmetric_key *key;
assert(!strcmp(LYD_NAME(node), "asymmetric-key"));
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
ret = nc_server_config_ks_create_asymmetric_key(node);
} else {
if (nc_server_config_get_asymmetric_key(node, &key)) {
ret = 1;
goto cleanup;
}
nc_server_config_ks_del_asymmetric_key(key);
}
cleanup:
return ret;
}
static int
nc_server_config_ks_public_key_format(const struct lyd_node *node, enum nc_operation op)
{
struct nc_asymmetric_key *key;
const char *format;
(void) op;
assert(!strcmp(LYD_NAME(node), "public-key-format"));
if (nc_server_config_get_asymmetric_key(node, &key)) {
return 1;
}
format = ((struct lyd_node_term *)node)->value.ident->name;
if (!strcmp(format, "ssh-public-key-format")) {
key->pubkey_type = NC_PUBKEY_FORMAT_SSH;
} else if (!strcmp(format, "subject-public-key-info-format")) {
key->pubkey_type = NC_PUBKEY_FORMAT_X509;
} else {
ERR(NULL, "Public key format (%s) not supported.", format);
}
return 0;
}
static int
nc_server_config_ks_public_key(const struct lyd_node *node, enum nc_operation op)
{
struct nc_asymmetric_key *key;
(void) op;
assert(!strcmp(LYD_NAME(node), "public-key"));
if (nc_server_config_get_asymmetric_key(node, &key)) {
return 1;
}
/* replace the pubkey */
free(key->pubkey_data);
key->pubkey_data = strdup(lyd_get_value(node));
NC_CHECK_ERRMEM_RET(!key->pubkey_data, 1);
return 0;
}
static int
nc_server_config_ks_private_key_format(const struct lyd_node *node, enum nc_operation op)
{
struct nc_asymmetric_key *key;
const char *format;
enum nc_privkey_format privkey_type;
(void) op;
assert(!strcmp(LYD_NAME(node), "private-key-format"));
if (nc_server_config_get_asymmetric_key(node, &key)) {
return 1;
}
format = ((struct lyd_node_term *)node)->value.ident->name;
if (!format) {
return 1;
}
privkey_type = nc_server_config_get_private_key_type(format);
if (privkey_type == NC_PRIVKEY_FORMAT_UNKNOWN) {
return 1;
}
key->privkey_type = privkey_type;
return 0;
}
static int
nc_server_config_ks_cleartext_private_key(const struct lyd_node *node, enum nc_operation op)
{
struct nc_asymmetric_key *key;
assert(!strcmp(LYD_NAME(node), "cleartext-private-key"));
if (nc_server_config_get_asymmetric_key(node, &key)) {
return 1;
}
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
/* replace the privkey */
free(key->privkey_data);
key->privkey_data = strdup(lyd_get_value(node));
NC_CHECK_ERRMEM_RET(!key->privkey_data, 1);
} else if (op == NC_OP_DELETE) {
free(key->privkey_data);
key->privkey_data = NULL;
}
return 0;
}
static int
nc_server_config_ks_create_certificate(const struct lyd_node *node, struct nc_asymmetric_key *key)
{
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&key->certs, sizeof *key->certs, &key->cert_count);
}
static int
nc_server_config_ks_certificate(const struct lyd_node *node, enum nc_operation op)
{
int ret = 0;
struct nc_asymmetric_key *key;
struct nc_certificate *cert;
assert(!strcmp(LYD_NAME(node), "certificate"));
if (nc_server_config_get_asymmetric_key(node, &key)) {
ret = 1;
goto cleanup;
}
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
ret = nc_server_config_ks_create_certificate(node, key);
} else {
if (nc_server_config_get_certificate(node, &cert)) {
ret = 1;
goto cleanup;
}
nc_server_config_ks_del_asymmetric_key_cert(key, cert);
}
cleanup:
return ret;
}
static int
nc_server_config_ks_cert_data(const struct lyd_node *node, enum nc_operation op)
{
struct nc_certificate *cert;
assert(!strcmp(LYD_NAME(node), "cert-data"));
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
if (nc_server_config_get_certificate(node, &cert)) {
return 1;
}
/* replace the cert data */
free(cert->data);
cert->data = strdup(lyd_get_value(node));
NC_CHECK_ERRMEM_RET(!cert->data, 1);
}
return 0;
}
int
nc_server_config_parse_keystore(const struct lyd_node *node, enum nc_operation op)
{
const char *name = LYD_NAME(node);
int ret = 0;
if (!strcmp(name, "keystore")) {
ret = nc_server_config_ks_keystore(node, op);
} else if (!strcmp(name, "asymmetric-keys")) {
ret = nc_server_config_ks_asymmetric_keys(node, op);
} else if (!strcmp(name, "asymmetric-key")) {
ret = nc_server_config_ks_asymmetric_key(node, op);
} else if (!strcmp(name, "public-key-format")) {
ret = nc_server_config_ks_public_key_format(node, op);
} else if (!strcmp(name, "public-key")) {
ret = nc_server_config_ks_public_key(node, op);
} else if (!strcmp(name, "private-key-format")) {
ret = nc_server_config_ks_private_key_format(node, op);
} else if (!strcmp(name, "cleartext-private-key")) {
ret = nc_server_config_ks_cleartext_private_key(node, op);
} else if (!strcmp(name, "certificate")) {
ret = nc_server_config_ks_certificate(node, op);
} else if (!strcmp(name, "cert-data")) {
ret = nc_server_config_ks_cert_data(node, op);
}
if (ret) {
ERR(NULL, "Configuring (%s) failed.", name);
return 1;
}
return 0;
}
int
nc_server_config_fill_keystore(const struct lyd_node *data, enum nc_operation op)
{
int ret = 0;
uint32_t prev_lo;
struct lyd_node *tree;
/* silently search for nodes, some of them may not be present */
prev_lo = ly_log_options(0);
ret = lyd_find_path(data, "/ietf-keystore:keystore", 0, &tree);
if (ret || (tree->flags & LYD_DEFAULT)) {
/* not found */
ret = 0;
goto cleanup;
}
if (nc_server_config_parse_tree(tree, op, NC_MODULE_KEYSTORE)) {
ret = 1;
goto cleanup;
}
cleanup:
/* reset the logging options back to what they were */
ly_log_options(prev_lo);
return ret;
}

169
src/server_config_p.h Normal file
View file

@ -0,0 +1,169 @@
/**
* @file server_config_p.h
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 server configuration
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef NC_CONFIG_SERVER_P_H_
#define NC_CONFIG_SERVER_P_H_
#include <libyang/libyang.h>
#include <stdint.h>
#include <stdlib.h>
#include "session_p.h"
/**
* Enumeration of ietf-netconf-server's modules/trees (top-level containers)
*/
typedef enum {
NC_MODULE_NETCONF_SERVER,
NC_MODULE_KEYSTORE,
NC_MODULE_TRUSTSTORE,
NC_MODULE_LIBNETCONF2_NETCONF_SERVER
} NC_MODULE;
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Get private key type from YANG identity stored in a string.
*
* @param[in] format Value of the YANG identityref.
* @return Private key format on success, NC_PRIVKEY_FORMAT_UNKNOWN otherwise.
*/
enum nc_privkey_format nc_server_config_get_private_key_type(const char *format);
#endif /* NC_ENABLED_SSH_TLS */
/**
* @brief Compares the nth-parent name.
*
* @param[in] node Node of which nth-parent to compare.
* @param[in] parent_count Count of parents.
* @param[in] parent_name Expected name of the parent.
* @return 1 if the name matches, 0 otherwise.
*/
int equal_parent_name(const struct lyd_node *node, uint16_t parent_count, const char *parent_name);
/**
* @brief Generic realloc function for arrays of structures representing YANG lists whose first member is the key (char *)
*
* @param[in] key_value Value of the key, which will be assigned to the first member of the given struct.
* @param[in] size Size of a member of the array.
* @param[in,out] ptr Pointer to the beginning of the given array, which will be reallocated.
* @param[in,out] count Count of members in the array, incremented at the end.
* @return 0 on success, 1 on error.
*/
int nc_server_config_realloc(const char *key_value, void **ptr, size_t size, uint16_t *count);
/**
* @brief Recursively parse the given tree and apply it's data to the server's configuration.
*
* @param[in] node YANG data tree.
* @param[in] parent_op Operation of the parent.
* @param[in] module Module for which to parse the data - either ietf-netconf-server, ietf-keystore or ietf-truststore
* @return 0 on success, 1 on error.
*/
int nc_server_config_parse_tree(const struct lyd_node *node, enum nc_operation parent_op, NC_MODULE module);
/**
* @brief Configures the listen subtree in the ietf-netconf-server module.
*
* @param[in] node Listen YANG data node.
* @param[in] op Operation to be done on the subtree. Only does something if the operation is NC_OP_DELETE.
* @return 0 on success, 1 on error.
*/
int nc_server_config_listen(const struct lyd_node *node, enum nc_operation op);
/**
* @brief Configures the Call Home subtree in the ietf-netconf-server module.
*
* @param[in] node call-home YANG data node.
* @param[in] op Operation to be done on the subtree. Only does something if the operation is NC_OP_DELETE.
* @return 0 on success, 1 on error.
*/
int nc_server_config_ch(const struct lyd_node *node, enum nc_operation op);
#ifdef NC_ENABLED_SSH_TLS
/** KEYSTORE **/
/**
* @brief Checks if keystore tree is present in the data and if yes, tries to apply it's data.
*
* @param[in] data YANG data tree.
* @param[in] op Operation saying what to do with the top-level node.
* @return 0 either if keystore is not present or if it is and application was successful, 1 on error.
*/
int nc_server_config_fill_keystore(const struct lyd_node *data, enum nc_operation op);
/**
* @brief Parse the given node, which belongs to the ietf-keystore subtree, and apply it's data to the server's configuration.
*
* @param[in] node YANG data node.
* @param[in] op Operation saying what to do with the node.
* @return 0 on success, 1 on error.
*/
int nc_server_config_parse_keystore(const struct lyd_node *node, enum nc_operation op);
/**
* @brief Configures the keystore subtree in the ietf-keystore module.
*
* @param[in] node Keystore YANG data node.
* @param[in] op Operation to be done on the subtree. Only does something if the operation is NC_OP_DELETE.
* @return 0.
*/
int nc_server_config_ks_keystore(const struct lyd_node *node, enum nc_operation op);
/** TRUSTSTORE **/
/**
* @brief Checks if truststore tree is present in the data and if yes, tries to apply it's data.
*
* @param[in] data YANG data tree.
* @param[in] op Operation saying what to do with the top-level node.
* @return 0 either if truststore is not present or if it is and application was successful, 1 on error.
*/
int nc_server_config_fill_truststore(const struct lyd_node *data, enum nc_operation op);
/**
* @brief Parse the given node, which belongs to the ietf-truststore subtree, and apply it's data to the server's configuration.
*
* @param[in] node YANG data node.
* @param[in] op Operation saying what to do with the node.
* @return 0 on success, 1 on error.
*/
int nc_server_config_parse_truststore(const struct lyd_node *node, enum nc_operation op);
/**
* @brief Configures the truststore subtree in the ietf-truststore module.
*
* @param[in] node Truststore YANG data node.
* @param[in] op Operation to be done on the subtree. Only does something if the operation is NC_OP_DELETE.
* @return 0.
*/
int nc_server_config_ts_truststore(const struct lyd_node *node, enum nc_operation op);
/** LIBNETCONF2-NETCONF-SERVER **/
/**
* @brief Configures the ln2-netconf-server subtree in the libnetconf2-netconf-server module.
*
* @param[in] node Optional ln2-netconf-server YANG data node.
* @param[in] op Operation to be done on the subtree. Only does something if the operation is NC_OP_DELETE.
* @return 0 on success, 1 on error.
*/
int nc_server_config_ln2_netconf_server(const struct lyd_node *node, enum nc_operation op);
#endif /* NC_ENABLED_SSH_TLS */
#endif /* NC_CONFIG_SERVER_P_H_ */

600
src/server_config_ts.c Normal file
View file

@ -0,0 +1,600 @@
/**
* @file server_config_ts.c
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 truststore configuration functions
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include "compat.h"
#include "log_p.h"
#include "server_config_p.h"
#include "session_p.h"
/**
* @brief Get the pointer to a certificate bag structure based on node's location in the YANG data.
*
* @param[in] node Node from which the certificate bag containing this node is derived.
* @param[out] cbag Certificate bag containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_certificate_bag(const struct lyd_node *node, struct nc_certificate_bag **cbag)
{
uint16_t i;
const char *cbag_name;
struct nc_truststore *ts;
const char *node_name = LYD_NAME(node);
assert(node && cbag);
while (node) {
if (!strcmp(LYD_NAME(node), "certificate-bag")) {
break;
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in a certificate-bag subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
cbag_name = lyd_get_value(node);
ts = &server_opts.truststore;
for (i = 0; i < ts->cert_bag_count; i++) {
if (!strcmp(ts->cert_bags[i].name, cbag_name)) {
*cbag = &ts->cert_bags[i];
return 0;
}
}
ERR(NULL, "Certificate bag \"%s\" was not found.", cbag_name);
return 1;
}
/**
* @brief Get the pointer to a certificate structure based on node's location in the YANG data.
*
* @param[in] node Node from which the certificate containing this node is derived.
* @param[out] cert Certificate containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_certificate(const struct lyd_node *node, struct nc_certificate **cert)
{
uint16_t i;
const char *cert_name;
struct nc_certificate_bag *cbag;
const char *node_name = LYD_NAME(node);
assert(node && cert);
if (nc_server_config_get_certificate_bag(node, &cbag)) {
return 1;
}
while (node) {
if (!strcmp(LYD_NAME(node), "certificate")) {
break;
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in a certificate subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
cert_name = lyd_get_value(node);
for (i = 0; i < cbag->cert_count; i++) {
if (!strcmp(cbag->certs[i].name, cert_name)) {
*cert = &cbag->certs[i];
return 0;
}
}
ERR(NULL, "Certificate \"%s\" was not found.", cert_name);
return 1;
}
/**
* @brief Get the pointer to a public key bag structure based on node's location in the YANG data.
*
* @param[in] node Node from which the public key bag containing this node is derived.
* @param[out] pbag Public key bag containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_public_key_bag(const struct lyd_node *node, struct nc_public_key_bag **pbag)
{
uint16_t i;
const char *pbag_name;
struct nc_truststore *ts;
const char *node_name = LYD_NAME(node);
assert(node && pbag);
while (node) {
if (!strcmp(LYD_NAME(node), "public-key-bag")) {
break;
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in a public-key-bag subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
pbag_name = lyd_get_value(node);
ts = &server_opts.truststore;
for (i = 0; i < ts->pub_bag_count; i++) {
if (!strcmp(ts->pub_bags[i].name, pbag_name)) {
*pbag = &ts->pub_bags[i];
return 0;
}
}
ERR(NULL, "Public key bag \"%s\" was not found.", pbag_name);
return 1;
}
/**
* @brief Get the pointer to a public key structure based on node's location in the YANG data.
*
* @param[in] node Node from which the public key containing this node is derived.
* @param[out] pkey Public key containing the node.
* @return 0 on success, 1 on error.
*/
static int
nc_server_config_get_public_key(const struct lyd_node *node, struct nc_public_key **pkey)
{
uint16_t i;
const char *pkey_name;
struct nc_public_key_bag *pbag;
const char *node_name = LYD_NAME(node);
assert(node && pkey);
if (nc_server_config_get_public_key_bag(node, &pbag)) {
return 1;
}
while (node) {
if (!strcmp(LYD_NAME(node), "public-key")) {
if (lyd_child(node)) {
/* check if it's not the leaf public-key, only case about the list */
break;
}
}
node = lyd_parent(node);
}
if (!node) {
ERR(NULL, "Node \"%s\" is not contained in a public-key subtree.", node_name);
return 1;
}
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
pkey_name = lyd_get_value(node);
for (i = 0; i < pbag->pubkey_count; i++) {
if (!strcmp(pbag->pubkeys[i].name, pkey_name)) {
*pkey = &pbag->pubkeys[i];
return 0;
}
}
ERR(NULL, "Public key \"%s\" was not found.", pkey_name);
return 1;
}
static void
nc_server_config_ts_del_certificate(struct nc_certificate_bag *cbag, struct nc_certificate *cert)
{
free(cert->name);
free(cert->data);
cbag->cert_count--;
if (!cbag->cert_count) {
free(cbag->certs);
cbag->certs = NULL;
} else if (cert != &cbag->certs[cbag->cert_count]) {
memcpy(cert, &cbag->certs[cbag->cert_count], sizeof *cbag->certs);
}
}
static void
nc_server_config_ts_del_public_key(struct nc_public_key_bag *pbag, struct nc_public_key *pkey)
{
free(pkey->name);
free(pkey->data);
pbag->pubkey_count--;
if (!pbag->pubkey_count) {
free(pbag->pubkeys);
pbag->pubkeys = NULL;
} else if (pkey != &pbag->pubkeys[pbag->pubkey_count]) {
memcpy(pkey, &pbag->pubkeys[pbag->pubkey_count], sizeof *pbag->pubkeys);
}
}
static void
nc_server_config_ts_del_certificate_bag(struct nc_certificate_bag *cbag)
{
uint16_t i, cert_count;
struct nc_truststore *ts = &server_opts.truststore;
free(cbag->name);
cert_count = cbag->cert_count;
for (i = 0; i < cert_count; i++) {
nc_server_config_ts_del_certificate(cbag, &cbag->certs[i]);
}
ts->cert_bag_count--;
if (!ts->cert_bag_count) {
free(ts->cert_bags);
ts->cert_bags = NULL;
} else if (cbag != &ts->cert_bags[ts->cert_bag_count]) {
memcpy(cbag, &ts->cert_bags[ts->cert_bag_count], sizeof *ts->cert_bags);
}
}
static void
nc_server_config_ts_del_public_key_bag(struct nc_public_key_bag *pbag)
{
uint16_t i, pubkey_count;
struct nc_truststore *ts = &server_opts.truststore;
free(pbag->name);
pubkey_count = pbag->pubkey_count;
for (i = 0; i < pubkey_count; i++) {
nc_server_config_ts_del_public_key(pbag, &pbag->pubkeys[i]);
}
ts->pub_bag_count--;
if (!ts->pub_bag_count) {
free(ts->pub_bags);
ts->pub_bags = NULL;
} else if (pbag != &ts->pub_bags[ts->pub_bag_count]) {
memcpy(pbag, &ts->pub_bags[ts->pub_bag_count], sizeof *ts->pub_bags);
}
}
static int
nc_server_config_ts_certificate_bags(const struct lyd_node *node, enum nc_operation op)
{
uint16_t i, cert_bag_count;
struct nc_truststore *ts = &server_opts.truststore;
(void) node;
if (op == NC_OP_DELETE) {
cert_bag_count = ts->cert_bag_count;
for (i = 0; i < cert_bag_count; i++) {
nc_server_config_ts_del_certificate_bag(&ts->cert_bags[i]);
}
}
return 0;
}
static int
nc_server_config_ts_public_key_bags(const struct lyd_node *node, enum nc_operation op)
{
uint16_t i, pub_bag_count;
struct nc_truststore *ts = &server_opts.truststore;
(void) node;
if (op == NC_OP_DELETE) {
pub_bag_count = ts->pub_bag_count;
for (i = 0; i < pub_bag_count; i++) {
nc_server_config_ts_del_public_key_bag(&ts->pub_bags[i]);
}
}
return 0;
}
int
nc_server_config_ts_truststore(const struct lyd_node *node, enum nc_operation op)
{
(void) node;
if (op == NC_OP_DELETE) {
nc_server_config_ts_certificate_bags(NULL, NC_OP_DELETE);
nc_server_config_ts_public_key_bags(NULL, NC_OP_DELETE);
}
return 0;
}
static int
nc_server_config_ts_create_certificate_bag(const struct lyd_node *node)
{
struct nc_truststore *ts = &server_opts.truststore;
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&ts->cert_bags, sizeof *ts->cert_bags, &ts->cert_bag_count);
}
static int
nc_server_config_ts_certificate_bag(const struct lyd_node *node, enum nc_operation op)
{
struct nc_certificate_bag *bag;
assert(!strcmp(LYD_NAME(node), "certificate-bag"));
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
if (nc_server_config_ts_create_certificate_bag(node)) {
return 1;
}
} else {
if (nc_server_config_get_certificate_bag(node, &bag)) {
return 1;
}
nc_server_config_ts_del_certificate_bag(bag);
}
return 0;
}
static int
nc_server_config_ts_create_certificate(const struct lyd_node *node, struct nc_certificate_bag *bag)
{
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&bag->certs, sizeof *bag->certs, &bag->cert_count);
}
static int
nc_server_config_ts_certificate(const struct lyd_node *node, enum nc_operation op)
{
struct nc_certificate_bag *bag;
struct nc_certificate *cert;
assert(!strcmp(LYD_NAME(node), "certificate"));
if (nc_server_config_get_certificate_bag(node, &bag)) {
return 1;
}
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
if (nc_server_config_ts_create_certificate(node, bag)) {
return 1;
}
} else {
if (nc_server_config_get_certificate(node, &cert)) {
return 1;
}
nc_server_config_ts_del_certificate(bag, cert);
}
return 0;
}
static int
nc_server_config_ts_cert_data(const struct lyd_node *node, enum nc_operation op)
{
struct nc_certificate *cert;
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
if (nc_server_config_get_certificate(node, &cert)) {
return 1;
}
free(cert->data);
cert->data = strdup(lyd_get_value(node));
NC_CHECK_ERRMEM_RET(!cert->data, 1);
}
return 0;
}
static int
nc_server_config_ts_create_public_key_bag(const struct lyd_node *node)
{
struct nc_truststore *ts = &server_opts.truststore;
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&ts->pub_bags, sizeof *ts->pub_bags, &ts->pub_bag_count);
}
static int
nc_server_config_ts_public_key_bag(const struct lyd_node *node, enum nc_operation op)
{
struct nc_public_key_bag *pbag;
assert(!strcmp(LYD_NAME(node), "public-key-bag"));
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
if (nc_server_config_ts_create_public_key_bag(node)) {
return 1;
}
} else {
if (nc_server_config_get_public_key_bag(node, &pbag)) {
return 1;
}
nc_server_config_ts_del_public_key_bag(pbag);
}
return 0;
}
static int
nc_server_config_ts_create_public_key(const struct lyd_node *node, struct nc_public_key_bag *bag)
{
node = lyd_child(node);
assert(!strcmp(LYD_NAME(node), "name"));
return nc_server_config_realloc(lyd_get_value(node), (void **)&bag->pubkeys, sizeof *bag->pubkeys, &bag->pubkey_count);
}
static int
nc_server_config_ts_public_key(const struct lyd_node *node, enum nc_operation op)
{
int ret = 0;
struct nc_public_key_bag *bag;
struct nc_public_key *pkey;
if (nc_server_config_get_public_key_bag(node, &bag)) {
ret = 1;
goto cleanup;
}
if (equal_parent_name(node, 1, "public-key-bag")) {
/* public-key list */
if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
ret = nc_server_config_ts_create_public_key(node, bag);
if (ret) {
goto cleanup;
}
} else {
if (nc_server_config_get_public_key(node, &pkey)) {
ret = 1;
goto cleanup;
}
nc_server_config_ts_del_public_key(bag, pkey);
}
} else {
/* public-key leaf */
if (nc_server_config_get_public_key(node, &pkey)) {
ret = 1;
goto cleanup;
}
/* replace the public key */
free(pkey->data);
pkey->data = strdup(lyd_get_value(node));
NC_CHECK_ERRMEM_GOTO(!pkey->data, ret = 1, cleanup);
}
cleanup:
return ret;
}
static int
nc_server_config_ts_public_key_format(const struct lyd_node *node, enum nc_operation op)
{
const char *format;
struct nc_public_key *pkey;
(void) op;
if (nc_server_config_get_public_key(node, &pkey)) {
return 1;
}
format = ((struct lyd_node_term *)node)->value.ident->name;
if (!strcmp(format, "ssh-public-key-format")) {
pkey->type = NC_PUBKEY_FORMAT_SSH;
} else if (!strcmp(format, "subject-public-key-info-format")) {
pkey->type = NC_PUBKEY_FORMAT_X509;
} else {
ERR(NULL, "Public key format (%s) not supported.", format);
}
return 0;
}
int
nc_server_config_parse_truststore(const struct lyd_node *node, enum nc_operation op)
{
const char *name = LYD_NAME(node);
int ret = 0;
if (!strcmp(name, "truststore")) {
ret = nc_server_config_ts_truststore(node, op);
} else if (!strcmp(name, "certificate-bags")) {
ret = nc_server_config_ts_certificate_bags(node, op);
} else if (!strcmp(name, "certificate-bag")) {
ret = nc_server_config_ts_certificate_bag(node, op);
} else if (!strcmp(name, "certificate")) {
ret = nc_server_config_ts_certificate(node, op);
} else if (!strcmp(name, "cert-data")) {
ret = nc_server_config_ts_cert_data(node, op);
} else if (!strcmp(name, "public-key-bags")) {
ret = nc_server_config_ts_public_key_bags(node, op);
} else if (!strcmp(name, "public-key-bag")) {
ret = nc_server_config_ts_public_key_bag(node, op);
} else if (!strcmp(name, "public-key")) {
ret = nc_server_config_ts_public_key(node, op);
} else if (!strcmp(name, "public-key-format")) {
ret = nc_server_config_ts_public_key_format(node, op);
}
if (ret) {
ERR(NULL, "Configuring (%s) failed.", name);
return 1;
}
return 0;
}
int
nc_server_config_fill_truststore(const struct lyd_node *data, enum nc_operation op)
{
int ret = 0;
uint32_t prev_lo;
struct lyd_node *tree;
/* silently search for nodes, some of them may not be present */
prev_lo = ly_log_options(0);
ret = lyd_find_path(data, "/ietf-truststore:truststore", 0, &tree);
if (ret || (tree->flags & LYD_DEFAULT)) {
/* not found */
ret = 0;
goto cleanup;
}
if (nc_server_config_parse_tree(tree, op, NC_MODULE_TRUSTSTORE)) {
ret = 1;
goto cleanup;
}
cleanup:
/* reset the logging options back to what they were */
ly_log_options(prev_lo);
return ret;
}

1366
src/server_config_util.c Normal file

File diff suppressed because it is too large Load diff

169
src/server_config_util.h Normal file
View file

@ -0,0 +1,169 @@
/**
* @file server_config_util.h
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 server configuration utlities header
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef NC_SERVER_CONFIG_UTIL_H_
#define NC_SERVER_CONFIG_UTIL_H_
#include <libyang/libyang.h>
#include "session_p.h"
#ifdef NC_ENABLED_SSH_TLS
/* private key's pkcs8 header */
#define NC_PKCS8_PRIVKEY_HEADER "-----BEGIN PRIVATE KEY-----\n"
/* private key's pkcs8 footer */
#define NC_PKCS8_PRIVKEY_FOOTER "\n-----END PRIVATE KEY-----\n"
/* private key's openssh header */
#define NC_OPENSSH_PRIVKEY_HEADER "-----BEGIN OPENSSH PRIVATE KEY-----\n"
/* private key's openssh footer */
#define NC_OPENSSH_PRIVKEY_FOOTER "\n-----END OPENSSH PRIVATE KEY-----\n"
/* private key's pkcs1 rsa header */
#define NC_PKCS1_RSA_PRIVKEY_HEADER "-----BEGIN RSA PRIVATE KEY-----\n"
/* private key's pkcs1 rsa footer */
#define NC_PKCS1_RSA_PRIVKEY_FOOTER "\n-----END RSA PRIVATE KEY-----\n"
/* private key's sec1 ec header */
#define NC_SEC1_EC_PRIVKEY_HEADER "-----BEGIN EC PRIVATE KEY-----\n"
/* private key's sec1 ec footer */
#define NC_SEC1_EC_PRIVKEY_FOOTER "\n-----END EC PRIVATE KEY-----\n"
/* private key's header when getting an EC/RSA privkey from file using libssh */
#define NC_LIBSSH_PRIVKEY_HEADER "-----BEGIN PRIVATE KEY-----\n"
/* private key's footer when getting an EC/RSA privkey from file using libssh */
#define NC_LIBSSH_PRIVKEY_FOOTER "\n-----END PRIVATE KEY-----\n"
/* public key's ssh2 header */
#define NC_SSH2_PUBKEY_HEADER "---- BEGIN SSH2 PUBLIC KEY ----\n"
/* public key's SubjectPublicKeyInfo format header */
#define NC_SUBJECT_PUBKEY_INFO_HEADER "-----BEGIN PUBLIC KEY-----\n"
/* public key's SubjectPublicKeyInfo format footer */
#define NC_SUBJECT_PUBKEY_INFO_FOOTER "\n-----END PUBLIC KEY-----\n"
/* certificate's PEM format header */
#define NC_PEM_CERTIFICATE_HEADER "-----BEGIN CERTIFICATE-----\n"
/* certificate's PEM format footer */
#define NC_PEM_CERTIFICATE_FOOTER "\n-----END CERTIFICATE-----\n"
typedef enum {
NC_ALG_HOSTKEY,
NC_ALG_KEY_EXCHANGE,
NC_ALG_ENCRYPTION,
NC_ALG_MAC
} NC_ALG_TYPE;
/**
* @brief Gets asymmetric key pair from private key (and optionally public key) file(s).
*
* @param[in] privkey_path Path to private key.
* @param[in] pubkey_path Optional path to public key. If not set, PK will be generated from private key.
* @param[in] wanted_pubkey_type Wanted public key format to be generated (SPKI/SSH)
* @param[out] privkey Base64 encoded private key.
* @param[out] privkey_type Type of the private key. (RSA, EC, etc)
* @param[out] pubkey Base64 encoded public key.
* @return 0 on success, non-zero otherwise.
*/
int nc_server_config_util_get_asym_key_pair(const char *privkey_path, const char *pubkey_path, enum nc_pubkey_format wanted_pubkey_type,
char **privkey, enum nc_privkey_format *privkey_type, char **pubkey);
/**
* @brief Gets public key from a file and converts it to the SSH format if need be.
*
* @param[in] pubkey_path Path to the public key.
* @param[out] pubkey Base64 encoded public key.
*
* @return 0 on success, non-zero otherwise.
*/
int nc_server_config_util_get_ssh_pubkey_file(const char *pubkey_path, char **pubkey);
/**
* @brief Gets a certificate from a file.
*
* @param[in] cert_path Path to the certificate.
* @param[out] cert Base64 PEM encoded certificate data.
*
* @return 0 on success, non-zero otherwise.
*/
int nc_server_config_util_read_certificate(const char *cert_path, char **cert);
/**
* @brief Converts private key format to its associated identityref value.
*
* @param[in] format Private key format.
*
* @return Identityref on success, NULL on failure.
*/
const char *nc_server_config_util_privkey_format_to_identityref(enum nc_privkey_format format);
#endif /* NC_ENABLED_SSH_TLS */
/**
* @brief Creates YANG data nodes in a path and gives the final node a value.
*
* @param[in] ctx libyang context.
* @param[in, out] tree The YANG data tree where the insertion will happen. On success
* this is set to the top level container.
* @param[in] value Value assigned to the final node in the path.
* @param[in] path_fmt Format of the path.
* @param[in] ... Parameters for the path format, essentially representing the lists' keys.
* @return 0 on success, 1 otherwise.
*/
int nc_server_config_create(const struct ly_ctx *ctx, struct lyd_node **tree, const char *value, const char *path_fmt, ...);
/**
* @brief Creates a YANG data node by appending it to a specified parent node.
*
* @param[in] ctx libyang context.
* @param[in] parent_path Path to the parent node.
* @param[in] child_name Name of the parent's child node to be created.
* @param[in] value Value given to the child node.
* @param[out] tree YANG data tree where the insertion will happen. On success
* this is set to the top level container.
* @return 0 on success, 1 otherwise.
*/
int nc_server_config_append(const struct ly_ctx *ctx, const char *parent_path, const char *child_name,
const char *value, struct lyd_node **tree);
/**
* @brief Deletes a subtree from the YANG data.
*
* @param tree YANG data from which the subtree will be deleted.
* @param[in] path_fmt Format of the path. The last node will be the top level node of the deleted tree.
* @param[in] ... Parameters for the path format, essentially representing the lists' keys.
* @return 0 on success, non-zero otherwise.
*/
int nc_server_config_delete(struct lyd_node **tree, const char *path_fmt, ...);
/**
* @brief Deletes a subtree from the YANG data, but doesn't return an error if the node doesn't exist.
*
* @param tree YANG data from which the subtree will be deleted.
* @param[in] path_fmt Format of the path. The last node will be the top level node of the deleted tree.
* @param[in] ... Parameters for the path format, essentially representing the lists' keys.
* @return 0 on success, non-zero otherwise.
*/
int nc_server_config_check_delete(struct lyd_node **tree, const char *path_fmt, ...);
#endif /* NC_CONFIG_NEW_H_ */

View file

@ -0,0 +1,798 @@
/**
* @file server_config_util_ssh.c
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 server SSH configuration utilities
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include "server_config_util.h"
#include <crypt.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include "compat.h"
#include "config.h"
#include "log_p.h"
#include "server_config.h"
#include "session_p.h"
static int
_nc_server_config_add_ssh_hostkey(const struct ly_ctx *ctx, const char *tree_path,
const char *privkey_path, const char *pubkey_path, struct lyd_node **config)
{
int ret = 0;
char *pubkey = NULL, *privkey = NULL;
enum nc_privkey_format privkey_type;
const char *privkey_format, *pubkey_format = "ietf-crypto-types:ssh-public-key-format";
NC_CHECK_ARG_RET(NULL, ctx, tree_path, privkey_path, config, 1);
/* get the keys as a string from the given files */
ret = nc_server_config_util_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_SSH, &privkey,
&privkey_type, &pubkey);
if (ret) {
goto cleanup;
}
/* get privkey identityref value */
privkey_format = nc_server_config_util_privkey_format_to_identityref(privkey_type);
if (!privkey_format) {
ret = 1;
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/public-key-format", pubkey_format, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/public-key", pubkey, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/private-key-format", privkey_format, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/cleartext-private-key", privkey, config);
if (ret) {
goto cleanup;
}
/* delete keystore choice nodes if present */
ret = nc_server_config_check_delete(config, "%s/central-keystore-reference", tree_path);
if (ret) {
goto cleanup;
}
cleanup:
free(privkey);
free(pubkey);
return ret;
}
API int
nc_server_config_add_ssh_hostkey(const struct ly_ctx *ctx, const char *endpt_name, const char *hostkey_name,
const char *privkey_path, const char *pubkey_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, hostkey_name, privkey_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/"
"server-identity/host-key[name='%s']/public-key", endpt_name, hostkey_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_hostkey(ctx, path, privkey_path, pubkey_path, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_add_ch_ssh_hostkey(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *hostkey_name, const char *privkey_path, const char *pubkey_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, hostkey_name, privkey_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key[name='%s']/public-key", client_name, endpt_name, hostkey_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_hostkey(ctx, path, privkey_path, pubkey_path, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ssh_hostkey(const struct ly_ctx *ctx, const char *endpt_name, const char *hostkey_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, config, 1);
if (hostkey_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"ssh/ssh-server-parameters/server-identity/host-key[name='%s']", endpt_name, hostkey_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"ssh/ssh-server-parameters/server-identity/host-key", endpt_name);
}
}
API int
nc_server_config_del_ch_ssh_hostkey(const char *client_name, const char *endpt_name,
const char *hostkey_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
if (hostkey_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key[name='%s']", client_name, endpt_name, hostkey_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key", client_name, endpt_name);
}
}
API int
nc_server_config_add_ssh_keystore_ref(const struct ly_ctx *ctx, const char *endpt_name, const char *hostkey_name,
const char *keystore_reference, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, hostkey_name, keystore_reference, config, 1);
ret = nc_server_config_create(ctx, config, keystore_reference, "/ietf-netconf-server:netconf-server/listen/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/host-key[name='%s']/public-key/"
"central-keystore-reference", endpt_name, hostkey_name);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/host-key[name='%s']/public-key/"
"inline-definition", endpt_name, hostkey_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_add_ch_ssh_keystore_ref(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *hostkey_name, const char *keystore_reference, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, hostkey_name, keystore_reference, config, 1);
ret = nc_server_config_create(ctx, config, keystore_reference, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key[name='%s']/public-key/central-keystore-reference", client_name, endpt_name, hostkey_name);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key[name='%s']/public-key/inline-definition", client_name, endpt_name, hostkey_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_ssh_keystore_ref(const char *endpt_name, const char *hostkey_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/host-key[name='%s']/public-key/"
"central-keystore-reference", endpt_name, hostkey_name);
}
API int
nc_server_config_del_ch_ssh_keystore_ref(const char *client_name, const char *endpt_name,
const char *hostkey_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, hostkey_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/server-identity/"
"host-key[name='%s']/public-key/central-keystore-reference", client_name, endpt_name, hostkey_name);
}
static int
_nc_server_config_add_ssh_user_pubkey(const struct ly_ctx *ctx, const char *tree_path, const char *pubkey_path,
struct lyd_node **config)
{
int ret = 0;
char *pubkey = NULL;
const char *pubkey_format = "ietf-crypto-types:ssh-public-key-format";
/* get pubkey data */
ret = nc_server_config_util_get_ssh_pubkey_file(pubkey_path, &pubkey);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "public-key-format", pubkey_format, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "public-key", pubkey, config);
if (ret) {
goto cleanup;
}
cleanup:
free(pubkey);
return ret;
}
API int
nc_server_config_add_ssh_user_pubkey(const struct ly_ctx *ctx, const char *endpt_name,
const char *user_name, const char *pubkey_name, const char *pubkey_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, user_name, pubkey_name, pubkey_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/inline-definition/"
"public-key[name='%s']", endpt_name, user_name, pubkey_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_user_pubkey(ctx, path, pubkey_path, config);
if (ret) {
goto cleanup;
}
/* delete truststore reference if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/central-truststore-reference",
endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete use system auth if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"libnetconf2-netconf-server:use-system-keys", endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_add_ch_ssh_user_pubkey(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *user_name, const char *pubkey_name, const char *pubkey_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, user_name, pubkey_name, pubkey_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/public-keys/inline-definition/public-key[name='%s']", client_name,
endpt_name, user_name, pubkey_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_user_pubkey(ctx, path, pubkey_path, config);
if (ret) {
goto cleanup;
}
/* delete truststore reference if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/central-truststore-reference", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete use system auth if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"libnetconf2-netconf-server:use-system-keys", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ssh_user_pubkey(const char *endpt_name, const char *user_name,
const char *pubkey_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, user_name, config, 1);
if (pubkey_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/inline-definition/"
"public-key[name='%s']", endpt_name, user_name, pubkey_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/inline-definition/"
"public-key", endpt_name, user_name);
}
}
API int
nc_server_config_del_ch_ssh_user_pubkey(const char *client_name, const char *endpt_name,
const char *user_name, const char *pubkey_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, user_name, config, 1);
if (pubkey_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/public-keys/inline-definition/public-key[name='%s']", client_name,
endpt_name, user_name, pubkey_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/public-keys/inline-definition/public-key", client_name,
endpt_name, user_name);
}
}
API int
nc_server_config_add_ssh_user_authkey(const struct ly_ctx *ctx, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, user_name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/"
"client-authentication/users/user[name='%s']/public-keys", endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = nc_server_config_append(ctx, path, "libnetconf2-netconf-server:use-system-keys", NULL, config);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/inline-definition",
endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete truststore reference if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/central-truststore-reference",
endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_add_ch_ssh_user_authkey(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *user_name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, user_name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users"
"/user[name='%s']/public-keys", client_name, endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = nc_server_config_append(ctx, path, "libnetconf2-netconf-server:use-system-keys", NULL, config);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/inline-definition", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete truststore reference if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/central-truststore-reference", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ssh_user_authkey(const char *endpt_name, const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/libnetconf2-netconf-server:use-system-keys", endpt_name, user_name);
}
API int
nc_server_config_ch_del_ssh_user_authkey(const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/libnetconf2-netconf-server:use-system-keys", client_name, endpt_name, user_name);
}
static int
_nc_server_config_add_ssh_user_password(const struct ly_ctx *ctx, const char *tree_path,
const char *password, struct lyd_node **config)
{
int ret = 0;
char *hashed_pw = NULL;
const char *salt = "$6$idsizuippipk$";
struct crypt_data cdata = {0};
NC_CHECK_ARG_RET(NULL, ctx, tree_path, password, config, 1);
hashed_pw = crypt_r(password, salt, &cdata);
if (!hashed_pw) {
ERR(NULL, "Hashing password failed (%s).", strerror(errno));
ret = 1;
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "password", hashed_pw, config);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_add_ssh_user_password(const struct ly_ctx *ctx, const char *endpt_name,
const char *user_name, const char *password, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, user_name, password, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/"
"client-authentication/users/user[name='%s']", endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_user_password(ctx, path, password, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_add_ch_ssh_user_password(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *user_name, const char *password, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, user_name, password, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']", client_name, endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_ssh_user_password(ctx, path, password, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ssh_user_password(const char *endpt_name, const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/password", endpt_name, user_name);
}
API int
nc_server_config_del_ch_ssh_user_password(const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/password", client_name, endpt_name, user_name);
}
API int
nc_server_config_add_ssh_user_interactive(const struct ly_ctx *ctx, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, user_name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/"
"client-authentication/users/user[name='%s']/libnetconf2-netconf-server:keyboard-interactive", endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = nc_server_config_append(ctx, path, "use-system-auth", NULL, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_add_ch_ssh_user_interactive(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, user_name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"libnetconf2-netconf-server:keyboard-interactive", client_name, endpt_name, user_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = nc_server_config_append(ctx, path, "use-system-auth", NULL, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ssh_user_interactive(const char *endpt_name, const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/"
"libnetconf2-netconf-server:keyboard-interactive", endpt_name, user_name);
}
API int
nc_server_config_del_ch_ssh_user_interactive(const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"libnetconf2-netconf-server:keyboard-interactive", client_name, endpt_name, user_name);
}
API int
nc_server_config_del_ssh_user(const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
if (user_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']", endpt_name, user_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user", endpt_name);
}
}
API int
nc_server_config_del_ch_ssh_user(const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
if (user_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']", client_name,
endpt_name, user_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user", client_name, endpt_name);
}
}
API int
nc_server_config_add_ssh_endpoint_client_ref(const struct ly_ctx *ctx, const char *endpt_name,
const char *referenced_endpt, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, referenced_endpt, config, 1);
return nc_server_config_create(ctx, config, referenced_endpt, "/ietf-netconf-server:netconf-server/listen/endpoints/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/libnetconf2-netconf-server:endpoint-reference",
endpt_name);
}
API int
nc_server_config_del_ssh_endpoint_client_ref(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"ssh/ssh-server-parameters/client-authentication/libnetconf2-netconf-server:endpoint-reference", endpt_name);
}
API int
nc_server_config_add_ssh_truststore_ref(const struct ly_ctx *ctx, const char *endpt_name, const char *user_name,
const char *truststore_reference, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, user_name, truststore_reference, config, 1);
ret = nc_server_config_create(ctx, config, truststore_reference, "/ietf-netconf-server:netconf-server/listen/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"central-truststore-reference", endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/inline-definition",
endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete use system auth if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"libnetconf2-netconf-server:use-system-keys", endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_add_ch_ssh_truststore_ref(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *user_name, const char *truststore_reference, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, user_name, truststore_reference, config, 1);
ret = nc_server_config_create(ctx, config, truststore_reference, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/public-keys/central-truststore-reference", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete inline definition nodes if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/"
"public-keys/inline-definition", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
/* delete use system auth if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/"
"ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"libnetconf2-netconf-server:use-system-keys", client_name, endpt_name, user_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_ssh_truststore_ref(const char *endpt_name, const char *user_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/"
"endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/users/user[name='%s']/public-keys/"
"central-truststore-reference", endpt_name, user_name);
}
API int
nc_server_config_del_ch_ssh_truststore_ref(const char *client_name, const char *endpt_name,
const char *user_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, user_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
"users/user[name='%s']/public-keys/central-truststore-reference", client_name, endpt_name, user_name);
}

View file

@ -0,0 +1,790 @@
/**
* @file server_config_util_tls.c
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 server TLS configuration utilities
*
* @copyright
* Copyright (c) 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include "server_config_util.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libyang/libyang.h>
#include "compat.h"
#include "config.h"
#include "log_p.h"
#include "server_config.h"
#include "session.h"
#include "session_p.h"
static int
_nc_server_config_add_tls_server_cert(const struct ly_ctx *ctx, const char *tree_path, const char *privkey_path,
const char *pubkey_path, const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *privkey = NULL, *pubkey = NULL, *cert = NULL;
enum nc_privkey_format privkey_type;
const char *privkey_format, *pubkey_format = "ietf-crypto-types:subject-public-key-info-format";
NC_CHECK_ARG_RET(NULL, ctx, tree_path, privkey_path, cert_path, config, 1);
/* get the keys as a string from the given files */
ret = nc_server_config_util_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_X509, &privkey, &privkey_type, &pubkey);
if (ret) {
ERR(NULL, "Getting keys from file(s) failed.");
goto cleanup;
}
/* get cert data from file */
ret = nc_server_config_util_read_certificate(cert_path, &cert);
if (ret) {
ERR(NULL, "Getting certificate from file \"%s\" failed.", cert_path);
goto cleanup;
}
/* get privkey identityref value */
privkey_format = nc_server_config_util_privkey_format_to_identityref(privkey_type);
if (!privkey_format) {
ret = 1;
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/public-key-format", pubkey_format, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/public-key", pubkey, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/private-key-format", privkey_format, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/cleartext-private-key", privkey, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "inline-definition/cert-data", cert, config);
if (ret) {
goto cleanup;
}
/* delete keystore if present */
ret = nc_server_config_check_delete(config, "%s/central-keystore-reference", tree_path);
if (ret) {
goto cleanup;
}
cleanup:
free(privkey);
free(pubkey);
free(cert);
return ret;
}
API int
nc_server_config_add_tls_server_cert(const struct ly_ctx *ctx, const char *endpt_name, const char *privkey_path,
const char *pubkey_path, const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, privkey_path, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/server-identity/certificate", endpt_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_server_cert(ctx, path, privkey_path, pubkey_path,
cert_path, config);
if (ret) {
ERR(NULL, "Creating new TLS server certificate YANG data failed.");
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_tls_server_cert(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/server-identity/certificate/inline-definition", endpt_name);
}
API int
nc_server_config_add_ch_tls_server_cert(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *privkey_path, const char *pubkey_path, const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, privkey_path, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/server-identity/"
"certificate", client_name, endpt_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_server_cert(ctx, path, privkey_path, pubkey_path,
cert_path, config);
if (ret) {
ERR(NULL, "Creating new CH TLS server certificate YANG data failed.");
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ch_tls_server_cert(const char *client_name, const char *endpt_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/server-identity/"
"certificate/inline-definition", client_name, endpt_name);
}
static int
_nc_server_config_add_tls_keystore_ref(const struct ly_ctx *ctx, const char *tree_path, const char *asym_key_ref,
const char *cert_ref, struct lyd_node **config)
{
int ret = 0;
/* create asymmetric key pair reference */
ret = nc_server_config_append(ctx, tree_path, "central-keystore-reference/asymmetric-key", asym_key_ref, config);
if (ret) {
goto cleanup;
}
/* create cert reference, this cert has to belong to the asym key */
ret = nc_server_config_append(ctx, tree_path, "central-keystore-reference/certificate", cert_ref, config);
if (ret) {
goto cleanup;
}
/* delete inline definition if present */
ret = nc_server_config_check_delete(config, "%s/inline-definition", tree_path);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_add_tls_keystore_ref(const struct ly_ctx *ctx, const char *endpt_name, const char *asym_key_ref,
const char *cert_ref, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, asym_key_ref, cert_ref, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/server-identity/certificate", endpt_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_keystore_ref(ctx, path, asym_key_ref, cert_ref, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_tls_keystore_ref(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/server-identity/certificate/central-keystore-reference", endpt_name);
}
API int
nc_server_config_add_ch_tls_keystore_ref(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *asym_key_ref, const char *cert_ref, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, asym_key_ref, cert_ref, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
"endpoint[name='%s']/tls/tls-server-parameters/server-identity/certificate", client_name, endpt_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_keystore_ref(ctx, path, asym_key_ref, cert_ref, config);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ch_tls_keystore_ref(const char *client_name, const char *endpt_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/server-identity/certificate/"
"central-keystore-reference", client_name, endpt_name);
}
static int
_nc_server_config_add_tls_client_cert(const struct ly_ctx *ctx, const char *tree_path,
const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *cert = NULL;
NC_CHECK_ARG_RET(NULL, ctx, tree_path, cert_path, config, 1);
ret = nc_server_config_util_read_certificate(cert_path, &cert);
if (ret) {
ERR(NULL, "Getting certificate from file \"%s\" failed.", cert_path);
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "cert-data", cert, config);
if (ret) {
goto cleanup;
}
cleanup:
free(cert);
return ret;
}
API int
nc_server_config_add_tls_client_cert(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name,
const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_name, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ee-certs/inline-definition/certificate[name='%s']", endpt_name, cert_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_client_cert(ctx, path, cert_path, config);
if (ret) {
ERR(NULL, "Creating new TLS client certificate YANG data failed.");
goto cleanup;
}
/* delete truststore if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/client-authentication/ee-certs/central-truststore-reference", endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_tls_client_cert(const char *endpt_name, const char *cert_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
if (cert_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/inline-definition/"
"certificate[name='%s']", endpt_name, cert_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/inline-definition/"
"certificate", endpt_name);
}
}
API int
nc_server_config_add_ch_tls_client_cert(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *cert_name, const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, cert_name, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ee-certs/"
"inline-definition/certificate[name='%s']", client_name, endpt_name, cert_name) == -1;
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_client_cert(ctx, path, cert_path, config);
if (ret) {
ERR(NULL, "Creating new CH TLS client certificate YANG data failed.");
goto cleanup;
}
/* delete truststore if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ee-certs/central-truststore-reference", client_name, endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ch_tls_client_cert(const char *client_name, const char *endpt_name,
const char *cert_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
if (cert_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ee-certs/"
"inline-definition/certificate[name='%s']", client_name, endpt_name, cert_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ee-certs/"
"inline-definition/certificate", client_name, endpt_name);
}
}
API int
nc_server_config_add_tls_client_cert_truststore_ref(const struct ly_ctx *ctx, const char *endpt_name,
const char *cert_bag_ref, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_bag_ref, config, 1);
ret = nc_server_config_create(ctx, config, cert_bag_ref, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/central-truststore-reference", endpt_name);
if (ret) {
goto cleanup;
}
/* delete inline definition if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/inline-definition", endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_tls_client_cert_truststore_ref(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/central-truststore-reference", endpt_name);
}
API int
nc_server_config_add_ch_tls_client_cert_truststore_ref(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *cert_bag_ref, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, cert_bag_ref, config, 1);
ret = nc_server_config_create(ctx, config, cert_bag_ref, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ee-certs/central-truststore-reference", client_name, endpt_name);
if (ret) {
goto cleanup;
}
/* delete inline definition if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ee-certs/inline-definition", client_name, endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_ch_tls_client_cert_truststore_ref(const char *client_name, const char *endpt_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ee-certs/central-truststore-reference", client_name, endpt_name);
}
API int
nc_server_config_add_tls_ca_cert(const struct ly_ctx *ctx, const char *endpt_name, const char *cert_name,
const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_name, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ca-certs/inline-definition/certificate[name='%s']", endpt_name, cert_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_client_cert(ctx, path, cert_path, config);
if (ret) {
ERR(NULL, "Creating new TLS client certificate authority YANG data failed.");
goto cleanup;
}
/* delete truststore if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/"
"tls/tls-server-parameters/client-authentication/ca-certs/central-truststore-reference", endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_tls_ca_cert(const char *endpt_name, const char *cert_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
if (cert_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ca-certs/inline-definition/"
"certificate[name='%s']", endpt_name, cert_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ca-certs/inline-definition/"
"certificate", endpt_name);
}
}
API int
nc_server_config_add_ch_tls_ca_cert(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
const char *cert_name, const char *cert_path, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, cert_name, cert_path, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ca-certs/"
"inline-definition/certificate[name='%s']", client_name, endpt_name, cert_name);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_client_cert(ctx, path, cert_path, config);
if (ret) {
ERR(NULL, "Creating new CH TLS client certificate authority YANG data failed.");
goto cleanup;
}
/* delete truststore if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ca-certs/central-truststore-reference", client_name, endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ch_tls_ca_cert(const char *client_name, const char *endpt_name,
const char *cert_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
if (cert_name) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ca-certs/"
"inline-definition/certificate[name='%s']", client_name, endpt_name, cert_name);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/tls-server-parameters/client-authentication/ca-certs/"
"inline-definition/certificate", client_name, endpt_name);
}
}
API int
nc_server_config_add_tls_ca_cert_truststore_ref(const struct ly_ctx *ctx, const char *endpt_name,
const char *cert_bag_ref, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, cert_bag_ref, config, 1);
ret = nc_server_config_create(ctx, config, cert_bag_ref, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ca-certs/central-truststore-reference", endpt_name);
if (ret) {
goto cleanup;
}
/* delete inline definition if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ca-certs/inline-definition", endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_tls_ca_cert_truststore_ref(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"tls-server-parameters/client-authentication/ca-certs/central-truststore-reference", endpt_name);
}
API int
nc_server_config_add_ch_tls_ca_cert_truststore_ref(const struct ly_ctx *ctx, const char *client_name,
const char *endpt_name, const char *cert_bag_ref, struct lyd_node **config)
{
int ret = 0;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, cert_bag_ref, config, 1);
ret = nc_server_config_create(ctx, config, cert_bag_ref, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ca-certs/central-truststore-reference", client_name, endpt_name);
if (ret) {
goto cleanup;
}
/* delete inline definition if present */
ret = nc_server_config_check_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ca-certs/inline-definition", client_name, endpt_name);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_del_ch_tls_ca_cert_truststore_ref(const char *client_name, const char *endpt_name,
struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/"
"netconf-client[name='%s']/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/ca-certs/central-truststore-reference", client_name, endpt_name);
}
static const char *
nc_server_config_tls_maptype2str(NC_TLS_CTN_MAPTYPE map_type)
{
switch (map_type) {
case NC_TLS_CTN_SPECIFIED:
return "ietf-x509-cert-to-name:specified";
case NC_TLS_CTN_SAN_RFC822_NAME:
return "ietf-x509-cert-to-name:san-rfc822-name";
case NC_TLS_CTN_SAN_DNS_NAME:
return "ietf-x509-cert-to-name:san-dns-name";
case NC_TLS_CTN_SAN_IP_ADDRESS:
return "ietf-x509-cert-to-name:san-ip-address";
case NC_TLS_CTN_SAN_ANY:
return "ietf-x509-cert-to-name:san-any";
case NC_TLS_CTN_COMMON_NAME:
return "ietf-x509-cert-to-name:common-name";
case NC_TLS_CTN_UNKNOWN:
default:
ERR(NULL, "Unknown CTN mapping type.");
return NULL;
}
}
static int
_nc_server_config_add_tls_ctn(const struct ly_ctx *ctx, const char *tree_path, const char *fingerprint,
NC_TLS_CTN_MAPTYPE map_type, const char *name, struct lyd_node **config)
{
int ret = 0;
const char *map;
NC_CHECK_ARG_RET(NULL, ctx, tree_path, name, config, 1);
if (fingerprint) {
/* optional */
ret = nc_server_config_append(ctx, tree_path, "fingerprint", fingerprint, config);
if (ret) {
goto cleanup;
}
}
/* get map str */
map = nc_server_config_tls_maptype2str(map_type);
if (!map) {
ret = 1;
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "map-type", map, config);
if (ret) {
goto cleanup;
}
ret = nc_server_config_append(ctx, tree_path, "name", name, config);
if (ret) {
goto cleanup;
}
cleanup:
return ret;
}
API int
nc_server_config_add_tls_ctn(const struct ly_ctx *ctx, const char *endpt_name, uint32_t id, const char *fingerprint,
NC_TLS_CTN_MAPTYPE map_type, const char *name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, id, name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/netconf-server-parameters/"
"client-identity-mappings/cert-to-name[id='%" PRIu32 "']", endpt_name, id);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_ctn(ctx, path, fingerprint, map_type, name, config);
if (ret) {
ERR(NULL, "Creating new TLS cert-to-name YANG data failed.");
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_tls_ctn(const char *endpt_name, uint32_t id, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
if (id) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"netconf-server-parameters/client-identity-mappings/cert-to-name[id='%" PRIu32 "']", endpt_name, id);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/"
"netconf-server-parameters/client-identity-mappings/cert-to-name", endpt_name);
}
}
API int
nc_server_config_add_ch_tls_ctn(const struct ly_ctx *ctx, const char *client_name, const char *endpt_name,
uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, struct lyd_node **config)
{
int ret = 0;
char *path = NULL;
NC_CHECK_ARG_RET(NULL, ctx, client_name, endpt_name, id, name, config, 1);
ret = asprintf(&path, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/netconf-server-parameters/client-identity-mappings/"
"cert-to-name[id='%" PRIu32 "']", client_name, endpt_name, id);
NC_CHECK_ERRMEM_GOTO(ret == -1, path = NULL; ret = 1, cleanup);
ret = _nc_server_config_add_tls_ctn(ctx, path, fingerprint, map_type, name, config);
if (ret) {
ERR(NULL, "Creating new CH TLS cert-to-name YANG data failed.");
goto cleanup;
}
cleanup:
free(path);
return ret;
}
API int
nc_server_config_del_ch_tls_ctn(const char *client_name, const char *endpt_name,
uint32_t id, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, client_name, endpt_name, config, 1);
if (id) {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/netconf-server-parameters/client-identity-mappings/"
"cert-to-name[id='%u']", client_name, endpt_name, id);
} else {
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/"
"endpoints/endpoint[name='%s']/tls/netconf-server-parameters/client-identity-mappings/"
"cert-to-name", client_name, endpt_name);
}
}
API int
nc_server_config_add_tls_endpoint_client_ref(const struct ly_ctx *ctx, const char *endpt_name, const char *referenced_endpt, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, ctx, endpt_name, referenced_endpt, config, 1);
return nc_server_config_create(ctx, config, referenced_endpt, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/libnetconf2-netconf-server:endpoint-reference", endpt_name);
}
API int
nc_server_config_del_tls_endpoint_client_ref(const char *endpt_name, struct lyd_node **config)
{
NC_CHECK_ARG_RET(NULL, endpt_name, config, 1);
return nc_server_config_delete(config, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint[name='%s']/tls/tls-server-parameters/"
"client-authentication/libnetconf2-netconf-server:endpoint-reference", endpt_name);
}

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,11 @@
/**
* \file session.h
* \author Radek Krejci <rkrejci@cesnet.cz>
* \brief libnetconf2 session manipulation
* @file session.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 session manipulation
*
* Copyright (c) 2015 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -21,7 +23,7 @@ extern "C" {
#include "netconf.h"
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Enumeration of NETCONF SSH authentication methods
@ -32,9 +34,16 @@ typedef enum {
NC_SSH_AUTH_INTERACTIVE = 0x04 /**< interactive SSH authentication */
} NC_SSH_AUTH_TYPE;
#endif /* NC_ENABLED_SSH */
#ifdef NC_ENABLED_TLS
/**
* @brief Enumeration of host key checking and known_hosts entry adding modes
*/
typedef enum {
NC_SSH_KNOWNHOSTS_ASK = 0, /**< add a known_hosts entry, but with a prompt */
NC_SSH_KNOWNHOSTS_STRICT, /**< do not add a known_hosts entry and the server's host key must be present in the configured known_hosts file */
NC_SSH_KNOWNHOSTS_ACCEPT_NEW, /**< add a known_hosts entry without a prompt */
NC_SSH_KNOWNHOSTS_ACCEPT, /**< add a known_hosts entry without a prompt and allow connections to servers which changed their host key */
NC_SSH_KNOWNHOSTS_SKIP /**< do not add a known_hosts entry and skip all host key checks */
} NC_SSH_KNOWNHOSTS_MODE;
/**
* @brief Enumeration of cert-to-name mapping types
@ -49,7 +58,17 @@ typedef enum {
NC_TLS_CTN_COMMON_NAME /**< common name as username */
} NC_TLS_CTN_MAPTYPE;
#endif /* NC_ENABLED_TLS */
/**
* @brief Enumeration of TLS versions.
*/
typedef enum {
NC_TLS_VERSION_10 = 1, /**< TLS1.0 */
NC_TLS_VERSION_11 = 2, /**< TLS1.1 */
NC_TLS_VERSION_12 = 4, /**< TLS1.2 */
NC_TLS_VERSION_13 = 8 /**< TLS1.3 */
} NC_TLS_VERSION;
#endif /* NC_ENABLED_SSH_TLS */
/**
* @brief Enumeration of possible session statuses
@ -70,12 +89,11 @@ typedef enum {
NC_TI_FD, /**< file descriptors - use standard input/output, transport protocol is implemented
outside the current application */
NC_TI_UNIX, /**< unix socket */
#ifdef NC_ENABLED_SSH
NC_TI_LIBSSH, /**< libssh - use libssh library, only for NETCONF over SSH transport */
#endif
#ifdef NC_ENABLED_TLS
NC_TI_OPENSSL /**< OpenSSL - use OpenSSL library, only for NETCONF over TLS transport */
#endif
#ifdef NC_ENABLED_SSH_TLS
NC_TI_SSH, /**< SSH - use libssh library, only for NETCONF over SSH transport */
NC_TI_TLS /**< TLS - use either OpenSSL or MbedTLS library, only for NETCONF over TLS transport */
#endif /* NC_ENABLED_SSH_TLS */
} NC_TRANSPORT_IMPL;
/**
@ -96,16 +114,6 @@ typedef enum {
NC_CH_RANDOM
} NC_CH_START_WITH;
/**
* @brief Enumeration of SSH key types.
*/
typedef enum {
NC_SSH_KEY_UNKNOWN = 0,
NC_SSH_KEY_DSA,
NC_SSH_KEY_RSA,
NC_SSH_KEY_ECDSA
} NC_SSH_KEY_TYPE;
/**
* @brief NETCONF session object
*/
@ -183,6 +191,18 @@ const char *nc_session_get_host(const struct nc_session *session);
*/
uint16_t nc_session_get_port(const struct nc_session *session);
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Get the SSH banner sent by the peer.
*
* @param[in] session Session to get the banner from.
* @return SSH banner on success, NULL on error.
*/
const char *nc_session_ssh_get_banner(const struct nc_session *session);
#endif
/**
* @brief Get session path (unix socket only).
*
@ -197,7 +217,7 @@ const char *nc_session_get_path(const struct nc_session *session);
* @param[in] session Session to get the information from.
* @return Session context.
*/
struct ly_ctx *nc_session_get_ctx(const struct nc_session *session);
const struct ly_ctx *nc_session_get_ctx(const struct nc_session *session);
/**
* @brief Assign arbitrary data to a session.
@ -215,6 +235,14 @@ void nc_session_set_data(struct nc_session *session, void *data);
*/
void *nc_session_get_data(const struct nc_session *session);
/**
* @brief Learn whether a session was created using Call Home or not.
*
* @param[in] session Session to get the information from.
* @return 0 if a standard session, non-zero if a Call Home session.
*/
int nc_session_is_callhome(const struct nc_session *session);
/**
* @brief Free the NETCONF session object.
*
@ -223,20 +251,6 @@ void *nc_session_get_data(const struct nc_session *session);
*/
void nc_session_free(struct nc_session *session, void (*data_free)(void *));
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
/**
* @brief Free all the dynamically allocated thread-specific libssl/libcrypto
* resources.
*
* This function should be called only if init (nc_client_init(), respectively nc_server_init()) was called.
* Call it in every thread your application creates just before the thread exits. In the last thread
* (usually the main one) call nc_client_destroy(), respectively nc_server_destroy().
*/
void nc_thread_destroy(void);
#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 session client manipulation
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -21,18 +22,14 @@ extern "C" {
#include <libyang/libyang.h>
#ifdef NC_ENABLED_SSH
# include <libssh/libssh.h>
#endif
#ifdef NC_ENABLED_TLS
# include <openssl/ssl.h>
#endif
#include "messages_client.h"
#include "netconf.h"
#include "session.h"
#ifdef NC_ENABLED_SSH_TLS
# include <libssh/libssh.h>
#endif /* NC_ENABLED_SSH_TLS */
/**
* @addtogroup client
* @{
@ -82,6 +79,26 @@ int nc_client_set_schema_callback(ly_module_imp_clb clb, void *user_data);
*/
ly_module_imp_clb nc_client_get_schema_callback(void **user_data);
/**
* @brief Enable/disable loading of all the YANG modules supported by
* the server when a new session is created. If disabled, it is expected
* that users update the context themselves and load the YANG modules
* that are planned to be used. Otherwise even basic RPCs may fail to be sent!
*
* @param[in] enabled Whether context autofill is enabled or disabled.
*/
void nc_client_set_new_session_context_autofill(int enabled);
/**
* @brief Set client session context to support schema-mount, if possible.
*
* Performed automatically unless ::nc_client_set_new_session_context_autofill() was disabled.
*
* @param[in] session NETCONF session to update.
* @return 0 on success, -1 on error.
*/
int nc_client_set_new_session_context_schema_mount(struct nc_session *session);
/**
* @brief Use the provided thread-specific client's context in the current thread.
*
@ -105,9 +122,11 @@ void nc_client_set_thread_context(void *context);
void *nc_client_get_thread_context(void);
/**
* @brief Initialize libssh and/or libssl/libcrypto for use in the client.
* @brief Initialize client for establishing connections.
*
* @return 0 on success, -1 on error.
*/
void nc_client_init(void);
int nc_client_init(void);
/**
* @brief Destroy all libssh and/or libssl/libcrypto dynamic memory and
@ -134,13 +153,9 @@ void nc_client_destroy(void);
*
* @param[in] fdin Input file descriptor for reading (clear) data from NETCONF server.
* @param[in] fdout Output file descriptor for writing (clear) data for NETCONF server.
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL in case of error.
*/
struct nc_session *nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx);
@ -153,20 +168,16 @@ struct nc_session *nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx);
* by sending and processing NETCONF \<hello\> messages.
*
* @param[in] address Path to the unix socket.
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL in case of error.
*/
struct nc_session *nc_connect_unix(const char *address, struct ly_ctx *ctx);
/** @} Client Session */
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
/**
* @defgroup client_ssh Client SSH
@ -177,28 +188,25 @@ struct nc_session *nc_connect_unix(const char *address, struct ly_ctx *ctx);
*/
/**
* @brief Set SSH authentication hostkey check (knownhosts) callback.
* @brief Set the behaviour of checking the host key and adding/reading entries to/from the known_hosts file.
*
* Repetitive calling causes replacing of the previous callback and its private data. Caller is responsible for
* freeing the private data when necessary (the private data can be obtained by
* nc_client_ssh_get_auth_hostkey_check_clb()).
* The default mode is ::NC_SSH_KNOWNHOSTS_ASK.
*
* @param[in] auth_hostkey_check Function to call, returns 0 on success, non-zero in error.
* If NULL, the default callback is set.
* @param[in] priv Optional private data to be passed to the callback function.
* @param[in] mode Server host key checking mode.
*/
void nc_client_ssh_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session, void *priv),
void *priv);
void nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode);
/**
* @brief Get currently set SSH authentication hostkey check (knownhosts) callback and its private data previously set
* by nc_client_ssh_set_auth_hostkey_check_clb().
* @brief Set the path to the known_hosts file.
*
* @param[out] auth_hostkey_check Currently set callback, NULL in case of the default callback.
* @param[out] priv Currently set (optional) private data to be passed to the callback function.
* Repetetive calling replaces the value. If the given file doesn't exist and the process has sufficient
* rights, it gets created whenever the file is needed, otherwise an error occurs. If NULL is passed or the
* path isn't set, the default known_hosts file will be used.
*
* @param[in] path Path to the known_hosts file.
* @return 0 on success, 1 on error.
*/
void nc_client_ssh_get_auth_hostkey_check_clb(int (**auth_hostkey_check)(const char *hostname, ssh_session session, void *priv),
void **priv);
int nc_client_ssh_set_knownhosts_path(const char *path);
/**
* @brief Set SSH password authentication callback.
@ -356,13 +364,9 @@ const char *nc_client_ssh_get_username(void);
* @param[in] host Hostname or address (both Ipv4 and IPv6 are accepted) of the target server.
* 'localhost' is used by default if NULL is specified.
* @param[in] port Port number of the target server. Default value 830 is used if 0 is specified.
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL on error.
*/
struct nc_session *nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx);
@ -375,15 +379,11 @@ struct nc_session *nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx
* set and connected only the host and the username must be set/is detected. Or the @p ssh_session
* can already be authenticated in which case it is used directly.
*
* @param[in] ssh_session libssh structure representing SSH session object. After passing it
* to libnetconf2 this way, it is fully managed by it (including freeing!).
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in] ssh_session libssh structure representing SSH session object. It is fully managed by the created session
* including freeing it.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL on error.
*/
struct nc_session *nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx);
@ -393,23 +393,15 @@ struct nc_session *nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx
*
* @param[in] session Existing NETCONF session. The session has to be created on SSH transport layer using libssh -
* it has to be created by nc_connect_ssh(), nc_connect_libssh() or nc_connect_ssh_channel().
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL on error.
*/
struct nc_session *nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx);
/** @} Client SSH */
#endif /* NC_ENABLED_SSH */
#ifdef NC_ENABLED_TLS
/**
* @defgroup client_tls Client TLS
* @ingroup client
@ -458,19 +450,12 @@ int nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir);
void nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir);
/**
* @brief Set client Certificate Revocation List paths.
*
* @param[in] crl_file Location of the CRL certificate file used to check for revocated certificates.
* @param[in] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
* @return 0 on success, -1 on error.
* @brief Deprecated.
*/
int nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir);
/**
* @brief Get client Certificate Revocation List paths.
*
* @param[out] crl_file Location of the CRL certificate file used to check for revocated certificates.
* @param[out] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
* @brief Deprecated.
*/
void nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir);
@ -478,42 +463,26 @@ void nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir);
* @brief Connect to the NETCONF server using TLS transport (via libssl)
*
* TLS session is created with the certificates set using nc_client_tls_* functions, which must be called beforehand!
* If the caller needs to use specific TLS session properties, they are supposed to use nc_connect_libssl().
* If the caller needs to use specific TLS session properties, they are supposed to use ::nc_connect_libssl().
*
* @param[in] host Hostname or address (both Ipv4 and IPv6 are accepted) of the target server.
* 'localhost' is used by default if NULL is specified.
* 'localhost' is used by default if NULL is specified. It is verified by TLS when connecting to it.
* @param[in] port Port number of the target server. Default value 6513 is used if 0 is specified.
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @return Created NETCONF session object or NULL on error.
*/
struct nc_session *nc_connect_tls(const char *host, uint16_t port, struct ly_ctx *ctx);
/**
* @brief Connect to the NETCONF server using the provided TLS (libssl) session.
*
* The TLS session supplied is expected to be fully connected and authenticated!
*
* @param[in] tls libssl structure representing the TLS session object.
* @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
* (ignoring what is actually supported by the server side). If not set,
* YANG context is created for the session using \<get-schema\> (if supported
* by the server side) or/and by searching for YANG schemas in the searchpath
* (see nc_client_schema_searchpath()). In every case except not providing context
* to connect to a server supporting \<get-schema\> it is possible that
* the session context will not include all the models supported by the server.
* @return Created NETCONF session object or NULL on error.
* @brief Deprecated. Should not be needed.
*/
struct nc_session *nc_connect_libssl(SSL *tls, struct ly_ctx *ctx);
struct nc_session *nc_connect_libssl(void *tls, struct ly_ctx *ctx);
/** @} Client TLS */
#endif /* NC_ENABLED_TLS */
#endif /* NC_ENABLED_SSH_TLS */
/**
* @addtogroup client_session
@ -584,6 +553,17 @@ NC_MSG_TYPE nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64
*/
NC_MSG_TYPE nc_recv_notif(struct nc_session *session, int timeout, struct lyd_node **envp, struct lyd_node **op);
/**
* @brief Callback for receiving notifications in a separate thread.
*
* @param[in] session NC session that received the notification.
* @param[in] envp Notification envelope data tree.
* @param[in] op Notification body data tree.
* @param[in] user_data Arbitrary user data passed to the callback.
*/
typedef void (*nc_notif_dispatch_clb)(struct nc_session *session, const struct lyd_node *envp, const struct lyd_node *op,
void *user_data);
/**
* @brief Receive NETCONF Notifications in a separate thread until the session is terminated
* or \<notificationComplete\> is received.
@ -594,8 +574,23 @@ NC_MSG_TYPE nc_recv_notif(struct nc_session *session, int timeout, struct lyd_no
* and the notification data.
* @return 0 if the thread was successfully created, -1 on error.
*/
int nc_recv_notif_dispatch(struct nc_session *session,
void (*notif_clb)(struct nc_session *session, const struct lyd_node *envp, const struct lyd_node *op));
int nc_recv_notif_dispatch(struct nc_session *session, nc_notif_dispatch_clb notif_clb);
/**
* @brief Receive NETCONF Notifications in a separate thread until the session is terminated
* or \<notificationComplete\> is received. Similar to ::nc_recv_notif_dispatch() but allows
* to set arbitrary user data that can be freed as well.
*
* @param[in] session Netconf session to read notifications from.
* @param[in] notif_clb Callback that is called for every received notification (including
* \<notificationComplete\>). Parameters are the session the notification was received on
* and the notification data.
* @param[in] user_data Arbitrary user data.
* @param[in] free_data Callback for freeing the user data after notif thread exit.
* @return 0 if the thread was successfully created, -1 on error.
*/
int nc_recv_notif_dispatch_data(struct nc_session *session, nc_notif_dispatch_clb notif_clb, void *user_data,
void (*free_data)(void *));
/**
* @brief Send NETCONF RPC message via the session.
@ -622,6 +617,58 @@ NC_MSG_TYPE nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int time
*/
void nc_client_session_set_not_strict(struct nc_session *session);
/**
* @brief Callback for monitoring client sessions.
*
* This callback is called whenever the client finds out that a session was disconnected by the server.
*
* @param[in] session Disconnected session. The session will not be freed, so it is safe to call nc_session_free() on it.
* @param[in] user_data Arbitrary user data passed to the callback.
*/
typedef void (*nc_client_monitoring_clb)(struct nc_session *session, void *user_data);
/**
* @brief Start a thread that monitors client sessions.
*
* Once the thread is running, new sessions will be monitored automatically.
*
* Note that once you start the monitoring thread, any other client thread that
* calls ::nc_session_free() needs to share the same thread context (or be the same thread)
* as the thread that called this function (see ::nc_client_set_thread_context()).
*
* @param[in] monitoring_clb Callback called whenever a session is terminated.
* @param[in] user_data Arbitrary user data passed to the callback.
* @param[in] free_data Callback for freeing the user data after monitoring thread exits.
* @return 0 on success, 1 on error.
*/
int nc_client_monitoring_thread_start(nc_client_monitoring_clb monitoring_clb, void *user_data, void (*free_data)(void *));
/**
* @brief Stop the client session monitoring thread.
*/
void nc_client_monitoring_thread_stop(void);
/**
* @brief Enable or disable TCP keepalives. Only affects new sessions.
*
* Client-side TCP keepalives have the following default values:
* - idle time: 1 second
* - max probes: 10
* - probe interval: 5 seconds
*
* @param[in] enable Whether to enable or disable TCP keepalives.
*/
void nc_client_enable_tcp_keepalives(int enable);
/**
* @brief Set TCP keepalive options.
*
* @param[in] idle_time Time in seconds before the first keepalive probe is sent. If 0, the default value 1 is used.
* @param[in] max_probes Maximum number of keepalive probes to send before considering the connection dead. If 0, the default value 10 is used.
* @param[in] probe_interval Time in seconds between individual keepalive probes. If 0, the default value 5 is used.
*/
void nc_client_set_tcp_keepalives(uint16_t idle_time, uint16_t max_probes, uint16_t probe_interval);
/** @} Client Session */
#ifdef __cplusplus

View file

@ -3,7 +3,8 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 Call Home session client manipulation
*
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
* @copyright
* Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
@ -21,15 +22,15 @@ extern "C" {
#include <libyang/libyang.h>
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
# include <libssh/libssh.h>
#endif
#endif /* NC_ENABLED_SSH_TLS */
#include "messages_client.h"
#include "netconf.h"
#include "session.h"
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
#ifdef NC_ENABLED_SSH_TLS
/**
* @defgroup client_ch Client-side Call Home
@ -44,7 +45,9 @@ extern "C" {
*
* @param[in] timeout Timeout for receiving a new connection in milliseconds, 0 for
* non-blocking call, -1 for infinite waiting.
* @param[in] ctx Session context to use. Can be NULL.
* @param[in,out] ctx Optional custom context to use for the session. If not set, a default context is created.
* Any YANG modules not present in the context and supported by the server are loaded using \<get-schema\>
* (if supported) and/or by searching the searchpath (see ::nc_client_set_schema_searchpath()).
* @param[out] session New session.
* @return 1 on success, 0 on timeout, -1 on error.
*/
@ -52,10 +55,6 @@ int nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **sess
/** @} Client-side Call Home */
#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */
#ifdef NC_ENABLED_SSH
/**
* @defgroup client_ch_ssh Client-side Call Home on SSH
* @ingroup client_ch
@ -65,28 +64,26 @@ int nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **sess
*/
/**
* @brief Set SSH Call Home authentication hostkey check (knownhosts) callback.
* @brief Set SSH Call Home behaviour of checking the host key and adding/reading entries to/from the known_hosts file.
*
* Repetitive calling causes replacing of the previous callback and its private data. Caller is responsible for
* freeing the private data when necessary (the private data can be obtained by
* nc_client_ssh_ch_get_auth_hostkey_check_clb()).
* The default mode is ::NC_SSH_KNOWNHOSTS_ASK.
*
* @param[in] auth_hostkey_check Function to call, returns 0 on success, non-zero in error.
* If NULL, the default callback is set.
* @param[in] priv Optional private data to be passed to the callback function.
* @param[in] mode Server host key checking mode.
*/
void nc_client_ssh_ch_set_auth_hostkey_check_clb(int (*auth_hostkey_check)(const char *hostname, ssh_session session, void *priv),
void *priv);
void nc_client_ssh_ch_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_MODE mode);
/**
* @brief Get currently set SSH Call Home authentication hostkey check (knownhosts) callback and its private data
* previously set by nc_client_ssh_ch_set_auth_hostkey_check_clb().
* @brief Set SSH Call Home path to the known_hosts file for connections.
*
* @param[out] auth_hostkey_check Currently set callback, NULL in case of the default callback.
* @param[out] priv Currently set (optional) private data to be passed to the callback function.
* Repetetive calling replaces the value. If the given file doesn't exist and the process has sufficient
* rights, it gets created whenever the file is needed, otherwise an error occurs. If NULL is passed or the
* path isn't set, the default known_hosts file will be used.
*
* @param[in] path Path to the known_hosts file.
* @return 0 on success, 1 on error.
*/
void nc_client_ssh_ch_get_auth_hostkey_check_clb(int (**auth_hostkey_check)(const char *hostname, ssh_session session, void *priv),
void **priv);
int nc_client_ssh_ch_set_knownhosts_path(const char *path);
/**
* @brief Set SSH Call Home password authentication callback.
*
@ -119,8 +116,7 @@ void nc_client_ssh_ch_get_auth_password_clb(char *(**auth_password)(const char *
* nc_client_ssh_ch_get_auth_interactive_clb()).
*
* @param[in] auth_interactive Function to call for every question, returns the answer for
* authentication name with instruction and echoing prompt.
* If NULL, the default callback is set.
* authentication name with instruction and echoing prompt. If NULL, the default callback is set.
* @param[in] priv Optional private data to be passed to the callback function.
*/
void nc_client_ssh_ch_set_auth_interactive_clb(char *(*auth_interactive)(const char *auth_name, const char *instruction,
@ -145,8 +141,8 @@ void nc_client_ssh_ch_get_auth_interactive_clb(char *(**auth_interactive)(const
* freeing the private data when necessary (the private data can be obtained by
* nc_client_ssh_ch_get_auth_privkey_passphrase_clb()).
*
* @param[in] auth_privkey_passphrase Function to call for every question, returns
* the passphrase for the specific private key.
* @param[in] auth_privkey_passphrase Function to call for every question, returns the passphrase for the specific
* private key.
* @param[in] priv Optional private data to be passed to the callback function.
*/
void nc_client_ssh_ch_set_auth_privkey_passphrase_clb(char *(*auth_privkey_passphrase)(const char *privkey_path, void *priv),
@ -254,10 +250,6 @@ const char *nc_client_ssh_ch_get_username(void);
/** @} Client-side Call Home on SSH */
#endif /* NC_ENABLED_SSH */
#ifdef NC_ENABLED_TLS
/**
* @defgroup client_ch_tls Client-side Call Home on TLS
* @ingroup client_ch
@ -275,6 +267,16 @@ const char *nc_client_ssh_ch_get_username(void);
*/
int nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port);
/**
* @brief Add a new client bind and start listening on it for TLS Call Home connections coming from the specified hostname.
*
* @param[in] address IP address to bind to.
* @param[in] port Port to bind to.
* @param[in] hostname Expected server hostname, verified by TLS when connecting to it. If NULL, the check is skipped.
* @return 0 on success, -1 on error.
*/
int nc_client_tls_ch_add_bind_hostname_listen(const char *address, uint16_t port, const char *hostname);
/**
* @brief Remove a TLS listening client bind.
*
@ -298,8 +300,7 @@ int nc_client_tls_ch_set_cert_key_paths(const char *client_cert, const char *cli
* @brief Get client Call Home authentication identity - a certificate and a private key.
*
* @param[out] client_cert Path to the file containing the client certificate. Can be NULL.
* @param[out] client_key Path to the file containing the private key for the @p client_cert.
* Can be NULL.
* @param[out] client_key Path to the file containing the private key for the @p client_cert. Can be NULL.
*/
void nc_client_tls_ch_get_cert_key_paths(const char **client_cert, const char **client_key);
@ -317,35 +318,24 @@ int nc_client_tls_ch_set_trusted_ca_paths(const char *ca_file, const char *ca_di
/**
* @brief Get client Call Home trusted CA certificates.
*
* @param[out] ca_file Location of the CA certificate file used to verify server certificates.
* Can be NULL.
* @param[out] ca_dir Location of the CA certificates directory used to verify the server certificates.
* Can be NULL.
* @param[out] ca_file Location of the CA certificate file used to verify server certificates. Can be NULL.
* @param[out] ca_dir Location of the CA certificates directory used to verify the server certificates. Can be NULL.
*/
void nc_client_tls_ch_get_trusted_ca_paths(const char **ca_file, const char **ca_dir);
/**
* @brief Set client Call Home Certificate Revocation Lists.
*
* @param[in] crl_file Location of the CRL certificate file used to check for revocated certificates.
* @param[in] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
* @return 0 on success, -1 on error.
* @brief Deprecated.
*/
int nc_client_tls_ch_set_crl_paths(const char *crl_file, const char *crl_dir);
/**
* @brief Get client Call Home Certificate Revocation Lists.
*
* @param[out] crl_file Location of the CRL certificate file used to check for revocated certificates.
* Can be NULL.
* @param[out] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
* Can be NULL.
* @brief Deprecated.
*/
void nc_client_tls_ch_get_crl_paths(const char **crl_file, const char **crl_dir);
/** @} Client-side Call Home on TLS */
#endif /* NC_ENABLED_TLS */
#endif /* NC_ENABLED_SSH_TLS */
#ifdef __cplusplus
}

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,12 @@
/**
* \file session_client_tls.c
* \author Radek Krejci <rkrejci@cesnet.cz>
* \author Michal Vasko <mvasko@cesnet.cz>
* \brief libnetconf2 - TLS specific session client transport functions
* @file session_client_tls.c
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 - TLS specific session client transport functions
*
* This source is compiled only with libssl.
*
* @copyright
* Copyright (c) 2015 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -15,225 +16,30 @@
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE /* pthread_rwlock_t, strdup */
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <libyang/libyang.h>
#include <openssl/err.h>
#include <openssl/ossl_typ.h>
#include <openssl/x509.h>
#include "libnetconf.h"
#include "compat.h"
#include "config.h"
#include "log_p.h"
#include "session_client.h"
#include "session_client_ch.h"
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#define X509_STORE_CTX_get_by_subject X509_STORE_get_by_subject
#endif
#include "session_p.h"
#include "session_wrapper.h"
struct nc_client_context *nc_client_context_location(void);
int nc_session_new_ctx(struct nc_session *session, struct ly_ctx *ctx);
#define client_opts nc_client_context_location()->opts
#define tls_opts nc_client_context_location()->tls_opts
#define tls_ch_opts nc_client_context_location()->tls_ch_opts
static int tlsauth_ch;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0
static int
tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
X509_STORE_CTX *store_ctx;
X509_OBJECT *obj;
X509_NAME *subject, *issuer;
X509 *cert;
X509_CRL *crl;
X509_REVOKED *revoked;
EVP_PKEY *pubkey;
int i, n, rc;
const ASN1_TIME *next_update = NULL;
struct nc_client_tls_opts *opts;
if (!preverify_ok) {
return 0;
}
opts = (tlsauth_ch ? &tls_ch_opts : &tls_opts);
if (!opts->crl_store) {
/* nothing to check */
return 1;
}
cert = X509_STORE_CTX_get_current_cert(x509_ctx);
subject = X509_get_subject_name(cert);
issuer = X509_get_issuer_name(cert);
/* try to retrieve a CRL corresponding to the _subject_ of
* the current certificate in order to verify it's integrity */
store_ctx = X509_STORE_CTX_new();
obj = X509_OBJECT_new();
X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL);
rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, subject, obj);
X509_STORE_CTX_free(store_ctx);
crl = X509_OBJECT_get0_X509_CRL(obj);
if ((rc > 0) && crl) {
next_update = X509_CRL_get0_nextUpdate(crl);
/* verify the signature on this CRL */
pubkey = X509_get_pubkey(cert);
if (X509_CRL_verify(crl, pubkey) <= 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
X509_OBJECT_free(obj);
if (pubkey) {
EVP_PKEY_free(pubkey);
}
return 0; /* fail */
}
if (pubkey) {
EVP_PKEY_free(pubkey);
}
/* check date of CRL to make sure it's not expired */
if (!next_update) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
X509_OBJECT_free(obj);
return 0; /* fail */
}
if (X509_cmp_current_time(next_update) < 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
X509_OBJECT_free(obj);
return 0; /* fail */
}
X509_OBJECT_free(obj);
}
/* try to retrieve a CRL corresponding to the _issuer_ of
* the current certificate in order to check for revocation */
store_ctx = X509_STORE_CTX_new();
obj = X509_OBJECT_new();
X509_STORE_CTX_init(store_ctx, opts->crl_store, NULL, NULL);
rc = X509_STORE_CTX_get_by_subject(store_ctx, X509_LU_CRL, issuer, obj);
X509_STORE_CTX_free(store_ctx);
crl = X509_OBJECT_get0_X509_CRL(obj);
if ((rc > 0) && crl) {
/* check if the current certificate is revoked by this CRL */
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
for (i = 0; i < n; i++) {
revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
if (ASN1_INTEGER_cmp(X509_REVOKED_get0_serialNumber(revoked), X509_get_serialNumber(cert)) == 0) {
ERR(NULL, "Certificate revoked!");
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
X509_OBJECT_free(obj);
return 0; /* fail */
}
}
X509_OBJECT_free(obj);
}
return 1; /* success */
}
#else
static int
tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
X509_STORE_CTX store_ctx;
X509_OBJECT obj;
X509_NAME *subject, *issuer;
X509 *cert;
X509_CRL *crl;
X509_REVOKED *revoked;
EVP_PKEY *pubkey;
int i, n, rc;
ASN1_TIME *next_update = NULL;
struct nc_client_tls_opts *opts;
if (!preverify_ok) {
return 0;
}
opts = (tlsauth_ch ? &tls_ch_opts : &tls_opts);
if (!opts->crl_store) {
/* nothing to check */
return 1;
}
cert = X509_STORE_CTX_get_current_cert(x509_ctx);
subject = X509_get_subject_name(cert);
issuer = X509_get_issuer_name(cert);
/* try to retrieve a CRL corresponding to the _subject_ of
* the current certificate in order to verify it's integrity */
memset((char *)&obj, 0, sizeof obj);
X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL);
rc = X509_STORE_CTX_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
X509_STORE_CTX_cleanup(&store_ctx);
crl = obj.data.crl;
if ((rc > 0) && crl) {
next_update = X509_CRL_get_nextUpdate(crl);
/* verify the signature on this CRL */
pubkey = X509_get_pubkey(cert);
if (X509_CRL_verify(crl, pubkey) <= 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
X509_OBJECT_free_contents(&obj);
if (pubkey) {
EVP_PKEY_free(pubkey);
}
return 0; /* fail */
}
if (pubkey) {
EVP_PKEY_free(pubkey);
}
/* check date of CRL to make sure it's not expired */
if (!next_update) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
X509_OBJECT_free_contents(&obj);
return 0; /* fail */
}
if (X509_cmp_current_time(next_update) < 0) {
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
X509_OBJECT_free_contents(&obj);
return 0; /* fail */
}
X509_OBJECT_free_contents(&obj);
}
/* try to retrieve a CRL corresponding to the _issuer_ of
* the current certificate in order to check for revocation */
memset((char *)&obj, 0, sizeof obj);
X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL);
rc = X509_STORE_CTX_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
X509_STORE_CTX_cleanup(&store_ctx);
crl = obj.data.crl;
if ((rc > 0) && crl) {
/* check if the current certificate is revoked by this CRL */
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
for (i = 0; i < n; i++) {
revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(cert)) == 0) {
ERR(NULL, "Certificate revoked!");
X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
X509_OBJECT_free_contents(&obj);
return 0; /* fail */
}
}
X509_OBJECT_free_contents(&obj);
}
return 1; /* success */
}
#endif
void
_nc_client_tls_destroy_opts(struct nc_client_tls_opts *opts)
{
@ -241,12 +47,6 @@ _nc_client_tls_destroy_opts(struct nc_client_tls_opts *opts)
free(opts->key_path);
free(opts->ca_file);
free(opts->ca_dir);
SSL_CTX_free(opts->tls_ctx);
free(opts->crl_file);
free(opts->crl_dir);
X509_STORE_free(opts->crl_store);
memset(opts, 0, sizeof *opts);
}
@ -260,32 +60,21 @@ nc_client_tls_destroy_opts(void)
static int
_nc_client_tls_set_cert_key_paths(const char *client_cert, const char *client_key, struct nc_client_tls_opts *opts)
{
if (!client_cert) {
ERRARG("client_cert");
return -1;
}
NC_CHECK_ARG_RET(NULL, client_cert, -1);
free(opts->cert_path);
free(opts->key_path);
opts->cert_path = strdup(client_cert);
if (!opts->cert_path) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!opts->cert_path, -1);
if (client_key) {
opts->key_path = strdup(client_key);
if (!opts->key_path) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!opts->key_path, -1);
} else {
opts->key_path = NULL;
}
opts->tls_ctx_change = 1;
return 0;
}
@ -305,7 +94,7 @@ static void
_nc_client_tls_get_cert_key_paths(const char **client_cert, const char **client_key, struct nc_client_tls_opts *opts)
{
if (!client_cert && !client_key) {
ERRARG("client_cert and client_key");
ERRARG(NULL, "client_cert and client_key");
return;
}
@ -333,7 +122,7 @@ static int
_nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir, struct nc_client_tls_opts *opts)
{
if (!ca_file && !ca_dir) {
ERRARG("ca_file and ca_dir");
ERRARG(NULL, "ca_file and ca_dir");
return -1;
}
@ -342,26 +131,18 @@ _nc_client_tls_set_trusted_ca_paths(const char *ca_file, const char *ca_dir, str
if (ca_file) {
opts->ca_file = strdup(ca_file);
if (!opts->ca_file) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!opts->ca_file, -1);
} else {
opts->ca_file = NULL;
}
if (ca_dir) {
opts->ca_dir = strdup(ca_dir);
if (!opts->ca_dir) {
ERRMEM;
return -1;
}
NC_CHECK_ERRMEM_RET(!opts->ca_dir, -1);
} else {
opts->ca_dir = NULL;
}
opts->tls_ctx_change = 1;
return 0;
}
@ -381,7 +162,7 @@ static void
_nc_client_tls_get_trusted_ca_paths(const char **ca_file, const char **ca_dir, struct nc_client_tls_opts *opts)
{
if (!ca_file && !ca_dir) {
ERRARG("ca_file and ca_dir");
ERRARG(NULL, "ca_file and ca_dir");
return;
}
@ -405,200 +186,197 @@ nc_client_tls_ch_get_trusted_ca_paths(const char **ca_file, const char **ca_dir)
_nc_client_tls_get_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts);
}
static int
_nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir, struct nc_client_tls_opts *opts)
API int
nc_client_tls_set_crl_paths(const char *UNUSED(crl_file), const char *UNUSED(crl_dir))
{
if (!crl_file && !crl_dir) {
ERRARG("crl_file and crl_dir");
ERR(NULL, "nc_client_tls_set_crl_paths() is deprecated, do not use it.");
return -1;
}
free(opts->crl_file);
free(opts->crl_dir);
if (crl_file) {
opts->crl_file = strdup(crl_file);
if (!opts->crl_file) {
ERRMEM;
return -1;
}
} else {
opts->crl_file = NULL;
}
if (crl_dir) {
opts->crl_dir = strdup(crl_dir);
if (!opts->crl_dir) {
ERRMEM;
return -1;
}
} else {
opts->crl_dir = NULL;
}
opts->crl_store_change = 1;
return 0;
}
API int
nc_client_tls_set_crl_paths(const char *crl_file, const char *crl_dir)
nc_client_tls_ch_set_crl_paths(const char *UNUSED(crl_file), const char *UNUSED(crl_dir))
{
return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_opts);
ERR(NULL, "nc_client_tls_ch_set_crl_paths() is deprecated, do not use it.");
return -1;
}
API int
nc_client_tls_ch_set_crl_paths(const char *crl_file, const char *crl_dir)
API void
nc_client_tls_get_crl_paths(const char **UNUSED(crl_file), const char **UNUSED(crl_dir))
{
return _nc_client_tls_set_crl_paths(crl_file, crl_dir, &tls_ch_opts);
}
static void
_nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir, struct nc_client_tls_opts *opts)
{
if (!crl_file && !crl_dir) {
ERRARG("crl_file and crl_dir");
ERR(NULL, "nc_client_tls_get_crl_paths() is deprecated, do not use it.");
return;
}
if (crl_file) {
*crl_file = opts->crl_file;
}
if (crl_dir) {
*crl_dir = opts->crl_dir;
}
}
API void
nc_client_tls_get_crl_paths(const char **crl_file, const char **crl_dir)
nc_client_tls_ch_get_crl_paths(const char **UNUSED(crl_file), const char **UNUSED(crl_dir))
{
_nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_opts);
}
API void
nc_client_tls_ch_get_crl_paths(const char **crl_file, const char **crl_dir)
{
_nc_client_tls_get_crl_paths(crl_file, crl_dir, &tls_ch_opts);
ERR(NULL, "nc_client_tls_ch_get_crl_paths() is deprecated, do not use it.");
return;
}
API int
nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port)
{
return nc_client_ch_add_bind_listen(address, port, NC_TI_OPENSSL);
return nc_client_ch_add_bind_listen(address, port, NULL, NC_TI_TLS);
}
API int
nc_client_tls_ch_add_bind_hostname_listen(const char *address, uint16_t port, const char *hostname)
{
return nc_client_ch_add_bind_listen(address, port, hostname, NC_TI_TLS);
}
API int
nc_client_tls_ch_del_bind(const char *address, uint16_t port)
{
return nc_client_ch_del_bind(address, port, NC_TI_OPENSSL);
return nc_client_ch_del_bind(address, port, NC_TI_TLS);
}
static int
nc_client_tls_update_opts(struct nc_client_tls_opts *opts)
nc_client_tls_connect_check(int connect_ret, void *tls_session, const char *peername)
{
char *key;
X509_LOOKUP *lookup;
uint32_t verify;
char *err;
if (!opts->tls_ctx || opts->tls_ctx_change) {
SSL_CTX_free(opts->tls_ctx);
/* check certificate verification result */
verify = nc_tls_get_verify_result_wrap(tls_session);
if (!verify && (connect_ret == 1)) {
VRB(NULL, "Server certificate verified (domain \"%s\").", peername);
} else if (verify) {
err = nc_tls_verify_error_string_wrap(verify);
ERR(NULL, "Server certificate error (%s).", err);
free(err);
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0
/* prepare global SSL context, highest available method is negotiated autmatically */
if (!(opts->tls_ctx = SSL_CTX_new(TLS_client_method())))
#else
/* prepare global SSL context, allow only mandatory TLS 1.2 */
if (!(opts->tls_ctx = SSL_CTX_new(TLSv1_2_client_method())))
#endif
/* check TLS connection result */
if (connect_ret != 1) {
nc_client_tls_print_connect_err_wrap(connect_ret, peername, tls_session);
}
return connect_ret;
}
static void *
nc_client_tls_session_new(int sock, const char *host, int timeout, struct nc_client_tls_opts *opts, void **out_tls_cfg, struct nc_tls_ctx *tls_ctx)
{
ERR(NULL, "Unable to create OpenSSL context (%s).", ERR_reason_error_string(ERR_get_error()));
return -1;
}
SSL_CTX_set_verify(opts->tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
int ret = 0, sock_tmp = sock;
struct timespec ts_timeout;
void *tls_session, *tls_cfg, *cli_cert, *cli_pkey, *cert_store, *crl_store;
/* get peer certificate */
if (SSL_CTX_use_certificate_file(opts->tls_ctx, opts->cert_path, SSL_FILETYPE_PEM) != 1) {
ERR(NULL, "Loading the client certificate from \'%s\' failed (%s).", opts->cert_path,
ERR_reason_error_string(ERR_get_error()));
return -1;
tls_session = tls_cfg = cli_cert = cli_pkey = cert_store = crl_store = NULL;
/* prepare TLS context from which a session will be created */
tls_cfg = nc_tls_config_new_wrap(NC_CLIENT);
if (!tls_cfg) {
goto fail;
}
/* if the file with private key not specified, expect that the private key is stored with the certificate */
if (!opts->key_path) {
key = opts->cert_path;
} else {
key = opts->key_path;
}
if (SSL_CTX_use_PrivateKey_file(opts->tls_ctx, key, SSL_FILETYPE_PEM) != 1) {
ERR(NULL, "Loading the client priavte key from \'%s\' failed (%s).", key,
ERR_reason_error_string(ERR_get_error()));
return -1;
/* opaque CA/CRL certificate store */
cert_store = nc_tls_cert_store_new_wrap();
if (!cert_store) {
goto fail;
}
if (!SSL_CTX_load_verify_locations(opts->tls_ctx, opts->ca_file, opts->ca_dir)) {
ERR(NULL, "Failed to load the locations of trusted CA certificates (%s).",
ERR_reason_error_string(ERR_get_error()));
return -1;
/* load client's key and certificate */
if (nc_client_tls_load_cert_key_wrap(opts->cert_path, opts->key_path, &cli_cert, &cli_pkey)) {
goto fail;
}
/* load trusted CA certificates */
if (nc_client_tls_load_trusted_certs_wrap(cert_store, opts->ca_file, opts->ca_dir)) {
goto fail;
}
/* load CRLs from set certificates' extensions */
if (nc_session_tls_crl_from_cert_ext_fetch(cli_cert, cert_store, &crl_store)) {
goto fail;
}
/* set client's verify mode flags */
nc_client_tls_set_verify_wrap(tls_cfg);
/* init TLS context and store data which may be needed later in it */
if (nc_tls_init_ctx_wrap(cli_cert, cli_pkey, cert_store, crl_store, tls_ctx)) {
goto fail;
}
/* memory is managed by context now */
cli_cert = cli_pkey = cert_store = crl_store = NULL;
/* setup config from ctx */
if (nc_tls_setup_config_from_ctx_wrap(tls_ctx, NC_CLIENT, tls_cfg)) {
goto fail;
}
/* session from config */
tls_session = nc_tls_session_new_wrap(tls_cfg);
if (!tls_session) {
goto fail;
}
/* set session fd */
nc_tls_set_fd_wrap(tls_session, sock, tls_ctx);
sock = -1;
/* set session hostname to check against in the server cert */
if (nc_client_tls_set_hostname_wrap(tls_session, host)) {
goto fail;
}
/* handshake */
if (timeout > -1) {
nc_timeouttime_get(&ts_timeout, timeout);
}
while ((ret = nc_client_tls_handshake_step_wrap(tls_session, sock_tmp)) == 0) {
usleep(NC_TIMEOUT_STEP);
if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
ERR(NULL, "SSL connect timeout.");
goto fail;
}
}
if (opts->crl_store_change || (!opts->crl_store && (opts->crl_file || opts->crl_dir))) {
/* set the revocation store with the correct paths for the callback */
X509_STORE_free(opts->crl_store);
opts->crl_store = X509_STORE_new();
if (!opts->crl_store) {
ERR(NULL, "Unable to create a certificate store (%s).", ERR_reason_error_string(ERR_get_error()));
return -1;
/* check if handshake was ok */
if (nc_client_tls_connect_check(ret, tls_session, host) != 1) {
goto fail;
}
#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0
/* whaveter this does... */
opts->crl_store->cache = 0;
#endif
*out_tls_cfg = tls_cfg;
return tls_session;
if (opts->crl_file) {
if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_file()))) {
ERR(NULL, "Failed to add lookup method to CRL checking.");
return -1;
}
if (X509_LOOKUP_add_dir(lookup, opts->crl_file, X509_FILETYPE_PEM) != 1) {
ERR(NULL, "Failed to add the revocation lookup file \"%s\".", opts->crl_file);
return -1;
}
fail:
if (sock > -1) {
close(sock);
}
if (opts->crl_dir) {
if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_hash_dir()))) {
ERR(NULL, "Failed to add lookup method to CRL checking.");
return -1;
}
if (X509_LOOKUP_add_dir(lookup, opts->crl_dir, X509_FILETYPE_PEM) != 1) {
ERR(NULL, "Failed to add the revocation lookup directory \"%s\".", opts->crl_dir);
return -1;
}
}
}
return 0;
nc_tls_session_destroy_wrap(tls_session);
nc_tls_cert_destroy_wrap(cli_cert);
nc_tls_privkey_destroy_wrap(cli_pkey);
nc_tls_cert_store_destroy_wrap(cert_store);
nc_tls_crl_store_destroy_wrap(crl_store);
nc_tls_config_destroy_wrap(tls_cfg);
return NULL;
}
API struct nc_session *
nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx)
{
struct nc_session *session = NULL;
int sock, verify, ret;
unsigned long tls_err;
struct timespec ts_timeout, ts_cur;
const char *peername;
int sock;
char *ip_host = NULL;
void *tls_cfg = NULL;
struct nc_tls_ctx tls_ctx = {0};
if (!tls_opts.cert_path || (!tls_opts.ca_file && !tls_opts.ca_dir)) {
ERRINIT;
if (!tls_opts.cert_path) {
ERR(NULL, "Client certificate not set.");
return NULL;
} else if (!tls_opts.ca_file && !tls_opts.ca_dir) {
ERR(NULL, "Certificate authority certificates not set.");
return NULL;
}
/* process parameters */
if (!host || strisempty(host)) {
if (!host || (host[0] == '\0')) {
host = "localhost";
}
@ -606,93 +384,32 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx)
port = NC_PORT_TLS;
}
/* create/update TLS structures */
if (nc_client_tls_update_opts(&tls_opts)) {
return NULL;
}
/* prepare session structure */
session = nc_new_session(NC_CLIENT, 0);
if (!session) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!session, NULL);
session->status = NC_STATUS_STARTING;
/* fill the session */
session->ti_type = NC_TI_OPENSSL;
if (!(session->ti.tls = SSL_new(tls_opts.tls_ctx))) {
ERR(NULL, "Failed to create a new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
goto fail;
}
/* create and assign socket */
sock = nc_sock_connect(host, port, -1, &client_opts.ka, NULL, &ip_host);
sock = nc_sock_connect(NULL, 0, host, port, -1, &client_opts.ka, NULL, &ip_host);
if (sock == -1) {
ERR(NULL, "Unable to connect to %s:%u (%s).", host, port, strerror(errno));
goto fail;
}
SSL_set_fd(session->ti.tls, sock);
/* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0
/* server identity (hostname) verification */
if (!SSL_set1_host(session->ti.tls, host)) {
ERR(NULL, "Failed to set expected server hostname.");
/* fill the session */
session->ti_type = NC_TI_TLS;
if (!(session->ti.tls.session = nc_client_tls_session_new(sock, host, NC_TRANSPORT_TIMEOUT, &tls_opts, &tls_cfg, &tls_ctx))) {
goto fail;
}
#endif
session->ti.tls.config = tls_cfg;
/* connect and perform the handshake */
nc_gettimespec_mono(&ts_timeout);
nc_addtimespec(&ts_timeout, NC_TRANSPORT_TIMEOUT);
tlsauth_ch = 0;
while (((ret = SSL_connect(session->ti.tls)) != 1) && (SSL_get_error(session->ti.tls, ret) == SSL_ERROR_WANT_READ)) {
usleep(NC_TIMEOUT_STEP);
nc_gettimespec_mono(&ts_cur);
if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
ERR(NULL, "SSL_connect timeout.");
/* memory belongs to session */
memcpy(&session->ti.tls.ctx, &tls_ctx, sizeof tls_ctx);
memset(&tls_ctx, 0, sizeof tls_ctx);
if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
goto fail;
}
}
if (ret != 1) {
switch (SSL_get_error(session->ti.tls, ret)) {
case SSL_ERROR_SYSCALL:
ERR(NULL, "SSL_connect failed (%s).", errno ? strerror(errno) : "unexpected EOF");
break;
case SSL_ERROR_SSL:
tls_err = ERR_get_error();
ERR(NULL, "SSL_connect failed (%s).", ERR_reason_error_string(tls_err));
break;
default:
ERR(NULL, "SSL_connect failed.");
break;
}
goto fail;
}
/* check certificate verification result */
verify = SSL_get_verify_result(session->ti.tls);
switch (verify) {
case X509_V_OK:
#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0
peername = SSL_get0_peername(session->ti.tls);
VRB(NULL, "Server certificate successfully verified (domain \"%s\").", peername ? peername : "<unknown>");
#else
(void)peername;
VRB(NULL, "Server certificate successfully verified.");
#endif
break;
default:
WRN(NULL, "Server certificate verification problem (%s).", X509_verify_cert_error_string(verify));
}
if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
goto fail;
}
ctx = session->ctx;
/* NETCONF handshake */
if (nc_handshake_io(session) != NC_MSG_HELLO) {
@ -704,46 +421,58 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx)
goto fail;
}
/* store information into session and the dictionary */
lydict_insert_zc(ctx, ip_host, &session->host);
/* start monitoring the session if the monitoring thread is running */
if (nc_client_monitoring_session_start(session)) {
goto fail;
}
/* store information into session */
session->host = ip_host;
session->port = port;
lydict_insert(ctx, "certificate-based", 0, &session->username);
session->username = strdup("certificate-based");
return session;
fail:
free(ip_host);
nc_session_free(session, NULL);
nc_tls_ctx_destroy_wrap(&tls_ctx);
return NULL;
}
API struct nc_session *
nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
nc_connect_libssl(void *UNUSED(tls), struct ly_ctx *UNUSED(ctx))
{
struct nc_session *session;
if (!tls) {
ERRARG("tls");
return NULL;
} else if (!SSL_is_init_finished(tls)) {
ERR(NULL, "Supplied TLS session is not fully connected!");
ERR(NULL, "nc_connect_libssl() is deprecated, do not use it.");
return NULL;
}
struct nc_session *
nc_accept_callhome_tls_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout, const char *peername)
{
struct nc_session *session = NULL;
void *tls_cfg = NULL;
struct nc_tls_ctx tls_ctx = {0};
/* prepare session structure */
session = nc_new_session(NC_CLIENT, 0);
if (!session) {
ERRMEM;
return NULL;
}
NC_CHECK_ERRMEM_RET(!session, NULL);
session->status = NC_STATUS_STARTING;
session->ti_type = NC_TI_OPENSSL;
session->ti.tls = tls;
if (nc_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
/* fill the session */
session->ti_type = NC_TI_TLS;
if (!(session->ti.tls.session = nc_client_tls_session_new(sock, peername, timeout, &tls_ch_opts, &tls_cfg, &tls_ctx))) {
goto fail;
}
session->ti.tls.config = tls_cfg;
/* memory belongs to session */
memcpy(&session->ti.tls.ctx, &tls_ctx, sizeof tls_ctx);
memset(&tls_ctx, 0, sizeof tls_ctx);
if (nc_client_session_new_ctx(session, ctx) != EXIT_SUCCESS) {
goto fail;
}
ctx = session->ctx;
/* NETCONF handshake */
if (nc_handshake_io(session) != NC_MSG_HELLO) {
@ -755,94 +484,22 @@ nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
goto fail;
}
session->flags |= NC_SESSION_CALLHOME;
/* start monitoring the session if the monitoring thread is running */
if (nc_client_monitoring_session_start(session)) {
goto fail;
}
/* store information into session */
session->host = strdup(host);
session->port = port;
session->username = strdup("certificate-based");
return session;
fail:
session->ti.tls = NULL;
nc_session_free(session, NULL);
nc_tls_ctx_destroy_wrap(&tls_ctx);
return NULL;
}
struct nc_session *
nc_accept_callhome_tls_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout)
{
int verify, ret;
SSL *tls = NULL;
struct nc_session *session = NULL;
struct timespec ts_timeout, ts_cur;
if (nc_client_tls_update_opts(&tls_ch_opts)) {
goto cleanup;
}
if (!(tls = SSL_new(tls_ch_opts.tls_ctx))) {
ERR(NULL, "Failed to create new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
goto cleanup;
}
SSL_set_fd(tls, sock);
/* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
SSL_set_mode(tls, SSL_MODE_AUTO_RETRY);
/* connect and perform the handshake */
if (timeout > -1) {
nc_gettimespec_mono(&ts_timeout);
nc_addtimespec(&ts_timeout, timeout);
}
tlsauth_ch = 1;
while (((ret = SSL_connect(tls)) == -1) && (SSL_get_error(tls, ret) == SSL_ERROR_WANT_READ)) {
usleep(NC_TIMEOUT_STEP);
if (timeout > -1) {
nc_gettimespec_mono(&ts_cur);
if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) {
ERR(NULL, "SSL_connect timeout.");
goto cleanup;
}
}
}
if (ret != 1) {
switch (SSL_get_error(tls, ret)) {
case SSL_ERROR_SYSCALL:
ERR(NULL, "SSL_connect failed (%s).", strerror(errno));
break;
case SSL_ERROR_SSL:
ERR(NULL, "SSL_connect failed (%s).", ERR_reason_error_string(ERR_get_error()));
break;
default:
ERR(NULL, "SSL_connect failed.");
break;
}
goto cleanup;
}
/* check certificate verification result */
verify = SSL_get_verify_result(tls);
switch (verify) {
case X509_V_OK:
VRB(NULL, "Server certificate successfully verified.");
break;
default:
WRN(NULL, "Server certificate verification problem (%s).", X509_verify_cert_error_string(verify));
}
/* connect */
session = nc_connect_libssl(tls, ctx);
if (!session) {
goto cleanup;
}
session->flags |= NC_SESSION_CALLHOME;
/* store information into session and the dictionary */
lydict_insert(session->ctx, host, 0, &session->host);
session->port = port;
lydict_insert(session->ctx, "certificate-based", 0, &session->username);
cleanup:
if (!session) {
SSL_free(tls);
close(sock);
}
return session;
}

2014
src/session_mbedtls.c Normal file

File diff suppressed because it is too large Load diff

1494
src/session_openssl.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 session server manipulation
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -21,20 +22,17 @@ extern "C" {
#include <libyang/libyang.h>
#include <stdint.h>
#ifdef NC_ENABLED_TLS
# include <openssl/x509.h>
#endif
#ifdef NC_ENABLED_SSH
# include <libssh/callbacks.h>
# include <libssh/libssh.h>
# include <libssh/server.h>
#endif
#include <sys/types.h>
#include "netconf.h"
#include "session.h"
#ifdef NC_ENABLED_SSH_TLS
# include <libssh/callbacks.h>
# include <libssh/libssh.h>
# include <libssh/server.h>
#endif /* NC_ENABLED_SSH_TLS */
/**
* @defgroup server_session Server Session
* @ingroup server
@ -57,6 +55,21 @@ extern "C" {
*/
typedef struct nc_server_reply *(*nc_rpc_clb)(struct lyd_node *rpc, struct nc_session *session);
/**
* @brief Callback for certificate expiration notification.
*
* This callback is called when a certificate expiration notification is generated.
* It is up to the user to decide what to do with the notification.
*
* In case an error occurs and you wish to terminate the notification thread,
* call nc_server_notif_cert_expiration_thread_stop().
*
* @param[in] expiration_time Expiration time of the certificate obtained via ly_time_time2str().
* @param[in] xpath Xpath of the certificate. Can be used to create the notification data.
* @param[in] user_data Arbitrary user data.
*/
typedef void (*nc_cert_exp_notif_clb)(const char *expiration_time, const char *xpath, void *user_data);
/**
* @brief Set the termination reason for a session. Use only in #nc_rpc_clb callbacks.
*
@ -85,47 +98,48 @@ void nc_session_set_status(struct nc_session *session, NC_STATUS status);
* @brief Set a global nc_rpc_clb that is called if the particular RPC request is
* received and the private field in the corresponding RPC schema node is NULL.
*
* If this callback is set, the default callbacks for "get-schema" and "close-session" are not used.
*
* @param[in] clb An user-defined nc_rpc_clb function callback, NULL to default.
*/
void nc_set_global_rpc_clb(nc_rpc_clb clb);
/**
* @brief Default RPC callback used for "ietf-netconf-monitoring:get-schema" RPC if no other specific
* or global callback is set.
*
* @param[in] rpc Received RPC.
* @param[in] session NC session @p rpc was received on.
* @return Server reply.
*/
struct nc_server_reply *nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session);
/**
* @brief Default RPC callback used for "ietf-netconf:close-session" RPC if no other specific
* or global callback is set.
*
* @param[in] rpc Received RPC.
* @param[in] session NC session @p rpc was received on.
* @return Server reply.
*/
struct nc_server_reply *nc_clb_default_close_session(struct lyd_node *rpc, struct nc_session *session);
/** @} Server Session */
/**
* @addtogroup server
* @defgroup server_functions Server Functions
* @ingroup server
* @{
*/
/**
* @brief Initialize libssh and/or libssl/libcrypto and the server using a libyang context.
* @brief Initialize libssh and/or libssl/libcrypto and the server.
*
* The context is not modified internally, only its dictionary is used for holding
* all the strings, which is thread-safe. Reading models is considered thread-safe
* as models cannot be removed and are rarely modified (augments or deviations).
* Must be called before other nc_server* functions.
*
* If the RPC callbacks on schema nodes (mentioned in @ref howtoserver) are modified after
* server initialization with that particular context, they will be called (changes
* will take effect). However, there could be race conditions as the access to
* these callbacks is not thread-safe.
*
* Server capabilities are generated based on its content. Changing the context
* in ways that result in changed capabilities (adding models, changing features)
* is discouraged after sessions are established as it is not possible to change
* capabilities of a session.
*
* This context can safely be destroyed only after calling the last libnetconf2
* function in an application.
*
* Supported RPCs of models in the context are expected to have their callback
* in the corresponding RPC schema node set to a nc_rpc_clb function callback using nc_set_rpc_callback().
* This callback is called by nc_ps_poll() if the particular RPC request is
* received. Callbacks for ietf-netconf:get-schema (supporting YANG and YIN format
* only) and ietf-netconf:close-session are set internally if left unset.
*
* @param[in] ctx Core NETCONF server context.
* @return 0 on success, -1 on error.
*/
int nc_server_init(struct ly_ctx *ctx);
int nc_server_init(void);
/**
* @brief Destroy any dynamically allocated libssh and/or libssl/libcrypto and
@ -133,6 +147,23 @@ int nc_server_init(struct ly_ctx *ctx);
*/
void nc_server_destroy(void);
/**
* @brief Initialize a context which can serve as a default server context.
*
* Loads the default modules ietf-netconf and ietf-netconf-monitoring and their enabled features - ietf-netconf
* enabled features are : writable-running, candidate, rollback-on-error, validate, startup, url, xpath, confirmed-commit and
* ietf-netconf-monitoring has no features.
*
* If ctx is :
* - NULL: a new context will be created and if the call is successful you have to free it,
* - non NULL: context will be searched for the two modules and their features
* and if anything is missing, it will be implemented.
*
* @param[in,out] ctx Optional context in which the modules will be loaded. Created if ctx is null.
* @return 0 on success, -1 on error.
*/
int nc_server_init_ctx(struct ly_ctx **ctx);
/**
* @brief Set the with-defaults capability extra parameters.
*
@ -154,8 +185,8 @@ int nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported);
*
* At least one argument must be non-NULL.
*
* @param[in,out] basic_mode basic-mode parameter.
* @param[in,out] also_supported also-supported parameter.
* @param[out] basic_mode basic-mode parameter.
* @param[out] also_supported also-supported parameter.
*/
void nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported);
@ -181,36 +212,6 @@ int nc_server_set_capability(const char *value);
void nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
void (*free_user_data)(void *user_data));
/**
* @brief Set server timeout for receiving a hello message.
*
* @param[in] hello_timeout Hello message timeout. 0 for infinite waiting.
*/
void nc_server_set_hello_timeout(uint16_t hello_timeout);
/**
* @brief get server timeout for receiving a hello message.
*
* @return Hello message timeout, 0 is infinite.
*/
uint16_t nc_server_get_hello_timeout(void);
/**
* @brief Set server timeout for dropping an idle session.
*
* @param[in] idle_timeout Idle session timeout. 0 to never drop a session
* because of inactivity.
*/
void nc_server_set_idle_timeout(uint16_t idle_timeout);
/**
* @brief Get server timeout for dropping an idle session.
*
* @return Idle session timeout, 0 for for never dropping
* a session because of inactivity.
*/
uint16_t nc_server_get_idle_timeout(void);
/**
* @brief Get all the server capabilities including all the schemas.
*
@ -218,9 +219,9 @@ uint16_t nc_server_get_idle_timeout(void);
* server options.
*
* @param[in] ctx Context to read most capabilities from.
* @return Array of capabilities stored in the @p ctx dictionary, NULL on error.
* @return Array of capabilities, NULL on error.
*/
const char **nc_server_get_cpblts(struct ly_ctx *ctx);
char **nc_server_get_cpblts(const struct ly_ctx *ctx);
/**
* @brief Get the server capabilities including the schemas with the specified YANG version.
@ -231,11 +232,11 @@ const char **nc_server_get_cpblts(struct ly_ctx *ctx);
* @param[in] ctx Context to read most capabilities from.
* @param[in] version YANG version of the schemas to be included in result, with
* LYS_VERSION_UNDEF the result is the same as from nc_server_get_cpblts().
* @return Array of capabilities stored in the @p ctx dictionary, NULL on error.
* @return Array of capabilities, NULL on error.
*/
const char **nc_server_get_cpblts_version(struct ly_ctx *ctx, LYS_VERSION version);
char **nc_server_get_cpblts_version(const struct ly_ctx *ctx, LYS_VERSION version);
/** @} Server */
/** @} Server Functions */
/**
* @addtogroup server_session
@ -245,14 +246,18 @@ const char **nc_server_get_cpblts_version(struct ly_ctx *ctx, LYS_VERSION versio
/**
* @brief Accept a new session on a pre-established transport session.
*
* For detailed description, look at ::nc_accept().
*
* @param[in] fdin File descriptor to read (unencrypted) XML data from.
* @param[in] fdout File descriptor to write (unencrypted) XML data to.
* @param[in] username NETCONF username as provided by the transport protocol.
* @param[in] ctx Context for the session to use.
* @param[out] session New session on success.
* @return NC_MSG_HELLO on success, NC_MSG_BAD_HELLO on client \<hello\> message
* parsing fail, NC_MSG_WOULDBLOCK on timeout, NC_MSG_ERROR on other errors.
*/
NC_MSG_TYPE nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **session);
NC_MSG_TYPE nc_accept_inout(int fdin, int fdout, const char *username, const struct ly_ctx *ctx,
struct nc_session **session);
/**
* @brief Create an empty structure for polling sessions.
@ -298,6 +303,26 @@ int nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session);
*/
struct nc_session *nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx);
/**
* @brief Callback for finding a session in a pollsession structure.
*
* @param[in] session Considered NETCONF session.
* @param[in] cb_data User data.
* @return 0 if the session does not match.
* @return non-zero if the session matches and should be returned.
*/
typedef int (*nc_ps_session_match_cb)(struct nc_session *session, void *cb_data);
/**
* @brief Find a session in a pollsession structure using a matching callback.
*
* @param[in] ps Pollsession structure to read from.
* @param[in] match_cb Matching callback to use.
* @param[in] cb_data User data passed to @p cb.
* @return Found session, NULL if none matched.
*/
struct nc_session *nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data);
/**
* @brief Learn the number of sessions in a pollsession structure.
*
@ -317,10 +342,10 @@ uint16_t nc_ps_session_count(struct nc_pollsession *ps);
#define NC_PSPOLL_SESSION_ERROR 0x0040 /**< Some session was terminated incorrectly (not by a \<close-session\> or \<kill-session\> RPC). */
#define NC_PSPOLL_ERROR 0x0080 /**< Other fatal errors (they are printed). */
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
# define NC_PSPOLL_SSH_MSG 0x00100 /**< SSH message received (and processed, if relevant, only with SSH support). */
# define NC_PSPOLL_SSH_CHANNEL 0x0200 /**< New SSH channel opened on an existing session (only with SSH support). */
#endif
#endif /* NC_ENABLED_SSH_TLS */
/**
* @brief Poll sessions and process any received RPCs.
@ -353,33 +378,10 @@ void nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *));
/** @} Server Session */
/**
* @addtogroup server
* @addtogroup server_functions
* @{
*/
/**
* @brief Add a new endpoint.
*
* Before the endpoint can accept any connections, its address and port must
* be set via nc_server_endpt_set_address() and nc_server_endpt_set_port().
*
* @param[in] name Arbitrary unique endpoint name.
* @param[in] ti Transport protocol to use.
* @return 0 on success, -1 on error.
*/
int nc_server_add_endpt(const char *name, NC_TRANSPORT_IMPL ti);
/**
* @brief Stop listening on and remove an endpoint.
*
* @param[in] name Endpoint name. NULL matches all endpoints.
* @param[in] ti Endpoint transport protocol. NULL matches any protocol.
* Redundant to set if @p name is set, endpoint names are
* unique disregarding their protocol.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti);
/**
* @brief Get the number of currently configured listening endpoints.
* Note that an ednpoint without address and/or port will be included
@ -390,75 +392,27 @@ int nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti);
int nc_server_endpt_count(void);
/**
* @brief Check if an endpoint exists.
* @brief Create a new UNIX socket endpoint and start listening.
*
* @param[in] name Endpoint name.
* @return 0 if does not exists, non-zero otherwise.
*/
int nc_server_is_endpt(const char *name);
/**
* @brief Change endpoint listening address.
*
* On error the previous listening socket (if any) is left untouched.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] address New listening address.
* @return 0 on success, -1 on error.
*/
int nc_server_endpt_set_address(const char *endpt_name, const char *address);
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
/**
* @brief Change endpoint listening port.
*
* This is only valid on SSH/TLS transport endpoint.
* On error the previous listening socket (if any) is left untouched.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] port New listening port.
* @return 0 on success, -1 on error.
*/
int nc_server_endpt_set_port(const char *endpt_name, uint16_t port);
#endif
/**
* @brief Change endpoint permissions.
*
* This is only valid on UNIX transport endpoint.
* On error the previous listening socket (if any) is left untouched.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] endpt_name Arbitrary unique identifier of the endpoint.
* @param[in] unix_socket_path Path to the listening socket.
* @param[in] mode New mode, -1 to use default.
* @param[in] uid New uid, -1 to use default.
* @param[in] gid New gid, -1 to use default.
* @return 0 on success, -1 on error.
*
* @return 0 on success, 1 on error.
*/
int nc_server_endpt_set_perms(const char *endpt_name, mode_t mode, uid_t uid, gid_t gid);
int nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid);
/**
* @brief Change endpoint keepalives state. Affects only new connections.
* @brief Deletes a UNIX socket endpoint.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] enable Whether to enable or disable keepalives.
* @return 0 on success, -1 on error.
* @param[in] endpt_name Identifier of the endpoint.
* Has no effect if the endpoint doesn't exist or if its transport is not UNIX socket.
*/
int nc_server_endpt_enable_keepalives(const char *endpt_name, int enable);
void nc_server_del_endpt_unix_socket(const char *endpt_name);
/**
* @brief Change endpoint keepalives parameters. Affects only new connections.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] idle_time Keepalive idle time in seconds, 1 by default, -1 to keep previous value.
* @param[in] max_probes Keepalive max probes sent, 10 by default, -1 to keep previous value.
* @param[in] probe_interval Keepalive probe interval in seconds, 5 by default, -1 to keep previous value.
* @return 0 on success, -1 on error.
*/
int nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int max_probes, int probe_interval);
/** @} Server */
/** @} */
/**
* @addtogroup server_session
@ -472,15 +426,26 @@ int nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int ma
* is used for waiting for transport-related data, which means this call can block
* for much longer that @p timeout, but only with slow/faulty/malicious clients.
*
* Server capabilities are generated based on the content of @p ctx. The context must
* not be destroyed before the accepted NETCONF session is freed. Basic usable context may
* be created by calling ::nc_server_init_ctx().
*
* Supported RPCs of models in the context are expected to have their callback
* in the corresponding RPC schema node set to a nc_rpc_clb function callback using ::nc_set_rpc_callback().
* This callback is called by ::nc_ps_poll() if the particular RPC request is
* received. Callbacks for ietf-netconf:get-schema (supporting YANG and YIN format
* only) and ietf-netconf:close-session are set internally if left unset.
*
* @param[in] timeout Timeout for receiving a new connection in milliseconds, 0 for
* non-blocking call, -1 for infinite waiting.
* @param[in] ctx Context for the session to use.
* @param[out] session New session.
* @return NC_MSG_HELLO on success, NC_MSG_BAD_HELLO on client \<hello\> message
* parsing fail, NC_MSG_WOULDBLOCK on timeout, NC_MSG_ERROR on other errors.
*/
NC_MSG_TYPE nc_accept(int timeout, struct nc_session **session);
NC_MSG_TYPE nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session);
#ifdef NC_ENABLED_SSH
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Accept a new NETCONF session on an SSH session of a running NETCONF @p orig_session.
@ -516,172 +481,77 @@ NC_MSG_TYPE nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_sessio
*/
/**
* @brief Add an authorized client SSH public key. This public key can be used for
* publickey authentication (for any SSH connection, even Call Home) afterwards.
* @brief Set the format of the path to authorized_keys files.
*
* @param[in] pubkey_base64 Authorized public key binary content encoded in base64.
* @param[in] type Authorized public key SSH type.
* @param[in] username Username that the client with the public key must use.
* @return 0 on success, -1 on error.
* This path format will be set globally for all clients wishing to authenticate via the
* SSH Public Key system authentication.
*
* @param[in] path Path to authorized_keys files. The path may contain the following tokens:
* - %u - replaced by the username of the user trying to authenticate,
* - %h - replaced by the home directory of the user trying to authenticate,
* - %U - replaced by the UID of the user trying to authenticate,
* - %% - a literal '%'.
* @return 0 on success, 1 on error.
*/
int nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username);
int nc_server_ssh_set_authkey_path_format(const char *path);
/**
* @brief Add an authorized client SSH public key. This public key can be used for
* publickey authentication (for any SSH connection, even Call Home) afterwards.
* @brief Keyboard interactive authentication callback.
*
* @param[in] pubkey_path Path to the public key.
* @param[in] username Username that the client with the public key must use.
* @return 0 on success, -1 on error.
* The callback has to handle sending interactive challenges and receiving responses by itself.
* An example callback may fit the following description:
* Prepare all prompts for the user and send them via `ssh_message_auth_interactive_request()`.
* Get the answers either by calling `ssh_message_get()` or `nc_server_ssh_kbdint_get_nanswers()`.
* Return value based on your authentication logic and user answers retrieved by
* calling `ssh_userauth_kbdint_getanswer()`.
*
* @param[in] session NETCONF session.
* @param[in] ssh_sess libssh session.
* @param[in] msg SSH message that contains the interactive request and which expects a reply with prompts.
* @param[in] user_data Arbitrary user data.
* @return 0 for successful authentication, non-zero to deny the user.
*/
int nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username);
typedef int (*nc_server_ssh_interactive_auth_clb)(const struct nc_session *session,
ssh_session ssh_sess, ssh_message msg, void *user_data);
/**
* @brief Remove an authorized client SSH public key.
* @brief Set the callback for SSH interactive authentication.
*
* @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys.
* @param[in] pubkey_base64 Authorized public key content. NULL matches any key.
* @param[in] type Authorized public key type. 0 matches all types.
* @param[in] username Username for an authorized public key. NULL matches all the usernames.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type,
const char *username);
/**
* @brief Set the callback for SSH password authentication. If none is set, local system users are used.
*
* @param[in] passwd_auth_clb Callback that should authenticate the user. Username can be directly obtained from @p session.
* Zero return indicates success, non-zero an error.
* @param[in] user_data Optional arbitrary user data that will be passed to @p passwd_auth_clb.
* @param[in] auth_clb Keyboard interactive authentication callback. This callback is only called once per authentication.
* @param[in] user_data Optional arbitrary user data that will be passed to @p interactive_auth_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password,
void *user_data), void *user_data, void (*free_user_data)(void *user_data));
void nc_server_ssh_set_interactive_auth_clb(nc_server_ssh_interactive_auth_clb auth_clb, void *user_data, void (*free_user_data)(void *user_data));
/**
* @brief Set the callback for SSH interactive authentication. If none is set, local system users are used.
* @brief Get the number of answers to Keyboard interactive authentication prompts.
*
* @param[in] interactive_auth_clb Callback that should authenticate the user.
* Zero return indicates success, non-zero an error.
* @param[in] user_data Optional arbitrary user data that will be passed to @p passwd_auth_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
* The actual answers can later be retrieved by calling `ssh_userauth_kbdint_getanswer()` on
* the @p libssh_session.
*
* @param[in] session NETCONF session.
* @param[in] libssh_session libssh session.
*
* @return Non-negative number of answers on success, -1 on configurable authentication timeout,
* disconnect or other error.
*/
void nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session,
const ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data));
int nc_server_ssh_kbdint_get_nanswers(const struct nc_session *session, ssh_session libssh_session);
/**
* @brief Set the callback for SSH public key authentication. If none is set, local system users are used.
* @brief Set the name of the PAM configuration file.
*
* @param[in] pubkey_auth_clb Callback that should authenticate the user.
* Zero return indicates success, non-zero an error.
* @param[in] user_data Optional arbitrary user data that will be passed to @p passwd_auth_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key,
void *user_data), void *user_data, void (*free_user_data)(void *user_data));
/**
* @brief Set the callback for retrieving host keys. Any RSA, DSA, and ECDSA keys can be added. However,
* a maximum of one key of each type will be used during SSH authentication, later keys replacing
* the earlier ones.
* This filename will be set globally for all clients wishing to authenticate via the
* SSH Keyboard Interactive authentication method.
*
* @param[in] hostkey_clb Callback that should return the key itself. Zero return indicates success, non-zero
* an error. On success exactly ONE of @p privkey_path or @p privkey_data is expected
* to be set. The one set will be freed.
* - @p privkey_path expects a PEM file,
* - @p privkey_data expects a base-64 encoded ANS.1 DER data,
* - @p privkey_type type of the key in @p privkey_data. Use ::NC_SSH_KEY_UNKNOWN for
* PKCS#8 key that includes the information about the key in its data.
* @param[in] user_data Optional arbitrary user data that will be passed to @p hostkey_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path,
char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data));
/**
* @brief Add endpoint SSH host keys the server will identify itself with. Only the name is set, the key itself
* wil be retrieved using a callback.
* @param[in] filename Name of the PAM configuration file. The file needs to be located in
* the default PAM directory (usually /etc/pam.d/).
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] name Arbitrary name of the host key.
* @param[in] idx Optional index where to add the key. -1 adds at the end.
* @return 0 on success, -1 on error.
* @return 0 on success, 1 on error.
*/
int nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx);
/**
* @brief Delete endpoint SSH host key. Their order is preserved.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] name Name of the host key. NULL matches all the keys, but if @p idx != -1 then this must be NULL.
* @param[in] idx Index of the hostkey. -1 matches all indices, but if @p name != NULL then this must be -1.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx);
/**
* @brief Move endpoint SSH host key.
*
* @param[in] endpt_name Exisitng endpoint name.
* @param[in] key_mov Name of the host key that will be moved.
* @param[in] key_after Name of the key that will preceed @p key_mov. NULL if @p key_mov is to be moved at the beginning.
* @return 0 in success, -1 on error.
*/
int nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after);
/**
* @brief Modify endpoint SSH host key.
*
* @param[in] endpt_name Exisitng endpoint name.
* @param[in] name Name of an existing host key.
* @param[in] new_name New name of the host key @p name.
* @return 0 in success, -1 on error.
*/
int nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name);
/**
* @brief Set endpoint accepted SSH authentication methods. All (publickey, password, interactive)
* are supported by default.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods);
/**
* @brief Get endpoint accepted SSH authentication methods.
*
* @param[in] endpt_name Existing endpoint name.
* @return Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
*/
int nc_server_ssh_endpt_get_auth_methods(const char *endpt_name);
/**
* @brief Set endpoint SSH authentication attempts of every client. 3 by default.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] auth_attempts Failed authentication attempts before a client is dropped.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts);
/**
* @brief Set endpoint SSH authentication timeout. 30 seconds by default.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout);
int nc_server_ssh_set_pam_conf_filename(const char *filename);
/** @} Server SSH */
#endif /* NC_ENABLED_SSH */
#ifdef NC_ENABLED_TLS
/**
* @defgroup server_tls Server TLS
* @ingroup server
@ -690,179 +560,19 @@ int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_t
* @{
*/
/**
* @brief Set the server TLS certificate. Only the name is set, the certificate itself
* wil be retrieved using a callback.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] name Arbitrary certificate name.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name);
/**
* @brief Set the callback for retrieving server certificate and matching private key.
*
* @param[in] cert_clb Callback that should return the certificate and the key itself. Zero return indicates success,
* non-zero an error. On success exactly ONE of @p cert_path or @p cert_data and ONE of
* @p privkey_path and @p privkey_data is expected to be set. Those set will be freed.
* - @p cert_path expects a PEM file,
* - @p cert_data expects a base-64 encoded ASN.1 DER data,
* - @p privkey_path expects a PEM file,
* - @p privkey_data expects a base-64 encoded ANS.1 DER data,
* - @p privkey_type type of the key in @p privkey_data.
* @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data,
char **privkey_path, char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data,
void (*free_user_data)(void *user_data));
/**
* @brief Set the callback for retrieving server certificate chain
*
* @param[in] cert_chain_clb Callback that should return all the certificates of the chain. Zero return indicates success,
* non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left
* NULL. Both will be (deeply) freed.
* - @p cert_paths expect an array of PEM files,
* - @p cert_path_count number of @p cert_paths array members,
* - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data,
* - @p cert_data_count number of @p cert_data array members.
* @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths,
int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data));
/**
* @brief Add a trusted certificate list. Can be both a CA or a client one. Can be
* safely used together with nc_server_tls_endpt_set_trusted_ca_paths().
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] name Arbitary name identifying this certificate list.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name);
/**
* @brief Set the callback for retrieving trusted certificates.
*
* @param[in] cert_list_clb Callback that should return all the certificates of a list. Zero return indicates success,
* non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left
* NULL. Both will be (deeply) freed.
* - @p cert_paths expect an array of PEM files,
* - @p cert_path_count number of @p cert_paths array members,
* - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data,
* - @p cert_data_count number of @p cert_data array members.
* @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb.
* @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data.
*/
void nc_server_tls_set_trusted_cert_list_clb(int (*cert_list_clb)(const char *name, void *user_data, char ***cert_paths,
int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data));
/**
* @brief Remove a trusted certificate.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] name Name of the certificate list to delete. NULL deletes all the lists.
* @return 0 on success, -1 on not found.
*/
int nc_server_tls_endpt_del_trusted_cert_list(const char *endpt_name, const char *name);
/**
* @brief Set trusted Certificate Authority certificate locations. There can only be
* one file and one directory, they are replaced if already set. Can be safely
* used with nc_server_tls_endpt_add_trusted_cert() or its _path variant.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] ca_file Path to a trusted CA cert store file in PEM format. Can be NULL.
* @param[in] ca_dir Path to a trusted CA cert store hashed directory (c_rehash utility
* can be used to create hashes) with PEM files. Can be NULL.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_file, const char *ca_dir);
/**
* @brief Set Certificate Revocation List locations. There can only be one file
* and one directory, they are replaced if already set.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] crl_file Path to a CRL store file in PEM format. Can be NULL.
* @param[in] crl_dir Path to a CRL store hashed directory (c_rehash utility
* can be used to create hashes) with PEM files. Can be NULL.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_file, const char *crl_dir);
/**
* @brief Destroy and clean CRLs. Certificates, private keys, and CTN entries are
* not affected.
*
* @param[in] endpt_name Existing endpoint name.
*/
void nc_server_tls_endpt_clear_crls(const char *endpt_name);
/**
* @brief Add a cert-to-name entry.
*
* It is possible to add an entry step-by-step, specifying first only @p ip and in later calls
* @p fingerprint, @p map_type, and optionally @p name spearately.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] id Priority of the entry. It must be unique. If already exists, the entry with this id
* is modified.
* @param[in] fingerprint Matching certificate fingerprint. If NULL, kept temporarily unset.
* @param[in] map_type Type of username-certificate mapping. If 0, kept temporarily unset.
* @param[in] name Specific username used only if @p map_type == NC_TLS_CTN_SPECIFED.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint,
NC_TLS_CTN_MAPTYPE map_type, const char *name);
/**
* @brief Remove a cert-to-name entry.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in] id Priority of the entry. -1 matches all the priorities.
* @param[in] fingerprint Fingerprint fo the entry. NULL matches all the fingerprints.
* @param[in] map_type Mapping type of the entry. 0 matches all the mapping types.
* @param[in] name Specific username for the entry. NULL matches all the usernames.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint,
NC_TLS_CTN_MAPTYPE map_type, const char *name);
/**
* @brief Get a cert-to-name entry.
*
* If a parameter is NULL, it is ignored. If its dereferenced value is NULL,
* it is filled and returned. If the value is set, it is used as a filter.
* Returns first matching entry.
*
* @param[in] endpt_name Existing endpoint name.
* @param[in,out] id Priority of the entry.
* @param[in,out] fingerprint Fingerprint fo the entry.
* @param[in,out] map_type Mapping type of the entry.
* @param[in,out] name Specific username for the entry.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_tls_endpt_get_ctn(const char *endpt_name, uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type,
char **name);
/**
* @brief Get client certificate.
*
* @param[in] session Session to get the information from.
* @return Const session client certificate.
*/
const X509 *nc_session_get_client_cert(const struct nc_session *session);
const void *nc_session_get_client_cert(const struct nc_session *session);
/**
* @brief Set TLS authentication additional verify callback.
*
* Server will always perform cert-to-name based on its configuration. Only after it passes
* and this callback is set, it is also called. It should return exactly what OpenSSL
* verify callback meaning 1 for success, 0 to deny the user.
* and this callback is set, it is also called. It should return non-zero for success, 0 to deny the user.
*
* @param[in] verify_clb Additional user verify callback.
*/
@ -870,7 +580,7 @@ void nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *ses
/** @} Server TLS */
#endif /* NC_ENABLED_TLS */
#endif /* NC_ENABLED_SSH_TLS */
/**
* @addtogroup server_session
@ -883,7 +593,7 @@ void nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *ses
* @param[in] session Session to get the information from.
* @return Session start time.
*/
time_t nc_session_get_start_time(const struct nc_session *session);
struct timespec nc_session_get_start_time(const struct nc_session *session);
/**
* @brief Increase session notification subscription flag count.
@ -912,14 +622,34 @@ void nc_session_dec_notif_status(struct nc_session *session);
*/
int nc_session_get_notif_status(const struct nc_session *session);
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Learn whether a session was created using Call Home or not.
* Works only for server sessions.
* @brief Start the certificate expiration notification thread.
*
* @param[in] session Session to get the information from.
* @return 0 if a standard session, non-zero if a Call Home session.
* The thread will periodically check the expiration time of all certificates in the configuration.
* When a notification is about to be generated, the callback @p cert_exp_notif_clb is called.
* The times of when these notifications are generated are based on the expiration times of certificates
* in the configuration and on the values of intervals set in the configuration. For more information,
* see the libnetconf2-netconf-server YANG module.
*
* @param[in] cert_exp_notif_clb The callback to be called when a notification is generated.
* @param[in] user_data Arbitrary user data to pass to the callback.
* @param[in] free_data Optional callback to free the user data.
*
* @return 0 on success, 1 on error.
*/
int nc_session_is_callhome(const struct nc_session *session);
int nc_server_notif_cert_expiration_thread_start(nc_cert_exp_notif_clb cert_exp_notif_clb,
void *user_data, void (*free_data)(void *));
/**
* @brief Stop the certificate expiration notification thread.
*
* @param[in] wait Boolean representing whether to block and wait for the thread to finish.
*/
void nc_server_notif_cert_expiration_thread_stop(int wait);
#endif /* NC_ENABLED_SSH_TLS */
/** @} Server Session */

View file

@ -3,6 +3,7 @@
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 Call Home session server manipulation
*
* @copyright
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
@ -21,11 +22,12 @@ extern "C" {
#include <libyang/libyang.h>
#include <stdint.h>
#include <time.h>
#include "netconf.h"
#include "session.h"
#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS)
#ifdef NC_ENABLED_SSH_TLS
/**
* @defgroup server_ch Server-side Call Home
@ -35,21 +37,15 @@ extern "C" {
* @{
*/
/**
* @brief Add a new Call Home client.
*
* @param[in] name Arbitrary unique client name.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_add_client(const char *name);
/** @} Server-side Call Home */
/**
* @brief Drop any connections, stop connecting and remove a client.
* @defgroup server_ch_functions Server-side Call Home Functions
* @ingroup server_ch
*
* @param[in] name Client name. NULL matches all the clients.
* @return 0 on success, -1 on not finding any match.
* @brief Server-side Call Home functions.
* @{
*/
int nc_server_ch_del_client(const char *name);
/**
* @brief Check if a Call Home client exists.
@ -59,28 +55,6 @@ int nc_server_ch_del_client(const char *name);
*/
int nc_server_ch_is_client(const char *name);
/**
* @brief Add a new Call Home client endpoint.
*
* @param[in] client_name Existing client name.
* @param[in] endpt_name Arbitrary unique (within the client) endpoint name.
* @param[in] ti Transport protocol to use.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_add_endpt(const char *client_name, const char *endpt_name, NC_TRANSPORT_IMPL ti);
/**
* @brief Remove a Call Home client endpoint.
*
* @param[in] client_name Existing client name.
* @param[in] endpt_name Existing endpoint of @p client_name. NULL matches all endpoints.
* @param[in] ti Client transport protocol. NULL matches any protocol.
* Redundant to set if @p endpt_name is set, client names are
* unique disregarding their protocol.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_del_endpt(const char *client_name, const char *endpt_name, NC_TRANSPORT_IMPL ti);
/**
* @brief Check if an endpoint of a Call Home client exists.
*
@ -91,343 +65,65 @@ int nc_server_ch_client_del_endpt(const char *client_name, const char *endpt_nam
int nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name);
/**
* @brief Change Call Home client endpoint listening address.
* @brief Callback for getting a locked context for new Call Home sessions.
*
* On error the previous listening socket (if any) is left untouched.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of @p client_name.
* @param[in] address New listening address.
* @return 0 on success, -1 on error.
* @param[in] cb_data Arbitrary ctx callback data.
* @return Context for the session to use during its lifetime;
* @return NULL on error and session fails to be created.
*/
int nc_server_ch_client_endpt_set_address(const char *client_name, const char *endpt_name, const char *address);
typedef const struct ly_ctx *(*nc_server_ch_session_acquire_ctx_cb)(void *cb_data);
/**
* @brief Change Call Home client endpoint listening port.
* @brief Callback for releasing a locked context for Call Home sessions.
*
* On error the previous listening socket (if any) is left untouched.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of @p client_name.
* @param[in] port New listening port.
* @return 0 on success, -1 on error.
* @param[in] cb_data Arbitrary ctx callback data.
*/
int nc_server_ch_client_endpt_set_port(const char *client_name, const char *endpt_name, uint16_t port);
typedef void (*nc_server_ch_session_release_ctx_cb)(void *cb_data);
/**
* @brief Change Call Home client endpoint keepalives state. Affects only new connections.
* @brief Callback for new Call Home sessions.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of @p client_name.
* @param[in] enable Whether to enable or disable keepalives.
* @return 0 on success, -1 on error.
* @param[in] client_name Name of the CH client which established the session.
* @param[in] new_session New established CH session, the pointer is internally discarded afterwards.
* @param[in] user_data Arbitrary new session callback data.
* @return 0 on success;
* @return non-zero on error and @p new_session is freed.
*/
int nc_server_ch_client_endpt_enable_keepalives(const char *client_name, const char *endpt_name, int enable);
typedef int (*nc_server_ch_new_session_cb)(const char *client_name, struct nc_session *new_session, void *user_data);
/**
* @brief Change Call Home client endpoint keepalives parameters. Affects only new connections.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of @p client_name.
* @param[in] idle_time Keepalive idle time in seconds, 1 by default, -1 to keep previous value.
* @param[in] max_probes Keepalive max probes sent, 10 by default, -1 to keep previous value.
* @param[in] probe_interval Keepalive probe interval in seconds, 5 by default, -1 to keep previous value.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_endpt_set_keepalives(const char *client_name, const char *endpt_name, int idle_time,
int max_probes, int probe_interval);
/**
* @brief Set Call Home client connection type.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] conn_type Call Home connection type.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_set_conn_type(const char *client_name, NC_CH_CONN_TYPE conn_type);
/**
* @brief Set Call Home client periodic connection period for reconnecting.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] period Call Home periodic connection period in minutes.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_periodic_set_period(const char *client_name, uint16_t period);
/**
* @brief Set Call Home client periodic connection period anchor time.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] anchor_time Call Home periodic connection anchor time for the period.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_periodic_set_anchor_time(const char *client_name, time_t anchor_time);
/**
* @brief Set Call Home client periodic connection idle timeout.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] idle_timeout Call Home periodic idle timeout.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_periodic_set_idle_timeout(const char *client_name, uint16_t idle_timeout);
/**
* @brief Set Call Home client start-with policy.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] start_with Call Home client start-with.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_set_start_with(const char *client_name, NC_CH_START_WITH start_with);
/**
* @brief Set Call Home client overall max attempts.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] max_attempts Call Home overall max reconnect attempts.
* @return 0 on success, -1 on error.
*/
int nc_server_ch_client_set_max_attempts(const char *client_name, uint8_t max_attempts);
/**
* @brief Establish a Call Home connection with a listening NETCONF client.
* @brief Dispatch a thread connecting to a listening NETCONF client and creating Call Home sessions.
*
* @param[in] client_name Existing client name.
* @param[out] session_clb Function that is called for every established session on the client. @p new_session
* pointer is internally discarded afterwards. If the callback returns non-zero, the @p new_session is freed.
* @param[in] acquire_ctx_cb Callback for acquiring new session context.
* @param[in] release_ctx_cb Callback for releasing session context.
* @param[in] ctx_cb_data Arbitrary user data passed to @p acquire_ctx_cb and @p release_ctx_cb.
* @param[in] new_session_cb Callback called for every established session on the client.
* @param[in] new_session_cb_data Arbitrary user data passed to @p new_session_cb.
* @return 0 if the thread was successfully created, -1 on error.
*/
int nc_connect_ch_client_dispatch(const char *client_name,
int (*session_clb)(const char *client_name, struct nc_session *new_session));
/** @} Server-side Call Home */
#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */
#ifdef NC_ENABLED_SSH
int nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
void *new_session_cb_data);
/**
* @defgroup server_ch_ssh Server-side Call Home on SSH
* @ingroup server_ch
* @brief Set callbacks and their data for Call Home threads.
*
* @brief SSH settings for the Call Home functionality
* @{
* If set, Call Home threads will be dispatched automatically upon creation of new Call Home clients.
*
* @param[in] acquire_ctx_cb Callback for acquiring new session context.
* @param[in] release_ctx_cb Callback for releasing session context.
* @param[in] ctx_cb_data Arbitrary user data passed to @p acquire_ctx_cb and @p release_ctx_cb.
* @param[in] new_session_cb Callback called for every established Call Home session.
* @param[in] new_session_cb_data Arbitrary user data passed to @p new_session_cb.
*/
void nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
void *new_session_cb_data);
/**
* @brief Add Call Home SSH host keys the server will identify itself with. Only the name is set, the key itself
* wil be retrieved using a callback.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] name Arbitrary name of the host key.
* @param[in] idx Optional index where to add the key. -1 adds at the end.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_add_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx);
/** @} Server-side Call Home Functions */
/**
* @brief Delete Call Home SSH host keys. Their order is preserved.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] name Name of the host key. NULL matches all the keys, but if @p idx != -1 then this must be NULL.
* @param[in] idx Index of the hostkey. -1 matches all indices, but if @p name != NULL then this must be -1.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_del_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx);
/**
* @brief Move Call Home SSH host key.
*
* @param[in] client_name Exisitng Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] key_mov Name of the host key that will be moved.
* @param[in] key_after Name of the key that will preceed @p key_mov. NULL if @p key_mov is to be moved at the beginning.
* @return 0 in success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov,
const char *key_after);
/**
* @brief Set accepted Call Home SSH authentication methods. All (publickey, password, interactive)
* are supported by default.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_set_auth_methods(const char *client_name, const char *endpt_name, int auth_methods);
/**
* @brief Get accepted Call Home SSH authentication methods.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @return Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
*/
int nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name);
/**
* @brief Set Call Home SSH authentication attempts of every client. 3 by default.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] auth_attempts Failed authentication attempts before a client is dropped.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_set_auth_attempts(const char *client_name, const char *endpt_name, uint16_t auth_attempts);
/**
* @brief Set Call Home SSH authentication timeout. 30 seconds by default.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped.
* @return 0 on success, -1 on error.
*/
int nc_server_ssh_ch_client_endpt_set_auth_timeout(const char *client_name, const char *endpt_name, uint16_t auth_timeout);
/** @} Server-side Call Home on SSH */
#endif /* NC_ENABLED_SSH */
#ifdef NC_ENABLED_TLS
/**
* @defgroup server_ch_tls Server-side Call Home on TLS
* @ingroup server_ch
*
* @brief TLS settings for the Call Home functionality
* @{
*/
/**
* @brief Set the server Call Home TLS certificate. Only the name is set, the certificate itself
* wil be retrieved using a callback.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] name Arbitrary certificate name.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_ch_client_endpt_set_server_cert(const char *client_name, const char *endpt_name, const char *name);
/**
* @brief Add a Call Home trusted certificate list. Can be both a CA or a client one.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] name Arbitary name identifying this certificate list.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_ch_client_endpt_add_trusted_cert_list(const char *client_name, const char *endpt_name, const char *name);
/**
* @brief Remove a set Call Home trusted certificate list. CRLs and CTN entries are not affected.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] name Name of the certificate list to delete. NULL deletes all the lists.
* @return 0 on success, -1 on not found.
*/
int nc_server_tls_ch_client_endpt_del_trusted_cert_list(const char *client_name, const char *endpt_name, const char *name);
/**
* @brief Set trusted Call Home Certificate Authority certificate locations. There
* can only be one file and one directory, they are replaced if already set.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] ca_file Path to a trusted CA cert store file in PEM format.
* Can be NULL.
* @param[in] ca_dir Path to a trusted CA cert store hashed directory
* (c_rehash utility can be used to create hashes)
* with PEM files. Can be NULL.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_ch_client_endpt_set_trusted_ca_paths(const char *client_name, const char *endpt_name, const char *ca_file,
const char *ca_dir);
/**
* @brief Set Call Home Certificate Revocation List locations. There can only be
* one file and one directory, they are replaced if already set.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] crl_file Path to a CRL store file in PEM format. Can be NULL.
* @param[in] crl_dir Path to a CRL store hashed directory (c_rehash utility
* can be used to create hashes) with PEM files. Can be NULL.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_ch_client_endpt_set_crl_paths(const char *client_name, const char *endpt_name, const char *crl_file,
const char *crl_dir);
/**
* @brief Destroy and clean Call Home CRLs. Call Home certificates, private keys,
* and CTN entries are not affected.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
*/
void nc_server_tls_ch_client_endpt_clear_crls(const char *client_name, const char *endpt_name);
/**
* @brief Add a cert-to-name entry.
*
* It is possible to add an entry step-by-step, specifying first only @p ip and in later calls
* @p fingerprint, @p map_type, and optionally @p name spearately.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] id Priority of the entry. It must be unique. If already exists, the entry with this id
* is modified.
* @param[in] fingerprint Matching certificate fingerprint. If NULL, kept temporarily unset.
* @param[in] map_type Type of username-certificate mapping. If 0, kept temporarily unset.
* @param[in] name Specific username used only if @p map_type == NC_TLS_CTN_SPECIFED.
* @return 0 on success, -1 on error.
*/
int nc_server_tls_ch_client_endpt_add_ctn(const char *client_name, const char *endpt_name, uint32_t id,
const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
/**
* @brief Remove a Call Home cert-to-name entry.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in] id Priority of the entry. -1 matches all the priorities.
* @param[in] fingerprint Fingerprint fo the entry. NULL matches all the fingerprints.
* @param[in] map_type Mapping type of the entry. 0 matches all the mapping types.
* @param[in] name Specific username for the entry. NULL matches all the usernames.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_tls_ch_client_endpt_del_ctn(const char *client_name, const char *endpt_name, int64_t id,
const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
/**
* @brief Get a Call Home cert-to-name entry.
*
* If a parameter is NULL, it is ignored. If its dereferenced value is NULL,
* it is filled and returned. If the value is set, it is used as a filter.
* Returns first matching entry.
*
* @param[in] client_name Existing Call Home client name.
* @param[in] endpt_name Existing endpoint name of the client.
* @param[in,out] id Priority of the entry.
* @param[in,out] fingerprint Fingerprint fo the entry.
* @param[in,out] map_type Mapping type of the entry.
* @param[in,out] name Specific username for the entry.
* @return 0 on success, -1 on not finding any match.
*/
int nc_server_tls_ch_client_endpt_get_ctn(const char *client_name, const char *endpt_name, uint32_t *id, char **fingerprint,
NC_TLS_CTN_MAPTYPE *map_type, char **name);
/** @} Server-side Call Home on TLS */
#endif /* NC_ENABLED_TLS */
#endif /* NC_ENABLED_SSH_TLS */
#ifdef __cplusplus
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

742
src/session_wrapper.h Normal file
View file

@ -0,0 +1,742 @@
/**
* @file session_wrapper.h
* @author Roman Janota <janota@cesnet.cz>
* @brief libnetconf2 - header for wrapped TLS library function calls (currently OpenSSL and MbedTLS)
*
* @copyright
* Copyright (c) 2024 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef _SESSION_WRAPPER_H_
#define _SESSION_WRAPPER_H_
#include <stdlib.h>
#include "config.h"
#ifdef HAVE_MBEDTLS
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/pk.h>
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crl.h>
#include <mbedtls/x509_crt.h>
/**
* @brief Context from which a TLS session may be created.
*/
struct nc_tls_ctx {
int *sock; /**< Socket FD. */
mbedtls_entropy_context *entropy; /**< Entropy. */
mbedtls_ctr_drbg_context *ctr_drbg; /**< Random bit generator. */
mbedtls_x509_crt *cert; /**< Certificate. */
mbedtls_pk_context *pkey; /**< Private key. */
mbedtls_x509_crt *cert_store; /**< CA certificates store. */
mbedtls_x509_crl *crl_store; /**< CRL store. */
};
#else
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
/**
* @brief Context from which a TLS session may be created.
*/
struct nc_tls_ctx {
X509 *cert; /**< Certificate. */
EVP_PKEY *pkey; /**< Private key. */
X509_STORE *cert_store; /**< CA certificate store. */
X509_STORE *crl_store; /**< CRL store. */
};
#endif
/**
* @brief Server side TLS verify callback data.
*/
struct nc_tls_verify_cb_data {
struct nc_session *session; /**< NETCONF session. */
struct nc_server_tls_opts *opts; /**< TLS server options. */
void *chain; /**< Certificate chain used to verify the client cert. */
};
/**
* @brief Creates a new TLS session from the given configuration.
*
* @param[in] tls_cfg TLS configuration.
* @return New TLS session on success, NULL on fail.
*/
void * nc_tls_session_new_wrap(void *tls_cfg);
/**
* @brief Destroys a TLS session.
*
* @param[in] tls_session TLS session to destroy.
*/
void nc_tls_session_destroy_wrap(void *tls_session);
/**
* @brief Creates a new TLS configuration.
*
* @param[in] side Side of the TLS connection.
* @return New TLS configuration on success, NULL on fail.
*/
void * nc_tls_config_new_wrap(int side);
/**
* @brief Destroys a TLS configuration.
*
* @param[in] tls_cfg TLS configuration to destroy.
*/
void nc_tls_config_destroy_wrap(void *tls_cfg);
/**
* @brief Creates a new TLS certificate.
*
* @return New TLS certificate on success, NULL on fail.
*/
void * nc_tls_cert_new_wrap(void);
/**
* @brief Destroys a TLS certificate.
*
* @param[in] cert TLS certificate to destroy.
*/
void nc_tls_cert_destroy_wrap(void *cert);
/**
* @brief Destroys a TLS private key.
*
* @param[in] pkey TLS private key to destroy.
*/
void nc_tls_privkey_destroy_wrap(void *pkey);
/**
* @brief Creates a new TLS certificate store.
*
* @return New TLS certificate store on success, NULL on fail.
*/
void * nc_tls_cert_store_new_wrap(void);
/**
* @brief Destroys a TLS certificate store.
*
* @param[in] cert_store TLS certificate store to destroy.
*/
void nc_tls_cert_store_destroy_wrap(void *cert_store);
/**
* @brief Creates a new CRL store.
*
* @return New CRL store on success, NULL on fail.
*/
void * nc_tls_crl_store_new_wrap(void);
/**
* @brief Destroys a CRL store.
*
* @param[in] crl_store CRL store to destroy.
*/
void nc_tls_crl_store_destroy_wrap(void *crl_store);
/**
* @brief Converts PEM certificate data to a certificate.
*
* @param[in] cert_data PEM certificate data.
* @return New certificate on success, NULL on fail.
*/
void * nc_tls_pem_to_cert_wrap(const char *cert_data);
/**
* @brief Adds a certificate to a certificate store.
*
* @param[in] cert Certificate to add.
* @param[in] cert_store Certificate store to add the certificate to.
* @return 0 on success and the memory belongs to cert_store, non-zero on fail.
*/
int nc_tls_add_cert_to_store_wrap(void *cert, void *cert_store);
/**
* @brief Converts PEM private key data to a private key.
*
* @param[in] privkey_data PEM private key data.
* @return New private key on success, NULL on fail.
*/
void * nc_tls_pem_to_privkey_wrap(const char *privkey_data);
/**
* @brief Parses and adds a CRL to a CRL store.
*
* @param[in] crl_data CRL data.
* @param[in] size Size of the CRL data.
* @param[in] crl_store CRL store to add the CRL to.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_add_crl_to_store_wrap(const unsigned char *crl_data, size_t size, void *crl_store);
/**
* @brief Sets the TLS version.
*
* @param[in] tls_cfg TLS configuration.
* @param[in] tls_versions Bit-field of supported TLS versions.
*
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_set_tls_versions_wrap(void *tls_cfg, unsigned int tls_versions);
/**
* @brief Set TLS server's verify flags, verify cb and its data.
*
* @param[in] tls_cfg TLS configuration.
* @param[in] cb_data Verify callback data.
*/
void nc_server_tls_set_verify_wrap(void *tls_cfg, struct nc_tls_verify_cb_data *cb_data);
/**
* @brief Set TLS client's verify flags.
*
* @param[in] tls_cfg TLS configuration.
*/
void nc_client_tls_set_verify_wrap(void *tls_cfg);
/**
* @brief Verify the certificate.
*
* @param[in] cert Certificate to verify.
* @param[in] depth Certificate depth.
* @param[in] trusted Boolean flag representing whether the certificate is trusted.
* @param[in] cb_data Data for the verify callback.
* @return 0 on success, 1 on verify fail, -1 on fatal error.
*/
int nc_server_tls_verify_cert(void *cert, int depth, int trusted, struct nc_tls_verify_cb_data *cb_data);
/**
* @brief Check if the peer certificate matches any configured ee certs.
*
* @param[in] peer_cert Peer certificate.
* @param[in] opts TLS options.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_verify_peer_cert(void *peer_cert, struct nc_server_tls_opts *opts);
/**
* @brief Get the subject of the certificate.
*
* @param[in] cert Certificate.
* @return Subject of the certificate on success, NULL on fail.
*/
char * nc_server_tls_get_subject_wrap(void *cert);
/**
* @brief Get the issuer of the certificate.
*
* @param[in] cert Certificate.
* @return Issuer of the certificate on success, NULL on fail.
*/
char * nc_server_tls_get_issuer_wrap(void *cert);
/**
* @brief Get the Subject Alternative Names of the certificate.
*
* @param[in] cert Certificate.
* @return SANs on success, NULL on fail.
*/
void * nc_tls_get_sans_wrap(void *cert);
/**
* @brief Destroy the SANs.
*
* @param[in] sans SANs to destroy.
*/
void nc_tls_sans_destroy_wrap(void *sans);
/**
* @brief Get the number of SANs.
*
* @param[in] sans SANs.
* @return Number of SANs.
*/
int nc_tls_get_num_sans_wrap(void *sans);
#ifdef NC_ENABLED_SSH_TLS
/**
* @brief Get the SAN value and type in the context of CTN.
*
* @param[in] sans SANs.
* @param[in] idx Index of the SAN.
* @param[out] san_value SAN value.
* @param[out] san_type SAN type.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_get_san_value_type_wrap(void *sans, int idx, char **san_value, NC_TLS_CTN_MAPTYPE *san_type);
#endif
/**
* @brief Get the number of certificates in a certificate chain.
*
* @param[in] chain Certificate chain.
* @return Number of certificates in the chain.
*/
int nc_tls_get_num_certs_wrap(void *chain);
/**
* @brief Get a certificate from a certificate chain.
*
* @param[in] chain Certificate chain.
* @param[in] idx Index of the certificate to get.
* @param[out] cert Certificate.
*/
void nc_tls_get_cert_wrap(void *chain, int idx, void **cert);
/**
* @brief Compare two certificates.
*
* @param[in] cert1 Certificate 1.
* @param[in] cert2 Certificate 2.
* @return 1 if the certificates match, 0 otherwise.
*/
int nc_server_tls_certs_match_wrap(void *cert1, void *cert2);
/**
* @brief Get the MD5 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_md5_wrap(void *cert, unsigned char *buf);
/**
* @brief Get the SHA1 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_sha1_wrap(void *cert, unsigned char *buf);
/**
* @brief Get the SHA224 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_sha224_wrap(void *cert, unsigned char *buf);
/**
* @brief Get the SHA256 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_sha256_wrap(void *cert, unsigned char *buf);
/**
* @brief Get the SHA384 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_sha384_wrap(void *cert, unsigned char *buf);
/**
* @brief Get the SHA512 digest of the certificate.
*
* @param[in] cert Certificate.
* @param[out] buf Buffer for the digest.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_sha512_wrap(void *cert, unsigned char *buf);
/**
* @brief Set the FD for a TLS session.
*
* @param[in] tls_session TLS session.
* @param[in] sock Socket FD.
* @param[in] tls_ctx TLS context.
*/
void nc_tls_set_fd_wrap(void *tls_session, int sock, struct nc_tls_ctx *tls_ctx);
/**
* @brief Perform a server-side step of the TLS handshake.
*
* @param[in] tls_session TLS session.
* @return 1 on success, 0 if the handshake is not finished, negative number on error.
*/
int nc_server_tls_handshake_step_wrap(void *tls_session);
/**
* @brief Perform a client-side step of the TLS handshake.
*
* @param[in] tls_session TLS session.
* @param[in] sock Socket FD.
* @return 1 on success, 0 if the handshake is not finished, negative number on error.
*/
int nc_client_tls_handshake_step_wrap(void *tls_session, int sock);
/**
* @brief Destroy a TLS context.
*
* @param[in] tls_ctx TLS context.
*/
void nc_tls_ctx_destroy_wrap(struct nc_tls_ctx *tls_ctx);
/**
* @brief Load client's certificate and a private key.
*
* @param[in] cert_path Path to the certificate.
* @param[in] key_path Path to the private key.
* @param[out] cert Certificate.
* @param[out] pkey Private key.
* @return 0 on success, non-zero on fail.
*/
int nc_client_tls_load_cert_key_wrap(const char *cert_path, const char *key_path, void **cert, void **pkey);
/**
* @brief Load client's trusted certificates.
*
* @param[in] cert_store Certificate store.
* @param[in] file_path Path to the file with trusted certificates.
* @param[in] dir_path Path to the directory with trusted certificates.
* @return 0 on success, non-zero on fail.
*/
int nc_client_tls_load_trusted_certs_wrap(void *cert_store, const char *file_path, const char *dir_path);
/**
* @brief Set the hostname for the TLS session.
*
* @param[in] tls_session TLS session.
* @param[in] hostname Hostname.
* @return 0 on success, non-zero on fail.
*/
int nc_client_tls_set_hostname_wrap(void *tls_session, const char *hostname);
/**
* @brief Initialize a TLS context.
*
* @param[in] cert Certificate.
* @param[in] pkey Private key.
* @param[in] cert_store Certificate store.
* @param[in] crl_store CRL store.
* @param[in,out] tls_ctx TLS context.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_init_ctx_wrap(void *cert, void *pkey, void *cert_store, void *crl_store, struct nc_tls_ctx *tls_ctx);
/**
* @brief Setup a TLS configuration from a TLS context.
*
* @param[in] tls_ctx TLS context.
* @param[in] side Side of the TLS connection.
* @param[in,out] tls_cfg TLS configuration.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_setup_config_from_ctx_wrap(struct nc_tls_ctx *tls_ctx, int side, void *tls_cfg);
/**
* @brief Get the error code from a TLS session's verification.
*
* @param[in] tls_session TLS session.
* @return Error code, 0 indicates success.
*/
uint32_t nc_tls_get_verify_result_wrap(void *tls_session);
/**
* @brief Get the error string from a TLS session's verification.
*
* @param[in] err_code Error code.
* @return Error string.
*/
char * nc_tls_verify_error_string_wrap(uint32_t err_code);
/**
* @brief Print the TLS session's connection error.
*
* @param[in] connect_ret Error code.
* @param[in] peername Peername.
* @param[in] tls_session TLS session.
*/
void nc_client_tls_print_connect_err_wrap(int connect_ret, const char *peername, void *tls_session);
/**
* @brief Print the TLS session's accept error.
*
* @param[in] accept_ret Error code.
* @param[in] tls_session TLS session.
*/
void nc_server_tls_print_accept_err_wrap(int accept_ret, void *tls_session);
/**
* @brief Checks if the DER data is a SubjectPublicKeyInfo public key.
*
* @param[in] der DER data.
* @param[in] len Length of the DER data.
*
* @return 1 if the data is a SubjectPublicKeyInfo public key, 0 if not, -1 on error.
*/
int nc_tls_is_der_subpubkey_wrap(unsigned char *der, long len);
/**
* @brief Decodes base64 to binary.
*
* @param[in] base64 Base64 string.
* @param[out] bin Binary result, memory managed by the caller.
* @return Length of the binary data on success, -1 on error.
*/
int nc_base64_decode_wrap(const char *base64, unsigned char **bin);
/**
* @brief Encodes binary to base64.
*
* @param[in] bin Binary data.
* @param[in] len Length of the binary data.
* @param[out] base64 NULL terminated Base64 result, memory managed by the caller.
* @return 0 on success, -1 on error.
*/
int nc_base64_encode_wrap(const unsigned char *bin, size_t len, char **base64);
/**
* @brief Reads data from a TLS session.
*
* @param[in] session NETCONF session.
* @param[out] buf Buffer for the data.
* @param[in] size Size of the buffer.
* @return Number of bytes read on success, -1 on error.
*/
int nc_tls_read_wrap(struct nc_session *session, unsigned char *buf, size_t size);
/**
* @brief Writes data to a TLS session.
*
* @param[in] session NETCONF session.
* @param[in] buf Data to write.
* @param[in] size Size of the data.
* @return Number of bytes written on success, -1 on error.
*/
int nc_tls_write_wrap(struct nc_session *session, const unsigned char *buf, size_t size);
/**
* @brief Get the number of pending bytes in a TLS session.
*
* @param[in] tls_session TLS session.
* @return Number of pending bytes.
*/
int nc_tls_get_num_pending_bytes_wrap(void *tls_session);
/**
* @brief Get the file descriptor of a TLS session.
*
* @param[in] session NETCONF session.
* @return File descriptor, -1 on error.
*/
int nc_tls_get_fd_wrap(const struct nc_session *session);
/**
* @brief Close a TLS session.
*
* @param[in] tls_session TLS session.
*/
void nc_tls_close_notify_wrap(void *tls_session);
/**
* @brief Import a private key from a file.
*
* @param[in] privkey_path Path to the private key file.
* @return Imported private key on success, NULL on fail.
*/
void * nc_tls_import_privkey_file_wrap(const char *privkey_path);
/**
* @brief Import a certificate from a file.
*
* @param[in] cert_path Path to the certificate file.
* @return Imported certificate on success, NULL on fail.
*/
void * nc_tls_import_cert_file_wrap(const char *cert_path);
/**
* @brief Export a private key to a PEM string.
*
* @param[in] pkey Private key.
* @return PEM string on success, NULL on fail.
*/
char * nc_tls_export_privkey_pem_wrap(void *pkey);
/**
* @brief Export a certificate to a PEM string.
*
* @param[in] cert Certificate.
* @return PEM string on success, NULL on fail.
*/
char * nc_tls_export_cert_pem_wrap(void *cert);
/**
* @brief Export a public key to a PEM string.
*
* @param[in] pkey Public key.
* @return PEM string on success, NULL on fail.
*/
char * nc_tls_export_pubkey_pem_wrap(void *pkey);
/**
* @brief Check if a private key is RSA.
*
* @param[in] pkey Private key.
* @return 1 if the private key is RSA, 0 if not.
*/
int nc_tls_privkey_is_rsa_wrap(void *pkey);
/**
* @brief Get the RSA public key parameters from a private key.
*
* @param[in] pkey Private key.
* @param[out] e Exponent.
* @param[out] n Modulus.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_get_rsa_pubkey_params_wrap(void *pkey, void **e, void **n);
/**
* @brief Destroy an MPI.
*
* @param[in] mpi MPI.
*/
void nc_tls_destroy_mpi_wrap(void *mpi);
/**
* @brief Check if a private key is EC.
*
* @param[in] pkey Private key.
* @return 1 if the private key is EC, 0 if not.
*/
int nc_tls_privkey_is_ec_wrap(void *pkey);
/**
* @brief Get the group name of an EC private key.
*
* @param[in] pkey Private key.
* @return Group name on success, NULL on fail.
*/
char * nc_tls_get_ec_group_wrap(void *pkey);
/**
* @brief Get the EC public key parameters from a private key.
*
* @param[in] pkey Private key.
* @param[out] q Public key point.
* @param[out] q_grp Public key group.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_get_ec_pubkey_params_wrap(void *pkey, void **q, void **q_grp);
/**
* @brief Convert an EC point to binary.
*
* @param[in] q EC point.
* @param[in] q_grp EC group.
* @param[out] bin Binary point.
* @param[out] bin_len Length of the binary point.
* @return 0 on success, non-zero on fail.
*/
int nc_tls_ec_point_to_bin_wrap(void *q, void *q_grp, unsigned char **bin, int *bin_len);
/**
* @brief Destroy an EC point.
*
* @param[in] p EC point.
*/
void nc_tls_ec_point_destroy_wrap(void *p);
/**
* @brief Destroy an EC group.
*
* @param[in] grp EC group.
*/
void nc_tls_ec_group_destroy_wrap(void *grp);
/**
* @brief Convert an MPI to binary.
*
* @param[in] mpi MPI.
* @param[out] bin Binary buffer.
* @param[out] bin_len Length of the binary.
* @return 0 on success, 1 on error.
*/
int nc_tls_mpi2bin_wrap(void *mpi, unsigned char **bin, int *bin_len);
/**
* @brief Import a public key from a file.
*
* @param[in] pubkey_path Path to the public key file.
* @return Imported public key on success, NULL on fail.
*/
void * nc_tls_import_pubkey_file_wrap(const char *pubkey_path);
/**
* @brief Get all the URIs from the CRLDistributionPoints x509v3 extensions.
*
* @param[in] leaf_cert Server/client certificate.
* @param[in] cert_store Certificate store.
* @param[out] uris URIs to download the CRLs from.
* @param[out] uri_count Number of URIs found.
* @return 0 on success, non-zero on fail.
*/
int nc_server_tls_get_crl_distpoint_uris_wrap(void *leaf_cert, void *cert_store, char ***uris, int *uri_count);
/**
* @brief Process a cipher suite so that it can be set by the underlying TLS lib.
*
* @param[in] cipher Cipher suite identity value.
* @param[out] out Processed cipher suite.
* @return 0 on success, 1 on fail.
*/
int nc_tls_process_cipher_suite_wrap(const char *cipher, char **out);
/**
* @brief Append a cipher suite to the list of cipher suites.
*
* @param[in] opts TLS options.
* @param[in] cipher_suite Cipher suite to append.
* @return 0 on success, 1 on fail.
*/
int nc_tls_append_cipher_suite_wrap(struct nc_server_tls_opts *opts, const char *cipher_suite);
/**
* @brief Set the list of cipher suites for the TLS configuration.
*
* @param[in] tls_cfg TLS configuration.
* @param[in] cipher_suites List of cipher suites.
*/
void nc_server_tls_set_cipher_suites_wrap(void *tls_cfg, void *cipher_suites);
/**
* @brief Get the certificate's expiration time.
*
* @param[in] cert Certificate.
*
* @return Calendar time of the expiration (it is in GMT) or -1 on error.
*/
time_t nc_tls_get_cert_exp_time_wrap(void *cert);
/**
* @brief Set the session to log TLS secrets for.
*
* @param[in] session Session to log secrets for.
*/
void nc_tls_keylog_session_wrap(void *session);
#endif

View file

@ -4,75 +4,99 @@ add_test(NAME headers
# format
if(${SOURCE_FORMAT_ENABLED})
add_test(NAME format WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND make format-check)
endif()
# list of all the tests in each directory
set(tests test_io test_fd_comm test_init_destroy_client test_init_destroy_server test_client_thread test_thread_messages)
set(client_tests test_client test_client_messages)
# add -Wl,--wrap flags
set(test test_client_ssh)
set(${test}_mock_funcs connect ssh_connect ssh_userauth_none ssh_userauth_kbdint ssh_is_connected
ssh_channel_open_session ssh_channel_request_subsystem ssh_channel_is_close ssh_channel_write
ssh_channel_poll_timeout ssh_userauth_password nc_handshake_io nc_ctx_check_and_fill
ssh_userauth_try_publickey ssh_userauth_publickey nc_sock_listen_inet nc_sock_accept_binds nc_accept_callhome_ssh_sock)
set(${test}_wrap_link_flags "-Wl")
foreach(mock_func IN LISTS ${test}_mock_funcs)
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
endforeach()
set(test test_client_tls)
set(${test}_mock_funcs connect SSL_connect nc_send_hello_io nc_handshake_io nc_ctx_check_and_fill)
set(${test}_wrap_link_flags "-Wl")
foreach(mock_func IN LISTS ${test}_mock_funcs)
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
endforeach()
#append tests depending on SSH/TLS
if(ENABLE_SSH OR ENABLE_TLS)
list(APPEND tests test_server_thread)
if(ENABLE_SSH)
list(APPEND client_tests test_client_ssh)
endif()
if(ENABLE_TLS)
list(APPEND client_tests test_client_tls)
endif()
add_test(NAME format WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND cmake --build ${CMAKE_BINARY_DIR} --target format-check)
endif()
foreach(src IN LISTS libsrc)
list(APPEND test_srcs "../${src}")
endforeach()
add_library(testobj OBJECT ${test_srcs})
add_library(testobj OBJECT ${test_srcs} ${compatsrc})
foreach(test_name IN LISTS tests)
add_executable(${test_name} $<TARGET_OBJECTS:testobj> ${test_name}.c)
target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${LIBYANG_LIBRARIES} netconf2)
target_include_directories(${test_name} PRIVATE ${CMOCKA_INCLUDE_DIR})
set_target_properties(${test_name} PROPERTIES LINK_FLAGS "${${test_name}_wrap_link_flags}")
add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>)
set(TEST_SRC "ln2_test.c")
set(NEXT_TEST_PORT 10050)
macro(get_test_ports PORT_COUNT PORT_DEFINITIONS)
if (NOT ${PORT_COUNT})
set(${PORT_COUNT} 1)
endif()
SET(PORT_INDEX 0)
while(PORT_INDEX LESS ${${PORT_COUNT}})
list(APPEND ${PORT_DEFINITIONS} "TEST_PORT_${PORT_INDEX}=${NEXT_TEST_PORT}")
math(EXPR PORT_INDEX "${PORT_INDEX} + 1")
math(EXPR NEXT_TEST_PORT "${NEXT_TEST_PORT} + 1")
endwhile()
set(NEXT_TEST_PORT ${NEXT_TEST_PORT} PARENT_SCOPE)
endmacro()
function(libnetconf2_test)
cmake_parse_arguments(TEST "" "NAME;PORT_COUNT" "WRAP_FUNCS" ${ARGN})
add_executable(${TEST_NAME} $<TARGET_OBJECTS:testobj> ${TEST_SRC} ${TEST_NAME}.c)
target_link_libraries(${TEST_NAME} ${CMOCKA_LIBRARIES} ${LIBYANG_LIBRARIES} netconf2)
target_include_directories(${TEST_NAME} PRIVATE ${CMOCKA_INCLUDE_DIR})
# wrap functions
if(TEST_WRAP_FUNCS)
set(wrap_link_flags "-Wl")
foreach(mock_func IN LISTS TEST_WRAP_FUNCS)
set(wrap_link_flags "${wrap_link_flags},--wrap=${mock_func}")
endforeach()
set_target_properties(${TEST_NAME} PROPERTIES LINK_FLAGS "${wrap_link_flags}")
endif()
foreach(test_name IN LISTS client_tests)
add_executable(${test_name} $<TARGET_OBJECTS:testobj> ./client/${test_name}.c)
target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${LIBYANG_LIBRARIES} netconf2)
target_include_directories(${test_name} PRIVATE ${CMOCKA_INCLUDE_DIR})
set_target_properties(${test_name} PROPERTIES LINK_FLAGS "${${test_name}_wrap_link_flags}")
add_test(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>)
endforeach()
# create a test, generate port numbers and set them as env vars for the test
add_test(NAME ${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}>)
get_test_ports(TEST_PORT_COUNT TEST_PORT_DEFINITIONS)
set_tests_properties(${TEST_NAME} PROPERTIES ENVIRONMENT "${TEST_PORT_DEFINITIONS}")
# do the same for valgrind tests
if(ENABLE_VALGRIND_TESTS)
foreach(test_name IN LISTS tests)
add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
--suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
endforeach()
add_test(${TEST_NAME}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
--suppressions=${PROJECT_SOURCE_DIR}/tests/library_valgrind.supp ${CMAKE_BINARY_DIR}/tests/${TEST_NAME})
get_test_ports(TEST_PORT_COUNT VALGRIND_TEST_PORT_DEFINITIONS)
set_tests_properties(${TEST_NAME}_valgrind PROPERTIES ENVIRONMENT "${VALGRIND_TEST_PORT_DEFINITIONS}")
endif()
endfunction()
foreach(test_name IN LISTS client_tests)
add_test(${test_name}_valgrind valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1
--suppressions=${PROJECT_SOURCE_DIR}/tests/ld.supp ${CMAKE_BINARY_DIR}/tests/${test_name})
endforeach()
# all the tests that don't require SSH and TLS
libnetconf2_test(NAME test_client_messages)
libnetconf2_test(NAME test_client_thread)
libnetconf2_test(NAME test_fd_comm)
libnetconf2_test(NAME test_io)
libnetconf2_test(NAME test_thread_messages)
libnetconf2_test(NAME test_unix_socket)
# tests depending on SSH/TLS
if(ENABLE_SSH_TLS)
libnetconf2_test(NAME test_auth_ssh)
libnetconf2_test(NAME test_authkeys)
libnetconf2_test(NAME test_cert_exp_notif)
libnetconf2_test(NAME test_ch PORT_COUNT 2)
libnetconf2_test(NAME test_client_monitoring)
libnetconf2_test(NAME test_endpt_share_clients PORT_COUNT 4)
libnetconf2_test(NAME test_ks_ts)
if (LIBPAM_HAVE_CONFDIR)
libnetconf2_test(NAME test_pam WRAP_FUNCS pam_start)
endif()
libnetconf2_test(NAME test_replace)
libnetconf2_test(NAME test_runtime_changes PORT_COUNT 2)
libnetconf2_test(NAME test_tls)
libnetconf2_test(NAME test_two_channels)
endif()
include_directories(${CMAKE_SOURCE_DIR}/src ${PROJECT_BINARY_DIR})
configure_file("${PROJECT_SOURCE_DIR}/tests/config.h.in" "${PROJECT_BINARY_DIR}/tests/config.h" ESCAPE_QUOTES @ONLY)
# compile PAM test module
add_library(pam_netconf SHARED ${CMAKE_SOURCE_DIR}/tests/pam/pam_netconf.c)
set_target_properties(pam_netconf PROPERTIES PREFIX "")
target_link_libraries(pam_netconf ${LIBPAM_LIBRARIES})
# generate PAM configuration file
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/netconf.conf
"#%PAM-1.4\n"
"auth required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
"account required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
"password required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
)

View file

@ -1,125 +0,0 @@
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <cmocka.h>
#include <libyang/libyang.h>
#include <log.h>
#include <session_client.h>
#include "tests/config.h"
static int
setup_f(void **state)
{
(void)state;
nc_verbosity(NC_VERB_VERBOSE);
return 0;
}
static int
teardown_f(void **state)
{
(void)state;
return 0;
}
static void
test_nc_client_setting_schema_searchpath(void **state)
{
(void)state;
const char *path;
int ret;
/* initiate client */
nc_client_init();
path = nc_client_get_schema_searchpath();
assert_null(path);
ret = nc_client_set_schema_searchpath("path");
assert_int_equal(ret, 0);
path = nc_client_get_schema_searchpath();
assert_string_equal(path, "path");
ret = nc_client_set_schema_searchpath("path1");
assert_int_equal(ret, 0);
path = nc_client_get_schema_searchpath();
assert_string_equal(path, "path1");
}
LY_ERR
test_clb(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev, void *user_data,
LYS_INFORMAT *format, const char **model_data, void (**free_module_data)(void *model_data, void *user_data))
{
(void)mod_name;
(void)mod_rev;
(void)submod_name;
(void)sub_rev;
(void)user_data;
(void)format;
(void)model_data;
(void)free_module_data;
return LY_SUCCESS;
}
LY_ERR
test_clb1(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev, void *user_data,
LYS_INFORMAT *format, const char **model_data, void (**free_module_data)(void *model_data, void *user_data))
{
(void)mod_name;
(void)mod_rev;
(void)submod_name;
(void)sub_rev;
(void)user_data;
(void)format;
(void)model_data;
(void)free_module_data;
return LY_SUCCESS;
}
static void
test_nc_client_setting_schema_callback(void **state)
{
(void)state;
ly_module_imp_clb ret_f;
char *data_ret;
int ret;
ret_f = nc_client_get_schema_callback((void **)&data_ret);
assert_null(ret_f);
assert_null(data_ret);
ret = nc_client_set_schema_callback(test_clb, "DATA");
assert_int_equal(ret, 0);
ret_f = nc_client_get_schema_callback((void **)&data_ret);
assert_ptr_equal(test_clb, ret_f);
assert_string_equal("DATA", data_ret);
ret = nc_client_set_schema_callback(test_clb1, "DATA1");
assert_int_equal(ret, 0);
ret_f = nc_client_get_schema_callback((void **)&data_ret);
assert_ptr_equal(test_clb1, ret_f);
assert_string_equal("DATA1", data_ret);
/* destroy client */
nc_client_destroy();
}
int
main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_nc_client_setting_schema_searchpath, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_setting_schema_callback, setup_f, teardown_f),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -1,813 +0,0 @@
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <cmocka.h>
#include <config.h>
#include <libyang/libyang.h>
#include <log.h>
#include <session_client.h>
#include <session_client_ch.h>
#include <session_p.h>
#include "tests/config.h"
#include <libssh/callbacks.h>
#include <libssh/libssh.h>
#include <libssh/server.h>
static int
ssh_hostkey_check_clb(const char *hostname, ssh_session session, void *priv)
{
(void)hostname;
(void)session;
(void)priv;
return 0;
}
static int
setup_f(void **state)
{
(void)state;
int ret;
nc_verbosity(NC_VERB_VERBOSE);
ret = nc_client_ssh_set_username("username");
assert_int_equal(ret, 0);
ret = nc_client_ssh_ch_set_username("ch_username");
assert_int_equal(ret, 0);
nc_client_ssh_set_auth_hostkey_check_clb(ssh_hostkey_check_clb, NULL);
return 0;
}
static int
teardown_f(void **state)
{
(void)state;
return 0;
}
MOCK int
__wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
(void)sockfd;
(void)addr;
(void)addrlen;
return (int)mock();
}
MOCK int
__wrap_ssh_connect(ssh_session session)
{
(void)session;
/* set support of all authentication methods by fake server */
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE);
return (int)mock();
}
MOCK int
__wrap_ssh_userauth_none(ssh_session session, const char *username)
{
(void)session;
(void)username;
return (int)mock();
}
MOCK int
__wrap_ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods)
{
(void)session;
(void)user;
(void)submethods;
return (int)mock();
}
MOCK int
__wrap_ssh_is_connected(ssh_session session)
{
(void)session;
return (int)mock();
}
MOCK int
__wrap_ssh_channel_open_session(ssh_channel channel)
{
(void)channel;
return (int)mock();
}
MOCK int
__wrap_ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem)
{
(void)channel;
(void)subsystem;
return (int)mock();
}
MOCK int
__wrap_ssh_channel_is_closed(ssh_channel channel)
{
(void)channel;
return 0;
}
MOCK int
__wrap_ssh_channel_write(ssh_channel channel, const void *data, uint32_t len)
{
(void)channel;
(void)data;
return len;
}
MOCK int
__wrap_ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr)
{
(void)channel;
(void)timeout;
(void)is_stderr;
return (int)mock();
}
MOCK int
__wrap_ssh_userauth_password(ssh_session session, const char *username, const char *password)
{
(void)session;
check_expected(password);
check_expected(username);
return (int)mock();
}
MOCK int
__wrap_nc_handshake_io(struct nc_session *session)
{
(void)session;
return (int)mock();
}
MOCK int
__wrap_nc_ctx_check_and_fill(struct nc_session *session)
{
(void)session;
return (int)mock();
}
MOCK int
__wrap_ssh_userauth_try_publickey(ssh_session session, const char *username, const ssh_key pubkey)
{
(void)session;
(void)username;
(void)pubkey;
return (int)mock();
}
MOCK int
__wrap_ssh_userauth_publickey(ssh_session session, const char *username, const ssh_key privkey)
{
(void)session;
(void)username;
(void)privkey;
return (int)mock();
}
MOCK int
__wrap_nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka)
{
(void)address;
(void)port;
(void)ka;
return (int)mock();
}
MOCK int
__wrap_nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, char **host, uint16_t *port, uint16_t *idx)
{
(void)binds;
(void)bind_count;
(void)timeout;
(void)host;
(void)port;
*idx = 0;
return (int)mock();
}
MOCK struct nc_session *
__wrap_nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout)
{
(void)sock;
(void)host;
(void)port;
(void)ctx;
(void)timeout;
return mock_ptr_type(struct nc_session *);
}
static int
test_hostkey_clb(const char *hostname, ssh_session session, void *priv)
{
(void)hostname;
(void)session;
(void)priv;
return 0;
}
static void
test_nc_client_ssh_setting_auth_hostkey_check_clb(void **state)
{
(void)state;
int (*ret_f)(const char *hostname, ssh_session session, void *priv);
char *priv_data_ret;
/* ssh_hostkey_check_clb is set in setup_f */
nc_client_ssh_get_auth_hostkey_check_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(ret_f, ssh_hostkey_check_clb);
assert_null(priv_data_ret);
/* set different callback and private data */
nc_client_ssh_set_auth_hostkey_check_clb(test_hostkey_clb, "DATA");
nc_client_ssh_get_auth_hostkey_check_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(ret_f, test_hostkey_clb);
assert_string_equal(priv_data_ret, "DATA");
}
char *
test_pwd_clb1(const char *username, const char *hostname, void *priv)
{
char *pass, *pass_to_return;
check_expected(username);
check_expected(hostname);
check_expected(priv);
pass = (char *)mock();
pass_to_return = malloc(sizeof *pass * (strlen(pass) + 1));
strcpy(pass_to_return, pass);
return pass_to_return;
}
char *
test_pwd_clb2(const char *username, const char *hostname, void *priv)
{
(void)username;
(void)hostname;
(void)priv;
return 0;
}
static void
test_nc_client_ssh_setting_auth_password_clb(void **state)
{
(void)state;
char *(*ret_f)(const char *username, const char *hostname, void *priv);
char *priv_data_ret;
/* set callback */
nc_client_ssh_set_auth_password_clb(test_pwd_clb1, "DATA");
nc_client_ssh_get_auth_password_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(test_pwd_clb1, ret_f);
assert_string_equal("DATA", priv_data_ret);
/* set different callback */
nc_client_ssh_set_auth_password_clb(test_pwd_clb2, "NEW DATA");
nc_client_ssh_get_auth_password_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(test_pwd_clb2, ret_f);
assert_string_equal("NEW DATA", priv_data_ret);
}
char *
test_inter_clb1(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv)
{
(void)auth_name;
(void)instruction;
(void)prompt;
(void)echo;
(void)priv;
return 0;
}
char *
test_inter_clb2(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv)
{
(void)auth_name;
(void)instruction;
(void)prompt;
(void)echo;
(void)priv;
return 0;
}
static void
test_nc_client_ssh_setting_auth_interactive_clb(void **state)
{
(void)state;
char *(*ret_f)(const char *auth_name, const char *instruction, const char *prompt, int echo, void *priv);
char *priv_data_ret;
/* set callback */
nc_client_ssh_set_auth_interactive_clb(test_inter_clb1, "DATA");
nc_client_ssh_get_auth_interactive_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(test_inter_clb1, ret_f);
assert_string_equal("DATA", priv_data_ret);
/* set diferent callback */
nc_client_ssh_set_auth_interactive_clb(test_inter_clb2, "NEW DATA");
nc_client_ssh_get_auth_interactive_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(test_inter_clb2, ret_f);
assert_string_equal("NEW DATA", priv_data_ret);
}
char *
test_passphrase_clb1(const char *privkey_path, void *priv)
{
(void)privkey_path;
(void)priv;
return 0;
}
char *
test_passphrase_clb2(const char *privkey_path, void *priv)
{
(void)privkey_path;
(void)priv;
return 0;
}
static void
test_nc_client_ssh_setting_auth_privkey_passphrase_clb(void **state)
{
(void)state;
char *(*ret_f)(const char *privkey_path, void *priv);
char *priv_data_ret;
/* set first callback */
nc_client_ssh_set_auth_privkey_passphrase_clb(test_passphrase_clb1, "DATA");
nc_client_ssh_get_auth_privkey_passphrase_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(ret_f, test_passphrase_clb1);
assert_string_equal("DATA", priv_data_ret);
/* set different callback */
nc_client_ssh_set_auth_privkey_passphrase_clb(test_passphrase_clb2, "NEW DATA");
nc_client_ssh_get_auth_privkey_passphrase_clb(&ret_f, (void **)&priv_data_ret);
assert_ptr_equal(ret_f, test_passphrase_clb2);
assert_string_equal("NEW DATA", priv_data_ret);
}
static void
test_nc_client_ssh_adding_keypair(void **state)
{
(void)state;
int ret;
const char *pubkey1, *pubkey2;
/* at the beginning keypair count should be 0 */
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 0);
/* add first key pair */
ret = nc_client_ssh_add_keypair(TESTS_DIR "/data/key_dsa.pub", TESTS_DIR "/data/key_dsa");
assert_int_equal(ret, 0);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 1);
/* add second keypair */
ret = nc_client_ssh_add_keypair("key_pub", "key_priv");
assert_int_equal(ret, 0);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 2);
ret = nc_client_ssh_get_keypair(1, &pubkey1, &pubkey2);
assert_int_equal(ret, 0);
assert_string_equal(pubkey1, "key_pub");
assert_string_equal(pubkey2, "key_priv");
/* delete first keypair */
ret = nc_client_ssh_del_keypair(0);
assert_int_equal(ret, 0);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 1);
/* try to get deleted keypair */
ret = nc_client_ssh_get_keypair(5, &pubkey1, &pubkey2);
assert_int_equal(ret, -1);
/* try to add keypair that is already set */
ret = nc_client_ssh_add_keypair("key_pub", "key_priv");
assert_int_equal(ret, -1);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 1);
/* try to delete keypair with id that is not used */
ret = nc_client_ssh_del_keypair(42);
assert_int_equal(ret, -1);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 1);
/* remove remaining keypairs */
ret = nc_client_ssh_del_keypair(0);
assert_int_equal(ret, 0);
ret = nc_client_ssh_get_keypair_count();
assert_int_equal(ret, 0);
}
static void
test_nc_client_ssh_setting_auth_pref(void **state)
{
(void)state;
int ret;
/* initiate client, must be called in first test */
nc_client_init();
/* check default prefference settings according to documentation */
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_INTERACTIVE);
assert_int_equal(ret, 3);
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_PASSWORD);
assert_int_equal(ret, 2);
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_PUBLICKEY);
assert_int_equal(ret, 1);
/* try to set prefetence of non existing method */
nc_client_ssh_set_auth_pref(42, 22);
/* try to get preference of non existing method */
ret = nc_client_ssh_get_auth_pref(42);
assert_int_equal(ret, 0);
/* change values of all methods and check if they actually changed */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 9);
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_INTERACTIVE);
assert_int_equal(ret, 9);
/* negative value should be set as -1 */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PASSWORD, -5);
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_PASSWORD);
assert_int_equal(ret, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, 11);
ret = nc_client_ssh_get_auth_pref(NC_SSH_AUTH_PUBLICKEY);
assert_int_equal(ret, 11);
}
static void
test_nc_client_ssh_setting_username(void **state)
{
(void)state;
int ret;
const char *username_ret;
username_ret = nc_client_ssh_get_username();
/* username is set to "username" in setup_f */
assert_string_equal(username_ret, "username");
/* set new username and check if it changes */
ret = nc_client_ssh_set_username("new_username");
assert_int_equal(ret, 0);
username_ret = nc_client_ssh_get_username();
assert_string_equal(username_ret, "new_username");
}
static void
test_nc_connect_ssh_interactive_succesfull(void **state)
{
(void)state;
struct nc_session *session;
/* set authentication method to use interactive authentication */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PASSWORD, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 20);
/* prepare return values for functions used by nc_connect_ssh */
will_return(__wrap_connect, 0);
will_return(__wrap_ssh_connect, 0);
will_return(__wrap_ssh_userauth_none, 1);
will_return(__wrap_ssh_userauth_kbdint, 0);
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_ssh_channel_open_session, 0);
will_return(__wrap_ssh_channel_request_subsystem, 0);
will_return(__wrap_nc_handshake_io, 3);
will_return(__wrap_nc_ctx_check_and_fill, 0);
session = nc_connect_ssh("127.0.0.1", 8080, NULL);
assert_non_null(session);
will_return(__wrap_ssh_channel_poll_timeout, 0);
nc_session_free(session, NULL);
}
static void
test_nc_connect_ssh_password_succesfull(void **state)
{
(void)state;
struct nc_session *session;
/* set authentication method to use password authentication */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PASSWORD, 1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, -1);
/* set authentication callback */
nc_client_ssh_set_auth_password_clb(test_pwd_clb1, "private_data");
will_return(test_pwd_clb1, "secret password");
/* set values that are expected as parameters for authentication callback */
expect_string(test_pwd_clb1, username, "username");
expect_string(test_pwd_clb1, hostname, "127.0.0.1");
expect_string(test_pwd_clb1, priv, "private_data");
/* fake succesfull connection */
will_return(__wrap_connect, 0);
will_return(__wrap_ssh_connect, 0);
/* do not authenticate using no authentication method */
will_return(__wrap_ssh_userauth_none, 1);
/* succesfully authenticate via password authentication */
expect_string(__wrap_ssh_userauth_password, password, "secret password");
expect_string(__wrap_ssh_userauth_password, username, "username");
will_return(__wrap_ssh_userauth_password, 0);
/* fake ssh functions that are used to open netconf channel */
will_return(__wrap_ssh_channel_open_session, 0);
will_return(__wrap_ssh_channel_request_subsystem, 0);
/* fake that connection is still alive*/
will_return(__wrap_ssh_is_connected, 1);
/* fake ssh function for recieving hello message */
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_nc_handshake_io, 3);
will_return(__wrap_nc_ctx_check_and_fill, 0);
session = nc_connect_ssh("127.0.0.1", 8080, NULL);
assert_non_null(session);
/* disconnect */
will_return(__wrap_ssh_channel_poll_timeout, 0);
nc_session_free(session, NULL);
}
static void
test_nc_connect_ssh_pubkey_succesfull(void **state)
{
(void)state;
struct nc_session *session;
int ret = 0;
/* set authentication method to use password authentication */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PASSWORD, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, 1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, -1);
/* add keypair for authentication */
ret = nc_client_ssh_add_keypair(TESTS_DIR "/data/key_dsa.pub", TESTS_DIR "/data/key_dsa");
assert_int_equal(ret, 0);
/* fake succesfull connection */
will_return(__wrap_connect, 0);
will_return(__wrap_ssh_connect, 0);
/* do not authenticate using no authentication method */
will_return(__wrap_ssh_userauth_none, 1);
will_return(__wrap_ssh_userauth_try_publickey, 0);
will_return(__wrap_ssh_userauth_publickey, 0);
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_ssh_channel_open_session, 0);
will_return(__wrap_ssh_channel_request_subsystem, 0);
/* fake ssh function for recieving hello message */
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_nc_handshake_io, 3);
will_return(__wrap_nc_ctx_check_and_fill, 0);
session = nc_connect_ssh("127.0.0.1", 8080, NULL);
assert_non_null(session);
/* disconnect */
will_return(__wrap_ssh_channel_poll_timeout, 0);
nc_session_free(session, NULL);
}
static void
test_nc_connect_connection_failed(void **state)
{
(void)state;
struct nc_session *session;
errno = ECONNREFUSED;
will_return(__wrap_connect, -1);
will_return(__wrap_ssh_is_connected, 0);
session = nc_connect_ssh("127.0.0.1", 8080, NULL);
assert_null(session);
}
static void
test_nc_connect_ssh_bad_hello(void **state)
{
(void)state;
struct nc_session *session;
/* set authentication method to use interactive authentication */
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PASSWORD, -1);
nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, 1);
nc_client_ssh_set_auth_password_clb(test_pwd_clb2, NULL);
will_return(__wrap_connect, 0);
will_return(__wrap_ssh_connect, 0);
will_return(__wrap_ssh_userauth_none, 1);
will_return(__wrap_ssh_userauth_kbdint, 0);
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_ssh_is_connected, 1);
will_return(__wrap_ssh_channel_open_session, 0);
will_return(__wrap_ssh_channel_request_subsystem, 0);
will_return(__wrap_nc_handshake_io, 4);
session = nc_connect_ssh("127.0.0.1", 8080, NULL);
assert_null(session);
/* destroy client, must be called in last test */
nc_client_destroy();
}
static void
test_nc_client_ssh_ch_setting_username(void **state)
{
(void)state;
const char *username_ret;
int ret;
/* username is set to "ch_username" in setup_f */
username_ret = nc_client_ssh_ch_get_username();
assert_string_equal(username_ret, "ch_username");
/* set new username and check if it changes */
ret = nc_client_ssh_ch_set_username("new_ch_username");
assert_int_equal(ret, 0);
username_ret = nc_client_ssh_ch_get_username();
assert_string_equal(username_ret, "new_ch_username");
}
static void
test_nc_client_ssh_ch_add_bind_listen(void **state)
{
(void)state;
int ret;
/* invalid parameters, address NULL or port 0 */
ret = nc_client_ssh_ch_add_bind_listen(NULL, 4334);
assert_int_equal(ret, -1);
ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 0);
assert_int_equal(ret, -1);
/* failed to create an ssh listening socket */
will_return(__wrap_nc_sock_listen_inet, -1);
ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 4334);
assert_int_equal(ret, -1);
/* fake a successful CH ssh listening socket */
will_return(__wrap_nc_sock_listen_inet, 1);
ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 4334);
assert_int_equal(ret, 0);
/* remove ssh listening client binds */
ret = nc_client_ssh_ch_del_bind("127.0.0.1", 4334);
assert_int_equal(ret, 0);
}
static void
test_nc_accept_callhome(void **state)
{
(void)state;
struct nc_session *session = NULL;
int timeout = 10;
int ret;
/* invalid parameter session */
ret = nc_accept_callhome(timeout, NULL, NULL);
assert_int_equal(ret, -1);
/* no client bind */
ret = nc_accept_callhome(timeout, NULL, &session);
assert_int_equal(ret, -1);
/* successfully add a client Call Home bind */
will_return(__wrap_nc_sock_listen_inet, 1);
ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 4334);
assert_int_equal(ret, 0);
/* failed to accept a client bind */
will_return(__wrap_nc_sock_accept_binds, -1);
ret = nc_accept_callhome(timeout, NULL, &session);
assert_int_equal(ret, -1);
/* failed to accept a server Call Home connection */
will_return(__wrap_nc_accept_callhome_ssh_sock, NULL);
will_return(__wrap_nc_sock_accept_binds, 2);
ret = nc_accept_callhome(timeout, NULL, &session);
assert_int_equal(ret, -1);
/* create session structure to fake a successful server call home connection */
session = nc_new_session(NC_CLIENT, 0);
assert_non_null(session);
will_return(__wrap_nc_sock_accept_binds, 2);
will_return(__wrap_nc_accept_callhome_ssh_sock, session);
ret = nc_accept_callhome(timeout, NULL, &session);
assert_int_equal(ret, 1);
/* remove ssh listening client binds */
ret = nc_client_ssh_ch_del_bind("127.0.0.1", 4334);
assert_int_equal(ret, 0);
/* free session */
nc_session_free(session, NULL);
}
static void
test_nc_client_ssh_callhome_successful(void **state)
{
(void)state;
struct nc_session *session = NULL;
int timeout = 10;
int ret;
/* create session structure */
session = nc_new_session(NC_CLIENT, 0);
assert_non_null(session);
/* prepare to fake return values for functions used by nc_accept_callhome */
will_return(__wrap_nc_sock_listen_inet, 1);
will_return(__wrap_nc_sock_accept_binds, 2);
will_return(__wrap_nc_accept_callhome_ssh_sock, session);
ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 4334);
assert_int_equal(ret, 0);
ret = nc_accept_callhome(timeout, NULL, &session);
assert_int_equal(ret, 1);
/* remove ssh listening client binds */
ret = nc_client_ssh_ch_del_bind("127.0.0.1", 4334);
assert_int_equal(ret, 0);
/* free session */
nc_session_free(session, NULL);
}
int
main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_auth_pref, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_auth_hostkey_check_clb, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_auth_password_clb, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_auth_interactive_clb, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_auth_privkey_passphrase_clb, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_adding_keypair, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_setting_username, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_ssh_interactive_succesfull, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_ssh_password_succesfull, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_ssh_pubkey_succesfull, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_connection_failed, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_ssh_bad_hello, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_ch_setting_username, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_ch_add_bind_listen, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_accept_callhome, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_ssh_callhome_successful, setup_f, teardown_f),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -1,200 +0,0 @@
#include <errno.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <cmocka.h>
#include <config.h>
#include <libyang/libyang.h>
#include <log.h>
#include <session_client.h>
#include "tests/config.h"
static int
setup_f(void **state)
{
(void)state;
nc_verbosity(NC_VERB_VERBOSE);
return 0;
}
static int
teardown_f(void **state)
{
(void)state;
return 0;
}
MOCK int
__wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
(void)sockfd;
(void)addr;
(void)addrlen;
return (int)mock();
}
MOCK int
__wrap_SSL_connect(SSL *ssl)
{
(void)ssl;
return (int)mock();
}
MOCK int
__wrap_nc_handshake_io(struct nc_session *session)
{
(void)session;
return (int)mock();
}
MOCK int
__wrap_nc_ctx_check_and_fill(struct nc_session *session)
{
(void)session;
return (int)mock();
}
static void
test_nc_client_tls_setting_cert_key_paths(void **state)
{
(void)state;
const char *cert, *key;
int ret;
nc_client_init();
/* no certificats are set, nc_client_tls_get_cert_key_paths should output NULL */
nc_client_tls_get_cert_key_paths(&cert, &key);
assert_null(cert);
assert_null(key);
/* set certificate path */
ret = nc_client_tls_set_cert_key_paths("cert_path", "key_path");
assert_int_equal(ret, 0);
nc_client_tls_get_cert_key_paths(&cert, &key);
assert_string_equal(cert, "cert_path");
assert_string_equal(key, "key_path");
/* override certificate path */
ret = nc_client_tls_set_cert_key_paths("cert_path1", "key_path1");
assert_int_equal(ret, 0);
nc_client_tls_get_cert_key_paths(&cert, &key);
assert_string_equal(cert, "cert_path1");
assert_string_equal(key, "key_path1");
}
static void
test_nc_client_tls_setting_trusted_ca_paths(void **state)
{
(void)state;
const char *file, *dir;
int ret;
ret = nc_client_tls_set_trusted_ca_paths("ca_file", "ca_dir");
assert_int_equal(ret, 0);
nc_client_tls_get_trusted_ca_paths(&file, &dir);
assert_string_equal("ca_file", file);
assert_string_equal("ca_dir", dir);
ret = nc_client_tls_set_trusted_ca_paths("ca_file1", "ca_dir1");
assert_int_equal(ret, 0);
nc_client_tls_get_trusted_ca_paths(&file, &dir);
assert_string_equal("ca_file1", file);
assert_string_equal("ca_dir1", dir);
}
static void
test_nc_connect_tls_succesfull(void **state)
{
(void)state;
int ret;
struct nc_session *session;
ret = nc_client_tls_set_cert_key_paths(TESTS_DIR "/data/client.crt", TESTS_DIR "/data/client.key");
assert_int_equal(ret, 0);
ret = nc_client_tls_set_trusted_ca_paths(NULL, TESTS_DIR "/data");
assert_int_equal(ret, 0);
will_return(__wrap_connect, 0);
will_return(__wrap_SSL_connect, 1);
/* fake succesfull handshake */
will_return(__wrap_nc_handshake_io, 3);
will_return(__wrap_nc_ctx_check_and_fill, 0);
session = nc_connect_tls("0.0.0.0", 6001, NULL);
assert_non_null(session);
nc_session_free(session, NULL);
}
static void
test_nc_client_tls_setting_crl_paths(void **state)
{
(void)state;
const char *file, *dir;
int ret;
nc_client_tls_get_crl_paths(&file, &dir);
assert_null(file);
assert_null(dir);
ret = nc_client_tls_set_crl_paths("file", "dir");
assert_int_equal(ret, 0);
nc_client_tls_get_crl_paths(&file, &dir);
assert_string_equal(file, "file");
assert_string_equal(dir, "dir");
ret = nc_client_tls_set_crl_paths("file1", "dir1");
assert_int_equal(ret, 0);
nc_client_tls_get_crl_paths(&file, &dir);
assert_string_equal(file, "file1");
assert_string_equal(dir, "dir1");
/* destroy client */
nc_client_destroy();
}
static void
test_nc_connect_tls_handshake_failed(void **state)
{
(void)state;
int ret;
struct nc_session *session;
ret = nc_client_tls_set_cert_key_paths(TESTS_DIR "/data/client.crt", TESTS_DIR "/data/client.key");
assert_int_equal(ret, 0);
ret = nc_client_tls_set_trusted_ca_paths(NULL, TESTS_DIR "/data");
assert_int_equal(ret, 0);
will_return(__wrap_connect, 0);
will_return(__wrap_SSL_connect, 1);
/* fake failed handshake */
will_return(__wrap_nc_handshake_io, 0);
session = nc_connect_tls("0.0.0.0", 6001, NULL);
assert_null(session);
}
int
main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_nc_client_tls_setting_cert_key_paths, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_tls_handshake_failed, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_connect_tls_succesfull, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_tls_setting_trusted_ca_paths, setup_f, teardown_f),
cmocka_unit_test_setup_teardown(test_nc_client_tls_setting_crl_paths, setup_f, teardown_f),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View file

@ -19,6 +19,24 @@
#endif
#define TESTS_DIR "@CMAKE_SOURCE_DIR@/tests"
#define MODULES_DIR "@CMAKE_SOURCE_DIR@/modules"
#define BUILD_DIR "@CMAKE_BINARY_DIR@"
#cmakedefine HAVE_MBEDTLS
@SSH_MACRO@
@TLS_MACRO@
/* nc_server.h local includes (not to use the installed ones) */
#include "netconf.h"
#include "log.h"
#include "messages_server.h"
#include "server_config.h"
#include "session_server.h"
#include "session_server_ch.h"
/* nc_client.h local includes (not to use the installed ones) */
#include "messages_client.h"
#include "session_client.h"
#include "session_client_ch.h"

1
tests/data/0b527f1f.0 Symbolic link
View file

@ -0,0 +1 @@
ec_serverca.pem

Some files were not shown because too many files have changed in this diff Show more