diff --git a/.travis.yml b/.travis.yml index e5da833e..2eb977e3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,11 +26,11 @@ before_install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then bash .travis-deps-osx.sh; fi script: - - cd $TRAVIS_BUILD_DIR && mkdir build_none && cd build_none ; cmake -DENABLE_TLS=OFF -DENABLE_SSH=OFF -DENABLE_DNSSEC=OFF .. && make -j2 && make test - - cd $TRAVIS_BUILD_DIR && mkdir build_tls && cd build_tls ; cmake -DENABLE_TLS=ON -DENABLE_SSH=OFF -DENABLE_DNSSEC=OFF .. && make -j2 && make test - - cd $TRAVIS_BUILD_DIR && mkdir build_ssh && cd build_ssh ; cmake -DENABLE_TLS=OFF -DENABLE_SSH=ON -DENABLE_DNSSEC=OFF .. && make -j2 && make test - - cd $TRAVIS_BUILD_DIR && mkdir build_ssh_tls && cd build_ssh_tls ; cmake -DENABLE_TLS=ON -DENABLE_SSH=ON -DENABLE_DNSSEC=OFF .. && make -j2 && make test - - cd $TRAVIS_BUILD_DIR && mkdir build_all && cd build_all ; cmake -DENABLE_TLS=ON -DENABLE_SSH=ON -DENABLE_DNSSEC=ON .. && make -j2 && make test + - cd $TRAVIS_BUILD_DIR && mkdir build_none && cd build_none ; cmake -DENABLE_TLS=OFF -DENABLE_SSH=OFF -DENABLE_DNSSEC=OFF .. && make -j2 && ctest -V + - cd $TRAVIS_BUILD_DIR && mkdir build_tls && cd build_tls ; cmake -DENABLE_TLS=ON -DENABLE_SSH=OFF -DENABLE_DNSSEC=OFF .. && make -j2 && ctest -V + - cd $TRAVIS_BUILD_DIR && mkdir build_ssh && cd build_ssh ; cmake -DENABLE_TLS=OFF -DENABLE_SSH=ON -DENABLE_DNSSEC=OFF .. && make -j2 && ctest -V + - cd $TRAVIS_BUILD_DIR && mkdir build_ssh_tls && cd build_ssh_tls ; cmake -DENABLE_TLS=ON -DENABLE_SSH=ON -DENABLE_DNSSEC=OFF .. && make -j2 && ctest -V + - cd $TRAVIS_BUILD_DIR && mkdir build_all && cd build_all ; cmake -DENABLE_TLS=ON -DENABLE_SSH=ON -DENABLE_DNSSEC=ON .. && make -j2 && ctest -V after_success: - if [ "$TRAVIS_OS_NAME" = "linux" -a "$CC" = "gcc" ]; then codecov; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 9cdd341a..830ea9af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,11 @@ cmake_minimum_required(VERSION 2.6) +project(libnetconf2 C) +include(GNUInstallDirs) include (CheckFunctionExists) # include custom Modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/") -project(libnetconf2 C) set(LIBNETCONF2_DESCRIPTION "NETCONF server and client library in C.") # check the supported platform @@ -12,19 +13,8 @@ if(NOT UNIX) message(FATAL_ERROR "Only *nix like systems are supported.") endif() -if(NOT LIB_INSTALL_DIR) - set(LIB_INSTALL_DIR lib) -endif() - -if(NOT INCLUDE_INSTALL_DIR) - set(INCLUDE_INSTALL_DIR include) -endif() - -set(INCLUDE_INSTALL_SUBDIR ${INCLUDE_INSTALL_DIR}/libnetconf2) - -if(NOT DATA_INSTALL_DIR) - set(DATA_INSTALL_DIR share/libnetconf2) -endif() +set(INCLUDE_INSTALL_SUBDIR ${CMAKE_INSTALL_INCLUDEDIR}/libnetconf2) +set(DATA_INSTALL_DIR ${CMAKE_INSTALL_DATADIR}/libnetconf2) # set default build type if not specified by user if(NOT CMAKE_BUILD_TYPE) @@ -37,8 +27,8 @@ set(CMAKE_C_FLAGS_DEBUG "-g -O0") # set version set(LIBNETCONF2_MAJOR_VERSION 0) -set(LIBNETCONF2_MINOR_VERSION 7) -set(LIBNETCONF2_MICRO_VERSION 49) +set(LIBNETCONF2_MINOR_VERSION 8) +set(LIBNETCONF2_MICRO_VERSION 56) set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION}) set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}) @@ -46,6 +36,9 @@ set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSI option(ENABLE_SSH "Enable NETCONF over SSH support (via libssh)" ON) option(ENABLE_TLS "Enable NETCONF over TLS support (via OpenSSL)" ON) option(ENABLE_DNSSEC "Enable support for SSHFP retrieval using DNSSEC for SSH (requires OpenSSL and libval)" OFF) +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") if(ENABLE_DNSSEC AND NOT ENABLE_SSH) message(WARNING "DNSSEC SSHFP retrieval cannot be used without SSH support.") @@ -151,17 +144,12 @@ if(DOXYGEN_FOUND) configure_file(Doxyfile.in Doxyfile) endif() -# option - partial message read timeout in seconds (also used for internal RPC reply wait) -if(NOT READ_TIMEOUT) - set(READ_TIMEOUT 30) -endif() - # install library -install(TARGETS netconf2 DESTINATION ${LIB_INSTALL_DIR}) +install(TARGETS netconf2 DESTINATION ${CMAKE_INSTALL_LIBDIR}) # install headers -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nc_client.h DESTINATION ${INCLUDE_INSTALL_DIR}) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nc_server.h DESTINATION ${INCLUDE_INSTALL_DIR}) +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} DESTINATION ${INCLUDE_INSTALL_SUBDIR}) # install schemas @@ -174,14 +162,14 @@ install( 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_PREFIX}/${LIB_INSTALL_DIR}/pkgconfig") + install(FILES "${CMAKE_CURRENT_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) - string(REGEX MATCH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/pkgconfig" SUBSTR "${PC_PATH}") + string(REGEX MATCH "${CMAKE_INSTALL_LIBDIR}/pkgconfig" SUBSTR "${PC_PATH}") string(LENGTH "${SUBSTR}" SUBSTR_LEN) if(SUBSTR_LEN EQUAL 0) - message(WARNING "pkg-config will not detect the new package after installation, adjust PKG_CONFIG_PATH using \"export PKG_CONFIG_PATH=\${PKG_CONFIG_PATH}:${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/pkgconfig\".") + message(WARNING "pkg-config will not detect the new package after installation, adjust PKG_CONFIG_PATH using \"export PKG_CONFIG_PATH=\${PKG_CONFIG_PATH}:${CMAKE_INSTALL_LIBDIR}/pkgconfig\".") endif() endif() endif() diff --git a/README.md b/README.md index b92400c6..f7fd08fd 100644 --- a/README.md +++ b/README.md @@ -9,31 +9,79 @@ and servers. NETCONF is the [NETwork CONFiguration protocol] (http://trac.tools.ietf.org/wg/netconf/trac/wiki) introduced by IETF. The library provides functions to connect NETCONF client and server to each -other via SSH and to send, receive and process NETCONF messages. In contrast -to the [previous libnetconf library](https://github.com/CESNET/libnetconf), -**libnetconf2** does not include NETCONF datastore implementation. This -functionality is left specific to the NETCONF server implementation. +other via SSH and to send, receive and process NETCONF messages. **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 of the library is welcome. Please -inform us about your experiences with using **libnetconf2** via the -[issue tracker](https://github.com/CESNET/libnetconf/issues). +[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). + +Besides the [**libyang**](https://github.com/CESNET/libyang), **libnetconf2** is +another basic building block for the [**Netopeer2** toolset] +(https://github.com/CESNET/Netopeer2). For a reference implementation of NETCONF +client and server, check the **Netopeer2** project. + +## libnetconf vs libnetconf2 **libnetconf2** is being developed with experiences gained from the development -of the [libnetconf](https://github.com/CESNET/libnetconf) library. This -previous generation of our NETCONF library is built on libxml2, used to -internally represent all the data. In **libnetconf2**, we have completely -replaced libxml2 by [libyang](https://github.com/CESNET/libyang). The libyang -library is much more efficient in work with YANG modeled data (which is the -case of NETCONF messages) and this advantage then applies also to -**libnetconf2**. The library is connected with YANG, so for example data -validation according to the provided YANG schemas is done internally instead -of using external DSDL tools (as it was in the first generation of libnetconf). - -**libnetconf2** is currently being developed, and some (server-side) functions -are not yet implemented. Feedback and bug reports concerning problems not -mentioned here are appreciated via the issue tracker. +of the [**libnetconf**](https://github.com/CESNET/libnetconf) library. Here are the +main differences between the both libraries that would help you to decide which +of them is more suitable for your needs. + +### libxml2 vs libyang + +To represent the schema and data trees, **libnetconf** uses libxml2, which is +intended for different purposes - schema and data trees connected with YANG +have specific needs and restrictions in comparison to more generic XML. +Therefore, in **libnetconf2**, we have completely replaced libxml2 by [libyang] +(https://github.com/CESNET/libyang). It is much more efficient in work with +YANG modeled data (which is the case of NETCONF messages) and this advantage +then applies also to **libnetconf2**. The library connects data with the YANG +schemas, so for example the data validation according to the provided YANG +schemas is done internally by libyang instead of using external and extremely +slow DSDL tools (as it was in the first generation of libnetconf). + +### Datastore + +**libnetconf** was trying to be all-in-one, so besides the NETCONF transport, +it also implements configuration datastores, NETCONF Access Control Module or +the NETCONF Event Notification storage. In contrast, to allow better design of +the NETCONF servers, **libnetconf2** is focused strictly to the NETCONF +transport and message manipulation. + +Therefore, all the features from **libnetconf** that are connected to the +datastore implementation are not available in **libnetconf2**. In the case of +the Netopeer2 server, all these features (and much more) are implemented as +part of the server itself or its datastore implementation - +[**sysrepo**](https://github.com/sysrepo/sysrepo). + +### Notifications + +While **libnetconf2** is able to send (on the server side) and receive (on the +client side) the NETCONF Event Notification messages, its generation and storage +is left up to the server implementation. In case of the Netopeer2 server, the +Notifications implementation is split between the server itself (managing +subscriptions) and sysrepo (Events storage). + +### Call Home + +Similarly as in case of Notifications, **libnetconf2** provides supporting +functions implementing the Call Home mechanism, but its management (setting the +connection parameters) is supposed to be done in the server. Again, as a +reference implementation, you can check the Netopeer2 server. + +In contrast to **libnetconf**, **libnetconf2** actually implements more of the +Call Home functionality. + +## Features + +* NETCONF v1.0 and v1.1 compliant ([RFC 6241](https://tools.ietf.org/html/rfc6241)) +* NETCONF over SSH ([RFC 6242](https://tools.ietf.org/html/rfc6242)) including Chunked Framing Mechanism + * DNSSEC SSH Key Fingerprints ([RFC 4255](https://tools.ietf.org/html/rfc4255)) +* NETCONF over TLS ([RFC 5539bis](https://tools.ietf.org/html/draft-ietf-netconf-rfc5539bis-05)) +* Transport support for NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)) +* NETCONF Call Home ([NETCONF Call Home Draft](https://tools.ietf.org/html/draft-ietf-netconf-call-home-17)) # Installation @@ -173,6 +221,37 @@ The `Debug` mode is currently used as the default one. to switch to the ``` $ cmake -D CMAKE_BUILD_TYPE:String="Release" .. ``` + +### Inactive Read Timeout + +It is possible to adjust inactive read timeout. It is used when a new message is +being read and no new data had arrived for this amount of seconds. 20 is the default value. + +``` +$ cmake -D READ_INACTIVE_TIMEOUT:String="20" .. +``` + +### Active Read Timeout + +Active read timeout is used to limit the maximum number of seconds a message is given +to arrive in its entirety once a beginning is read. The default is 300 (5 minutes). + +``` +$ cmake -D READ_ACTIVE_TIMEOUT:String="300" .. +``` + +### PSPoll Thread Count + +This value limits the maximum number of threads that can concurrently access +(wait for access) a single pspoll structure. To simplify, how many threads could +simultaneously call a function whose parameter is one and the same pspoll structure. +If using **netopeer2-server**, it will warn that this value needs to be adjusted if +too small. + +``` +$ cmake -D MAX_PSPOLL_THREAD_COUNT:String="6" .. +``` + ### CMake Notes Note that, with CMake, if you want to change the compiler or its options after diff --git a/libnetconf2.pc.in b/libnetconf2.pc.in index 0a934423..a06f1a0d 100644 --- a/libnetconf2.pc.in +++ b/libnetconf2.pc.in @@ -1,9 +1,11 @@ prefix=@CMAKE_INSTALL_PREFIX@ -includedir=${prefix}/@INCLUDE_INSTALL_DIR@ -libdir=${prefix}/@LIB_INSTALL_DIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ Name: @PROJECT_NAME@ Description: @LIBNETCONF2_DESCRIPTION@ Version: @LIBNETCONF2_VERSION@ Libs: -L${libdir} -lnetconf2 Cflags: -I${includedir} + +LNC2_MAX_THREAD_COUNT=@MAX_PSPOLL_THREAD_COUNT@ diff --git a/nc_server.h.in b/nc_server.h.in index a2b6adde..664f4a81 100644 --- a/nc_server.h.in +++ b/nc_server.h.in @@ -3,12 +3,12 @@ * \author Radek Krejci * \brief libnetconf2's main public header for NETCONF servers. * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2017 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 */ diff --git a/src/config.h.in b/src/config.h.in index 0179821a..7b592e85 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -3,7 +3,7 @@ * \author Radek Krejci * \brief libnetconf2 various configuration settings. * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2017 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. @@ -50,9 +50,19 @@ #define SCHEMAS_DIR "@CMAKE_INSTALL_PREFIX@/@DATA_INSTALL_DIR@" /* - * Partial message read timeout in seconds - * (also used as nc_pollsession lock timeout and internal RPC reply timeout) + * Inactive read timeout */ -#define NC_READ_TIMEOUT @READ_TIMEOUT@ +#define NC_READ_INACT_TIMEOUT @READ_INACTIVE_TIMEOUT@ + +/* + * Active read timeout in seconds + * (also used for internal RPC reply timeout) + */ +#define NC_READ_ACT_TIMEOUT @READ_ACTIVE_TIMEOUT@ + +/* + * pspoll structure queue size (also found in nc_server.h) + */ +#define NC_PS_QUEUE_SIZE @MAX_PSPOLL_THREAD_COUNT@ #endif /* NC_CONFIG_H_ */ diff --git a/src/io.c b/src/io.c index 34ecc166..e5ae97e4 100644 --- a/src/io.c +++ b/src/io.c @@ -23,6 +23,7 @@ #include #include #include +#include #ifdef NC_ENABLED_TLS # include @@ -35,11 +36,11 @@ #define BUFFERSIZE 512 static ssize_t -nc_read(struct nc_session *session, char *buf, size_t count, uint16_t *read_timeout) +nc_read(struct nc_session *session, char *buf, size_t count, uint32_t inact_timeout, struct timespec *ts_act_timeout) { size_t readd = 0; ssize_t r = -1; - uint16_t sleep_count = 0; + struct timespec ts_cur, ts_inact_timeout; assert(session); assert(buf); @@ -52,6 +53,8 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint16_t *read_time return 0; } + nc_gettimespec(&ts_inact_timeout); + nc_addtimespec(&ts_inact_timeout, inact_timeout); do { switch (session->ti_type) { case NC_TI_NONE: @@ -61,12 +64,9 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint16_t *read_time /* read via standard file descriptor */ r = read(session->ti.fd.in, buf + readd, count - readd); if (r < 0) { - if (errno == EAGAIN) { + if ((errno == EAGAIN) || (errno == EINTR)) { r = 0; break; - } else if (errno == EINTR) { - usleep(NC_TIMEOUT_STEP); - continue; } else { ERR("Session %u: reading from file descriptor (%d) failed (%s).", session->id, session->ti.fd.in, strerror(errno)); @@ -134,24 +134,29 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint16_t *read_time #endif } - /* nothing read */ if (r == 0) { + /* nothing read */ usleep(NC_TIMEOUT_STEP); - ++sleep_count; - if (1000000 / NC_TIMEOUT_STEP == sleep_count) { - /* we slept a full second */ - --(*read_timeout); - sleep_count = 0; - } - if (!*read_timeout) { - ERR("Session %u: reading a full NETCONF message timeout elapsed.", session->id); + nc_gettimespec(&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) { + ERR("Session %u: inactive read timeout elapsed.", session->id); + } else { + ERR("Session %u: active read timeout elapsed.", session->id); + } session->status = NC_STATUS_INVALID; session->term_reason = NC_SESSION_TERM_OTHER; return -1; } + } else { + /* something read */ + readd += r; + + /* reset inactive timeout */ + nc_gettimespec(&ts_inact_timeout); + nc_addtimespec(&ts_inact_timeout, inact_timeout); } - readd += r; } while (readd < count); buf[count] = '\0'; @@ -159,7 +164,7 @@ nc_read(struct nc_session *session, char *buf, size_t count, uint16_t *read_time } static ssize_t -nc_read_chunk(struct nc_session *session, size_t len, uint16_t *read_timeout, char **chunk) +nc_read_chunk(struct nc_session *session, size_t len, uint32_t inact_timeout, struct timespec *ts_act_timeout, char **chunk) { ssize_t r; @@ -176,7 +181,7 @@ nc_read_chunk(struct nc_session *session, size_t len, uint16_t *read_timeout, ch return -1; } - r = nc_read(session, *chunk, len, read_timeout); + r = nc_read(session, *chunk, len, inact_timeout, ts_act_timeout); if (r <= 0) { free(*chunk); return -1; @@ -189,7 +194,8 @@ nc_read_chunk(struct nc_session *session, size_t len, uint16_t *read_timeout, ch } static ssize_t -nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint16_t *read_timeout, char **result) +nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint32_t inact_timeout, + struct timespec *ts_act_timeout, char **result) { char *chunk = NULL; size_t size, count = 0, r, len; @@ -229,7 +235,7 @@ nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint } /* get another character */ - r = nc_read(session, &(chunk[count]), 1, read_timeout); + r = nc_read(session, &(chunk[count]), 1, inact_timeout, ts_act_timeout); if (r != 1) { free(chunk); return -1; @@ -264,7 +270,9 @@ nc_read_msg(struct nc_session *session, struct lyxml_elem **data) int ret; char *msg = NULL, *chunk; uint64_t chunk_len, len = 0; - uint16_t read_timeout = NC_READ_TIMEOUT; + /* use timeout in milliseconds instead seconds */ + uint32_t inact_timeout = NC_READ_INACT_TIMEOUT * 1000; + struct timespec ts_act_timeout; struct nc_server_reply *reply; assert(session && data); @@ -275,10 +283,13 @@ nc_read_msg(struct nc_session *session, struct lyxml_elem **data) return NC_MSG_ERROR; } + nc_gettimespec(&ts_act_timeout); + nc_addtimespec(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000); + /* read the message */ switch (session->version) { case NC_VERSION_10: - ret = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, &read_timeout, &msg); + ret = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, inact_timeout, &ts_act_timeout, &msg); if (ret == -1) { goto error; } @@ -288,11 +299,11 @@ nc_read_msg(struct nc_session *session, struct lyxml_elem **data) break; case NC_VERSION_11: while (1) { - ret = nc_read_until(session, "\n#", 0, &read_timeout, NULL); + ret = nc_read_until(session, "\n#", 0, inact_timeout, &ts_act_timeout, NULL); if (ret == -1) { goto error; } - ret = nc_read_until(session, "\n", 0, &read_timeout, &chunk); + ret = nc_read_until(session, "\n", 0, inact_timeout, &ts_act_timeout, &chunk); if (ret == -1) { goto error; } @@ -316,7 +327,7 @@ nc_read_msg(struct nc_session *session, struct lyxml_elem **data) } /* now we have size of next chunk, so read the chunk */ - ret = nc_read_chunk(session, chunk_len, &read_timeout, &chunk); + ret = nc_read_chunk(session, chunk_len, inact_timeout, &ts_act_timeout, &chunk); if (ret == -1) { goto error; } @@ -416,7 +427,7 @@ nc_read_poll(struct nc_session *session, int timeout) /* EINTR is handled, it resumes waiting */ ret = ssh_channel_poll_timeout(session->ti.libssh.channel, timeout, 0); if (ret == SSH_ERROR) { - ERR("Session %u: polling on the SSH channel failed (%s).", session->id, + ERR("Session %u: SSH channel poll error (%s).", session->id, ssh_get_error(session->ti.libssh.session)); session->status = NC_STATUS_INVALID; session->term_reason = NC_SESSION_TERM_OTHER; @@ -433,13 +444,19 @@ nc_read_poll(struct nc_session *session, int timeout) } else { /* ret == 0 */ fds.revents = 0; } - /* fallthrough */ + break; #endif #ifdef NC_ENABLED_TLS case NC_TI_OPENSSL: - if (session->ti_type == NC_TI_OPENSSL) { - fds.fd = SSL_get_fd(session->ti.tls); + ret = SSL_pending(session->ti.tls); + if (ret) { + /* some buffered TLS data available */ + ret = 1; + fds.revents = POLLIN; + break; } + + fds.fd = SSL_get_fd(session->ti.tls); /* fallthrough */ #endif case NC_TI_FD: @@ -447,16 +464,13 @@ nc_read_poll(struct nc_session *session, int timeout) fds.fd = session->ti.fd.in; } - /* poll only if it is not an SSH session */ - if (ret == -2) { - fds.events = POLLIN; - fds.revents = 0; + fds.events = POLLIN; + fds.revents = 0; - sigfillset(&sigmask); - pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); - ret = poll(&fds, 1, timeout); - pthread_sigmask(SIG_SETMASK, &origmask, NULL); - } + sigfillset(&sigmask); + pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); + ret = poll(&fds, 1, timeout); + pthread_sigmask(SIG_SETMASK, &origmask, NULL); break; @@ -547,6 +561,7 @@ 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)); @@ -826,7 +841,7 @@ nc_write_error_elem(struct wclb_arg *arg, const char *name, uint16_t nam_len, co static void nc_write_error(struct wclb_arg *arg, struct nc_server_error *err, const char *prefix) { - uint16_t i, pref_len; + uint16_t i, pref_len = 0; char str_sid[11]; if (prefix) { @@ -989,13 +1004,14 @@ nc_write_error(struct wclb_arg *arg, struct nc_server_error *err, const char *pr /* return -1 can change session status */ int -nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...) +nc_write_msg(struct nc_session *session, int type, ...) { va_list ap; int count; const char *attrs, *base_prefix; struct lyd_node *content; struct lyxml_elem *rpc_elem; + struct nc_server_notif *notif; struct nc_server_reply *reply; struct nc_server_reply_error *error_rpl; char *buf = NULL; @@ -1016,13 +1032,14 @@ nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...) arg.session = session; arg.len = 0; + switch (type) { case NC_MSG_RPC: content = va_arg(ap, struct lyd_node *); attrs = va_arg(ap, const char *); count = asprintf(&buf, "", - NC_NS_BASE, session->msgid + 1, attrs ? attrs : ""); + NC_NS_BASE, session->opts.client.msgid + 1, attrs ? attrs : ""); if (count == -1) { ERRMEM; va_end(ap); @@ -1034,7 +1051,7 @@ nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...) lyd_print_clb(nc_write_xmlclb, (void *)&arg, content, LYD_XML, LYP_WITHSIBLINGS | LYP_NETCONF); nc_write_clb((void *)&arg, "", 6, 0); - session->msgid++; + session->opts.client.msgid++; break; case NC_MSG_REPLY: @@ -1070,7 +1087,6 @@ nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...) nc_write_clb((void *)&arg, "ok/>", 4, 0); break; case NC_RPL_DATA: - assert(((struct nc_server_reply_data *)reply)->data->schema->nodetype == LYS_RPC); switch(((struct nc_server_reply_data *)reply)->wd) { case NC_WD_UNKNOWN: case NC_WD_EXPLICIT: @@ -1112,9 +1128,14 @@ nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...) break; case NC_MSG_NOTIF: - nc_write_clb((void *)&arg, "", 21 + 47 + 3, 0); - /* TODO content */ - nc_write_clb((void *)&arg, "", 12, 0); + notif = va_arg(ap, struct nc_server_notif *); + + nc_write_clb((void *)&arg, "", 21 + 47 + 2, 0); + nc_write_clb((void *)&arg, "", 11, 0); + nc_write_clb((void *)&arg, notif->eventtime, strlen(notif->eventtime), 0); + nc_write_clb((void *)&arg, "", 12, 0); + lyd_print_clb(nc_write_xmlclb, (void *)&arg, notif->tree, LYD_XML, 0); + nc_write_clb((void *)&arg, "", 15, 0); break; case NC_MSG_HELLO: diff --git a/src/libnetconf.h b/src/libnetconf.h index 2c759e59..2c38dc2f 100644 --- a/src/libnetconf.h +++ b/src/libnetconf.h @@ -46,7 +46,7 @@ * * @section about-license License * - * Copyright (c) 2015-2016 CESNET, z.s.p.o. + * Copyright (c) 2015-2017 CESNET, z.s.p.o. * * (The BSD 3-Clause License) * @@ -72,6 +72,7 @@ * - @subpage howtoserver * - @subpage howtoclientcomm * - @subpage howtoservercomm + * - @subpage howtotimeouts */ /** @@ -105,11 +106,11 @@ * documentation. _libnetconf2_ thread-safety information is below. * * Client is __NOT__ thread-safe and there is no access control in the client - * functions at all. Server is __MOSTLY__ thread-safe meaning you can set all the + * functions at all. Server is __FULLY__ thread-safe meaning you can set all the * options simultaneously while listening for or accepting new sessions or - * polling the existing ones. It should even be safe to poll one session in - * several threads, but it is definitely discouraged. Generally, servers can - * use more threads without any problems as long as they keep their workflow sane + * polling the existing ones. It is even safe to poll one session in several + * pollsession structures or one pollsession structure in several threads. Generally, + * servers can use more threads without any problems as long as they keep their workflow sane * (behavior such as freeing sessions only after no thread uses them or similar). * * Functions List @@ -294,7 +295,7 @@ * In it, you set the server context, which determines what modules it * supports and what capabilities to advertise. Few capabilities that * cannot be learnt from the context are set with separate functions - * nc_server_set_capab_withdefaults() and nc_server_set_capab_interleave(). + * 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(). @@ -310,9 +311,9 @@ * * Server options can be only set, there are no getters. * - * To be able to accept any connections, general endpoints must first be added - * with nc_server_add_endpt(). They can then be modified to accept either SSH, TLS, - * or both kinds of sessions. + * 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(). * * Functions List * -------------- @@ -320,39 +321,46 @@ * Available in __nc_server.h__. * * - nc_server_set_capab_withdefaults() - * - nc_server_set_capab_interleave() + * - nc_server_set_capability() * - nc_server_set_hello_timeout() * - nc_server_set_idle_timeout() * * - nc_server_add_endpt() * - nc_server_del_endpt() + * - nc_server_endpt_set_address() + * - nc_server_endpt_set_port() * * * SSH * === * - * To start listening for SSH connections you must set the address - * and port to listen on for a particular endpoint using nc_server_ssh_endpt_set_address() - * and nc_server_endpt_set_port(). - * To then successfully accept an SSH session you must also set the host key using - * nc_server_ssh_endpt_add_hostkey(). + * 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(). + * + * There are also some other optional settings. Note that authorized + * public keys are set for the server as a whole, not endpoint-specifically. * * Functions List * -------------- * * Available in __nc_server.h__. * - * - nc_server_ssh_endpt_set_address() - * - nc_server_ssh_endpt_set_port() - * * - 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_banner() * - nc_server_ssh_endpt_set_auth_methods() * - nc_server_ssh_endpt_set_auth_attempts() * - nc_server_ssh_endpt_set_auth_timeout() - * - nc_server_ssh_endpt_add_authkey() - * - nc_server_ssh_endpt_del_authkey() + * + * - nc_server_ssh_set_hostkey_clb() + * + * - nc_server_ssh_add_authkey() + * - nc_server_ssh_add_authkey_path() + * - nc_server_ssh_del_authkey() * * * TLS @@ -361,23 +369,25 @@ * 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 by calling nc_server_tls_endpt_set_address() - * and nc_server_tls_endpt_set_port(), - * you need to set the server certificate (nc_server_tls_endpt_set_cert() - * or nc_server_tls_endpt_set_cert_path()) and private key (nc_server_tls_endpt_set_key() - * or nc_server_tls_endpt_set_key_path()). + * 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(). * * 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 using nc_server_tls_endpt_add_trusted_cert(). Lastly, you can - * set paths with all the trusted CA certificates with nc_server_tls_endpt_set_trusted_ca_paths(). + * 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. * * 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(). * - * If you need to remove trusted certificates, you can do so with nc_server_tls_endpt_del_trusted_cert(). + * 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(). * * Functions List @@ -385,21 +395,18 @@ * * Available in __nc_server.h__. * - * - nc_server_tls_endpt_set_address() - * - nc_server_tls_endpt_set_port() - * - * - nc_server_tls_endpt_set_cert() - * - nc_server_tls_endpt_set_cert_path() - * - nc_server_tls_endpt_set_key() - * - nc_server_tls_endpt_set_key_path() - * - nc_server_tls_endpt_add_trusted_cert() - * - nc_server_tls_endpt_add_trusted_cert_path() + * - 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_del_trusted_cert(); * - 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_tls_set_server_cert_clb() + * - nc_server_tls_set_trusted_cert_list_clb() * * FD * == @@ -419,53 +426,62 @@ * Call Home * ========= * - * Call Home does not work with endpoints like standard sessions. - * Connecting is similar to the [client](@ref howtoclient), just call - * nc_connect_callhome_ssh() or nc_connect_callhome_tls(). Any options - * must be reset manually by nc_server_ssh_ch_clear_opts() - * or using nc_server_tls_ch_del_trusted_cert() and nc_server_tls_ch_clear_crls() - * after another Call Home session (with different options than the previous one) - * is to be established. Also, monitoring of these sessions is up to the application. + * _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. * * Functions List * -------------- * * Available in __nc_server.h__. * - * - nc_connect_callhome_ssh() - * - nc_connect_callhome_tls() - * - * - nc_server_ssh_ch_add_hostkey() - * - nc_server_ssh_ch_del_hostkey() - * - nc_server_ssh_ch_set_banner() - * - nc_server_ssh_ch_set_auth_methods() - * - nc_server_ssh_ch_set_auth_attempts() - * - nc_server_ssh_ch_set_auth_timeout() - * - nc_server_ssh_ch_add_authkey() - * - nc_server_ssh_ch_del_authkey() - * - nc_server_ssh_ch_clear_opts() - * - * - nc_server_tls_ch_set_cert() - * - nc_server_tls_ch_set_cert_path() - * - nc_server_tls_ch_set_key() - * - nc_server_tls_ch_set_key_path() - * - nc_server_tls_ch_add_trusted_cert() - * - nc_server_tls_ch_add_trusted_cert_path() - * - nc_server_tls_ch_set_trusted_ca_paths() - * - nc_server_tls_ch_del_trusted_cert(); - * - nc_server_tls_ch_set_crl_paths() - * - nc_server_tls_ch_clear_crls() - * - nc_server_tls_ch_add_ctn() - * - nc_server_tls_ch_del_ctn() - * - nc_server_tls_ch_clear_opts() + * - nc_server_ch_add_client() + * - nc_server_ch_del_client() + * - nc_server_ch_client_add_endpt() + * - nc_server_ch_client_del_endpt() + * - nc_server_ch_client_endpt_set_address() + * - nc_server_ch_client_endpt_set_port() + * - nc_server_ch_client_set_conn_type() + * - nc_server_ch_client_persist_set_idle_timeout() + * - nc_server_ch_client_persist_set_keep_alive_max_wait() + * - nc_server_ch_client_persist_set_keep_alive_max_attempts() + * - nc_server_ch_client_period_set_idle_timeout() + * - nc_server_ch_client_period_set_reconnect_timeout() + * - nc_server_ch_client_set_start_with() + * - nc_server_ch_client_set_max_attempts() + * - nc_connect_ch_client_dispatch() + * + * - nc_server_ssh_ch_client_add_hostkey() + * - nc_server_ssh_ch_client_del_hostkey() + * - nc_server_ssh_ch_client_mov_hostkey() + * - nc_server_ssh_ch_client_mod_hostkey() + * - nc_server_ssh_ch_client_set_banner() + * - nc_server_ssh_ch_client_set_auth_methods() + * - nc_server_ssh_ch_client_set_auth_attempts() + * - nc_server_ssh_ch_client_set_auth_timeout() + * + * - nc_server_tls_ch_client_set_server_cert() + * - nc_server_tls_ch_client_add_trusted_cert_list() + * - nc_server_tls_ch_client_del_trusted_cert_list() + * - nc_server_tls_ch_client_set_trusted_ca_paths() + * - nc_server_tls_ch_client_set_crl_paths() + * - nc_server_tls_ch_client_clear_crls() + * - nc_server_tls_ch_client_add_ctn() + * - nc_server_tls_ch_client_del_ctn() + * - nc_server_tls_ch_client_get_ctn() * * * Connecting And Cleanup * ====================== * * When accepting connections with nc_accept(), all the endpoints are examined - * and the first with a pending connection is used. To remove all - * the endpoints and free any used dynamic memory, [destroy](@ref howtoinit) the server. + * and the first with a pending connection is used. To remove all CH clients, + * endpoints, and free any used dynamic memory, [destroy](@ref howtoinit) the server. * * Functions List * -------------- @@ -544,4 +560,50 @@ * - nc_session_accept_ssh_channel() */ +/** + * @page howtotimeouts Timeouts + * + * 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 + * 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. + * + * Compile Options + * --------------- + * + * You can adjust active and inactive read timeout using `cmake` variables. + * For details look into `README.md`. + * + * API Functions + * ------------- + * + * Once a new connection is established including transport protocol negotiations, + * _hello_ message is exchanged. You can set how long will the server wait for + * receiving this message from a client before dropping it. + * + * Having a NETCONF session working, it may not communicate for a longer time. + * 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 + * 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_set_idle_timeout() + * - nc_server_ch_client_persist_set_idle_timeout() + * - nc_server_ch_client_period_set_idle_timeout() + * - nc_server_ch_client_period_set_reconnect_timeout() + * - nc_server_ssh_endpt_set_auth_timeout() + * - nc_server_ssh_ch_client_set_auth_timeout() + */ + #endif /* NC_LIBNETCONF_H_ */ diff --git a/src/log.h b/src/log.h index cc3c9a80..83bc2def 100644 --- a/src/log.h +++ b/src/log.h @@ -34,6 +34,8 @@ typedef enum NC_VERB_LEVEL { * * This level is set for libnetconf2 and alo libyang that is used internally. libyang * verbose level can be set explicitly, but must be done so after calling this function. + * However, if debug verbosity is used, selecting displayed libyang debug message groups + * must be done explicitly. * * @param[in] level Enabled verbosity level (includes all the levels with higher priority). */ diff --git a/src/messages_client.h b/src/messages_client.h index c3af824c..466ad935 100644 --- a/src/messages_client.h +++ b/src/messages_client.h @@ -153,8 +153,11 @@ struct nc_reply { * @brief NETCONF client data rpc-reply object */ struct nc_reply_data { - NC_RPL type; /**< NC_RPL_DATA */ - struct lyd_node *data; /**< libyang data tree */ + NC_RPL type; /**< NC_RPL_DATA */ + struct lyd_node *data; /**< libyang RPC reply data tree (output of an RPC), + \ and \ replies are special, + in those cases there is the configuration itself + and it should be validated as such (using \b LYD_OPT_GET or \b LYD_OPT_GETCONFIG). */ }; /** @@ -439,7 +442,7 @@ struct nc_rpc *nc_rpc_getschema(const char *identifier, const char *version, con * @return Created RPC object to send via a NETCONF session or NULL in case of (memory allocation) error. */ struct nc_rpc *nc_rpc_subscribe(const char *stream_name, const char *filter, const char *start_time, - const char *stop_time, NC_PARAMTYPE paramtype); + const char *stop_time, NC_PARAMTYPE paramtype); /** * @brief Free the NETCONF RPC object. diff --git a/src/messages_p.h b/src/messages_p.h index ca171f71..c5a0a6f9 100644 --- a/src/messages_p.h +++ b/src/messages_p.h @@ -68,6 +68,12 @@ struct nc_server_rpc { struct lyd_node *tree; /**< libyang data tree of the message (NETCONF operation) */ }; +struct nc_server_notif { + char *eventtime; /**< eventTime of the notification */ + struct lyd_node *tree; /**< libyang data tree of the message */ + int free; +}; + struct nc_client_reply_error { NC_RPL type; struct nc_err *err; diff --git a/src/messages_server.c b/src/messages_server.c index 2ea3d600..15ca95b2 100644 --- a/src/messages_server.c +++ b/src/messages_server.c @@ -19,8 +19,8 @@ #include -#include "session_server.h" #include "libnetconf.h" +#include "session_server.h" extern struct nc_server_opts server_opts; @@ -122,8 +122,25 @@ nc_server_reply_add_err(struct nc_server_reply *reply, struct nc_server_error *e return 0; } +API const struct nc_server_error * +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"); + return NULL; + } + + err_rpl = (struct nc_server_reply_error *)reply; + if (!err_rpl->count) { + return NULL; + } + return err_rpl->err[err_rpl->count - 1]; +} + API struct nc_server_error * -nc_err(NC_ERR tag, ...) +nc_err(int tag, ...) { va_list ap; struct nc_server_error *ret; @@ -151,7 +168,7 @@ nc_err(NC_ERR tag, ...) case NC_ERR_ACCESS_DENIED: case NC_ERR_ROLLBACK_FAILED: case NC_ERR_OP_NOT_SUPPORTED: - type = va_arg(ap, NC_ERR_TYPE); + 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"); goto fail; @@ -160,14 +177,14 @@ nc_err(NC_ERR tag, ...) case NC_ERR_TOO_BIG: case NC_ERR_RES_DENIED: - type = va_arg(ap, NC_ERR_TYPE); + type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */ /* nothing to check */ break; case NC_ERR_MISSING_ATTR: case NC_ERR_BAD_ATTR: case NC_ERR_UNKNOWN_ATTR: - type = va_arg(ap, NC_ERR_TYPE); + type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */ arg1 = va_arg(ap, const char *); arg2 = va_arg(ap, const char *); @@ -182,7 +199,7 @@ nc_err(NC_ERR tag, ...) case NC_ERR_MISSING_ELEM: case NC_ERR_BAD_ELEM: case NC_ERR_UNKNOWN_ELEM: - type = va_arg(ap, NC_ERR_TYPE); + type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */ arg1 = va_arg(ap, const char *); if ((type != NC_ERR_TYPE_PROT) && (type != NC_ERR_TYPE_APP)) { @@ -193,7 +210,7 @@ nc_err(NC_ERR tag, ...) break; case NC_ERR_UNKNOWN_NS: - type = va_arg(ap, NC_ERR_TYPE); + type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */ arg1 = va_arg(ap, const char *); arg2 = va_arg(ap, const char *); @@ -218,7 +235,7 @@ nc_err(NC_ERR tag, ...) break; case NC_ERR_OP_FAILED: - type = va_arg(ap, NC_ERR_TYPE); + type = (NC_ERR_TYPE)va_arg(ap, int); /* NC_ERR_TYPE enum is automatically promoted to int */ if (type == NC_ERR_TYPE_TRAN) { ERRARG("type"); @@ -468,6 +485,7 @@ nc_err_libyang(void) break; case LYVE_INATTR: case LYVE_MISSATTR: + case LYVE_INMETA: str = ly_errmsg(); stri = strchr(str, '"'); stri++; @@ -476,8 +494,10 @@ nc_err_libyang(void) attr = strndup(stri, strj - stri); if (ly_vecode == LYVE_INATTR) { e = nc_err(NC_ERR_UNKNOWN_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath()); - } else { + } else if (ly_vecode == LYVE_MISSATTR) { e = nc_err(NC_ERR_MISSING_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath()); + } else { /* LYVE_INMETA */ + e = nc_err(NC_ERR_BAD_ATTR, NC_ERR_TYPE_PROT, attr, ly_errpath()); } free(attr); break; @@ -502,7 +522,7 @@ nc_err_libyang(void) } API NC_ERR_TYPE -nc_err_get_type(struct nc_server_error *err) +nc_err_get_type(const struct nc_server_error *err) { if (!err) { ERRARG("err"); @@ -513,7 +533,7 @@ nc_err_get_type(struct nc_server_error *err) } API NC_ERR -nc_err_get_tag(struct nc_server_error *err) +nc_err_get_tag(const struct nc_server_error *err) { if (!err) { ERRARG("err"); @@ -543,7 +563,7 @@ nc_err_set_app_tag(struct nc_server_error *err, const char *error_app_tag) } API const char * -nc_err_get_app_tag(struct nc_server_error *err) +nc_err_get_app_tag(const struct nc_server_error *err) { if (!err) { ERRARG("err"); @@ -573,7 +593,7 @@ nc_err_set_path(struct nc_server_error *err, const char *error_path) } API const char * -nc_err_get_path(struct nc_server_error *err) +nc_err_get_path(const struct nc_server_error *err) { if (!err) { ERRARG("err"); @@ -612,7 +632,7 @@ nc_err_set_msg(struct nc_server_error *err, const char *error_message, const cha } API const char * -nc_err_get_msg(struct nc_server_error *err) +nc_err_get_msg(const struct nc_server_error *err) { if (!err) { ERRARG("err"); @@ -799,3 +819,54 @@ nc_err_free(struct nc_server_error *err) free(err->other); free(err); } + +API struct nc_server_notif * +nc_server_notif_new(struct lyd_node* event, char *eventtime, NC_PARAMTYPE paramtype) +{ + struct nc_server_notif *ntf; + + if (!event || event->schema->nodetype != LYS_NOTIF) { + ERRARG("event"); + return NULL; + } else if (!eventtime) { + ERRARG("eventtime"); + return NULL; + } + + ntf = malloc(sizeof *ntf); + if (paramtype == NC_PARAMTYPE_DUP_AND_FREE) { + ntf->eventtime = strdup(eventtime); + ntf->tree = lyd_dup(event, 1); + } else { + ntf->eventtime = eventtime; + ntf->tree = event; + } + ntf->free = (paramtype == NC_PARAMTYPE_CONST ? 0 : 1); + + return ntf; +} + +API void +nc_server_notif_free(struct nc_server_notif *notif) +{ + if (!notif) { + return; + } + + if (notif->free) { + lyd_free(notif->tree); + free(notif->eventtime); + } + free(notif); +} + +API const char * +nc_server_notif_get_time(const struct nc_server_notif *notif) +{ + if (!notif) { + ERRARG("notif"); + return NULL; + } + + return notif->eventtime; +} diff --git a/src/messages_server.h b/src/messages_server.h index 733fe5cd..46e1f09b 100644 --- a/src/messages_server.h +++ b/src/messages_server.h @@ -3,7 +3,7 @@ * \author Michal Vasko * \brief libnetconf2's functions and structures of server NETCONF messages. * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2015-2017 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. @@ -18,6 +18,7 @@ #include #include "netconf.h" +#include "session.h" /** * @brief Enumeration of NETCONF errors @@ -61,6 +62,11 @@ typedef enum NC_ERROR_TYPE { */ struct nc_server_reply; +/** + * @brief NETCONF server Event Notification object + */ +struct nc_server_notif; + /** * @brief NETCONF server error structure */ @@ -101,11 +107,19 @@ struct nc_server_reply *nc_server_reply_err(struct nc_server_error *err); */ int nc_server_reply_add_err(struct nc_server_reply *reply, struct nc_server_error *err); +/** + * @brief Get last error from an ERROR rpy-reply object. + * + * @param[in] reply ERROR reply to read from. + * @return Last error, NULL on failure. + */ +const struct nc_server_error *nc_server_reply_get_last_err(const struct nc_server_reply *reply); + /** * @brief Create a server error structure. Its \ is filled with * a general description of the specific error. * - * @param[in] tag \ of the server error. According to the tag, the + * @param[in] tag \ of the server error specified as #NC_ERR value. According to the tag, the * specific additional parameters are required: * - #NC_ERR_IN_USE * - #NC_ERR_INVALID_VALUE @@ -139,7 +153,7 @@ int nc_server_reply_add_err(struct nc_server_reply *reply, struct nc_server_erro * - no additional arguments * @return Server error structure, NULL on error. */ -struct nc_server_error *nc_err(NC_ERR tag, ...); +struct nc_server_error *nc_err(int tag, ...); /** * @brief Create a server error structure based on libyang error. @@ -157,7 +171,7 @@ struct nc_server_error *nc_err_libyang(void); * @param[in] err Server error to read from. * @return Server error type, 0 on error. */ -NC_ERR_TYPE nc_err_get_type(struct nc_server_error *err); +NC_ERR_TYPE nc_err_get_type(const struct nc_server_error *err); /** * @brief Get the \ of a server error. @@ -165,7 +179,7 @@ NC_ERR_TYPE nc_err_get_type(struct nc_server_error *err); * @param[in] err Server error to read from. * @return Server error tag, 0 on error. */ -NC_ERR nc_err_get_tag(struct nc_server_error *err); +NC_ERR nc_err_get_tag(const struct nc_server_error *err); /** * @brief Set the \ element of an error. Any previous value will be overwritten. @@ -182,7 +196,7 @@ int nc_err_set_app_tag(struct nc_server_error *err, const char *error_app_tag); * @param[in] err Server error to read from. * @return Server error app tag, NULL on error. */ -const char *nc_err_get_app_tag(struct nc_server_error *err); +const char *nc_err_get_app_tag(const struct nc_server_error *err); /** * @brief Set the \ element of an error. Any previous value will be overwritten. @@ -199,7 +213,7 @@ int nc_err_set_path(struct nc_server_error *err, const char *error_path); * @param[in] err Server error to read from. * @return Server error path, NULL on error. */ -const char *nc_err_get_path(struct nc_server_error *err); +const char *nc_err_get_path(const struct nc_server_error *err); /** * @brief Set the \ element of an error. Any previous value will be overwritten. @@ -217,7 +231,7 @@ int nc_err_set_msg(struct nc_server_error *err, const char *error_message, const * @param[in] err Server error to read from. * @return Server error message, NULL on error. */ -const char *nc_err_get_msg(struct nc_server_error *err); +const char *nc_err_get_msg(const struct nc_server_error *err); /** * @brief Set the \ element of an error. Any previous value will be overwritten. @@ -278,4 +292,46 @@ void nc_server_reply_free(struct nc_server_reply *reply); */ void nc_err_free(struct nc_server_error *err); +/** + * @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. + * @param[in] eventtime YANG dateTime format value of the time when the event was generated by the event source. + * Caller can use nc_time2datetime() to create the value from the time_t 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() + * and freed using nc_server_notif_free(). + */ +struct nc_server_notif *nc_server_notif_new(struct lyd_node* event, char *eventtime, NC_PARAMTYPE paramtype); + +/** + * @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] 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, + * #NC_MSG_WOULDBLOCK in case of a busy session, and + * #NC_MSG_ERROR on error. + */ +NC_MSG_TYPE nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout); + +/** + * @brief Free a server Event Notification object. + * + * @param[in] notif Server Event Notification object to free. + */ +void nc_server_notif_free(struct nc_server_notif *notif); + +/** + * @brief Get the notification timestamp. + * + * @param[in] notif Server notification to read from. + * @return Datetime timestamp of the notification, NULL on error. + */ +const char *nc_server_notif_get_time(const struct nc_server_notif *notif); + #endif /* NC_MESSAGES_SERVER_H_ */ diff --git a/src/netconf.h b/src/netconf.h index a2fb96ab..ade14cb2 100644 --- a/src/netconf.h +++ b/src/netconf.h @@ -27,17 +27,17 @@ extern "C" { /** @brief Default NETCONF over SSH port */ #define NC_PORT_SSH 830 /** @brief Default NETCONF over SSH Call Home port */ -#define NC_PORT_CH_SSH 6666 +#define NC_PORT_CH_SSH 4334 /** @brief Default NETCONF over TLS port */ #define NC_PORT_TLS 6513 /** @brief Default NETCONF over TLS Call Home port */ -#define NC_PORT_CH_TLS 6667 +#define NC_PORT_CH_TLS 4335 /** @brief Microseconds after which tasks are repeated until the full timeout elapses. - * A second (1000 000) should be divisible by this number without remain. + * A millisecond (1000) should be divisible by this number without remain. */ -#define NC_TIMEOUT_STEP 20 +#define NC_TIMEOUT_STEP 50 /** * @brief Set RPC callback to a schema node. diff --git a/src/session.c b/src/session.c index ba0de59a..ffc4fe68 100644 --- a/src/session.c +++ b/src/session.c @@ -3,7 +3,7 @@ * \author Michal Vasko * \brief libnetconf2 - general session functions * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2017 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,6 +12,7 @@ * https://opensource.org/licenses/BSD-3-Clause */ +#include #include #include #include @@ -65,34 +66,42 @@ nc_gettimespec(struct timespec *ts) #endif } -/* ts1 < ts2, returns milliseconds */ -uint32_t +/* ts1 < ts2 -> +, ts1 > ts2 -> -, returns milliseconds */ +int32_t nc_difftimespec(struct timespec *ts1, struct timespec *ts2) { - uint64_t nsec_diff = 0; + int64_t nsec_diff = 0; - if (ts1->tv_nsec > ts2->tv_nsec) { - ts2->tv_nsec += 1000000000L; - --ts2->tv_sec; - } + nsec_diff += (((int64_t)ts2->tv_sec) - ((int64_t)ts1->tv_sec)) * 1000000000L; + nsec_diff += ((int64_t)ts2->tv_nsec) - ((int64_t)ts1->tv_nsec); - if (ts1->tv_sec <= ts2->tv_sec) { - nsec_diff += (ts2->tv_sec - ts1->tv_sec) * 1000000000L; - } else { - ERRINT; - } + return (nsec_diff ? nsec_diff / 1000000L : 0); +} + +void +nc_addtimespec(struct timespec *ts, uint32_t msec) +{ + assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L)); + + ts->tv_sec += msec / 1000; + ts->tv_nsec += (msec % 1000) * 1000000L; - if (ts1->tv_nsec < ts2->tv_nsec) { - nsec_diff += ts2->tv_nsec - ts1->tv_nsec; + if (ts->tv_nsec >= 1000000000L) { + ++ts->tv_sec; + ts->tv_nsec -= 1000000000L; + } else if (ts->tv_nsec < 0) { + --ts->tv_sec; + ts->tv_nsec += 1000000000L; } - return (nsec_diff ? nsec_diff / 1000000L : 0); + assert((ts->tv_nsec >= 0) && (ts->tv_nsec < 1000000000L)); } #ifndef HAVE_PTHREAD_MUTEX_TIMEDLOCK int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime) { + int32_t diff; int rc; struct timespec cur, dur; @@ -100,18 +109,14 @@ pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime) while ((rc = pthread_mutex_trylock(mutex)) == EBUSY) { nc_gettimespec(&cur); - if ((cur.tv_sec > abstime->tv_sec) || ((cur.tv_sec == abstime->tv_sec) && (cur.tv_nsec >= abstime->tv_nsec))) { + if ((diff = nc_difftimespec(&cur, abstime)) < 1) { + /* timeout */ break; - } - - dur.tv_sec = abstime->tv_sec - cur.tv_sec; - dur.tv_nsec = abstime->tv_nsec - cur.tv_nsec; - if (dur.tv_nsec < 0) { - dur.tv_sec--; - dur.tv_nsec += 1000000000; - } - - if ((dur.tv_sec != 0) || (dur.tv_nsec > 5000000)) { + } else if (diff < 5) { + /* sleep until timeout */ + dur = *abstime; + } else { + /* sleep 5 ms */ dur.tv_sec = 0; dur.tv_nsec = 5000000; } @@ -123,44 +128,156 @@ pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *abstime) } #endif +struct nc_session * +nc_new_session(int not_allocate_ti) +{ + struct nc_session *sess; + + sess = calloc(1, sizeof *sess); + if (!sess) { + return NULL; + } + + if (!not_allocate_ti) { + sess->ti_lock = malloc(sizeof *sess->ti_lock); + sess->ti_cond = malloc(sizeof *sess->ti_cond); + sess->ti_inuse = malloc(sizeof *sess->ti_inuse); + if (!sess->ti_lock || !sess->ti_cond || !sess->ti_inuse) { + free(sess->ti_lock); + free(sess->ti_cond); + free((int *)sess->ti_inuse); + free(sess); + return NULL; + } + } + + return sess; +} + /* * @return 1 - success * 0 - timeout * -1 - error */ int -nc_timedlock(pthread_mutex_t *lock, int timeout, const char *func) +nc_session_lock(struct nc_session *session, int timeout, const char *func) { int ret; struct timespec ts_timeout; if (timeout > 0) { nc_gettimespec(&ts_timeout); - - ts_timeout.tv_sec += timeout / 1000; - ts_timeout.tv_nsec += (timeout % 1000) * 1000000; - - ret = pthread_mutex_timedlock(lock, &ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + + /* LOCK */ + ret = pthread_mutex_timedlock(session->ti_lock, &ts_timeout); + if (!ret) { + while (*session->ti_inuse) { + ret = pthread_cond_timedwait(session->ti_cond, session->ti_lock, &ts_timeout); + if (ret) { + pthread_mutex_unlock(session->ti_lock); + break; + } + } + } } else if (!timeout) { - ret = pthread_mutex_trylock(lock); - if (ret == EBUSY) { - /* equivalent in this case */ - ret = ETIMEDOUT; + if (*session->ti_inuse) { + /* immediate timeout */ + return 0; + } + + /* LOCK */ + ret = pthread_mutex_trylock(session->ti_lock); + if (!ret) { + /* be extra careful, someone could have been faster */ + if (*session->ti_inuse) { + pthread_mutex_unlock(session->ti_lock); + return 0; + } } } else { /* timeout == -1 */ - ret = pthread_mutex_lock(lock); + /* LOCK */ + ret = pthread_mutex_lock(session->ti_lock); + if (!ret) { + while (*session->ti_inuse) { + ret = pthread_cond_wait(session->ti_cond, session->ti_lock); + if (ret) { + pthread_mutex_unlock(session->ti_lock); + break; + } + } + } } - if (ret == ETIMEDOUT) { - /* timeout */ - return 0; - } else if (ret) { + if (ret) { + if ((ret == EBUSY) || (ret == ETIMEDOUT)) { + /* timeout */ + return 0; + } + /* error */ - ERR("Mutex lock failed (%s, %s).", func, strerror(ret)); + ERR("%s: failed to lock a session (%s).", func, strerror(ret)); return -1; } /* ok */ + assert(*session->ti_inuse == 0); + *session->ti_inuse = 1; + + /* UNLOCK */ + ret = pthread_mutex_unlock(session->ti_lock); + if (ret) { + /* error */ + ERR("%s: faile to unlock a session (%s).", func, strerror(ret)); + return -1; + } + + return 1; +} + +int +nc_session_unlock(struct nc_session *session, int timeout, const char *func) +{ + int ret; + struct timespec ts_timeout; + + assert(*session->ti_inuse); + + if (timeout > 0) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + + /* LOCK */ + ret = pthread_mutex_timedlock(session->ti_lock, &ts_timeout); + } else if (!timeout) { + /* LOCK */ + ret = pthread_mutex_trylock(session->ti_lock); + } else { /* timeout == -1 */ + /* LOCK */ + ret = pthread_mutex_lock(session->ti_lock); + } + + if (ret && (ret != EBUSY) && (ret != ETIMEDOUT)) { + /* error */ + ERR("%s: failed to lock a session (%s).", func, strerror(ret)); + return -1; + } else if (ret) { + WRN("%s: session lock timeout, should not happen."); + } + + *session->ti_inuse = 0; + pthread_cond_signal(session->ti_cond); + + if (!ret) { + /* UNLOCK */ + ret = pthread_mutex_unlock(session->ti_lock); + if (ret) { + /* error */ + ERR("%s: failed to unlock a session (%s).", func, strerror(ret)); + return -1; + } + } + return 1; } @@ -175,6 +292,17 @@ nc_session_get_status(const struct nc_session *session) return session->status; } +API NC_SESSION_TERM_REASON +nc_session_get_termreason(const struct nc_session *session) +{ + if (!session) { + ERRARG("session"); + return 0; + } + + return session->term_reason; +} + API uint32_t nc_session_get_id(const struct nc_session *session) { @@ -313,17 +441,17 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) } /* stop notifications loop if any */ - if (session->ntf_tid) { - tid = *session->ntf_tid; - free((pthread_t *)session->ntf_tid); - session->ntf_tid = NULL; + if ((session->side == NC_CLIENT) && session->opts.client.ntf_tid) { + tid = *session->opts.client.ntf_tid; + free((pthread_t *)session->opts.client.ntf_tid); + session->opts.client.ntf_tid = NULL; /* the thread now knows it should quit */ pthread_join(tid, NULL); } if (session->ti_lock) { - r = nc_timedlock(session->ti_lock, NC_READ_TIMEOUT * 1000, __func__); + r = nc_session_lock(session, NC_SESSION_FREE_LOCK_TIMEOUT, __func__); if (r == -1) { return; } else if (!r) { @@ -340,7 +468,7 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) if ((session->side == NC_CLIENT) && (session->status == NC_STATUS_RUNNING) && locked) { /* cleanup message queues */ /* notifications */ - for (contiter = session->notifs; contiter; ) { + for (contiter = session->opts.client.notifs; contiter; ) { lyxml_free(session->ctx, contiter->msg); p = contiter; @@ -349,7 +477,7 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) } /* rpc replies */ - for (contiter = session->replies; contiter; ) { + for (contiter = session->opts.client.replies; contiter; ) { lyxml_free(session->ctx, contiter->msg); p = contiter; @@ -390,11 +518,11 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) } /* list of server's capabilities */ - if (session->cpblts) { - for (i = 0; session->cpblts[i]; i++) { - lydict_remove(session->ctx, session->cpblts[i]); + if (session->opts.client.cpblts) { + for (i = 0; session->opts.client.cpblts[i]; i++) { + lydict_remove(session->ctx, session->opts.client.cpblts[i]); } - free(session->cpblts); + free(session->opts.client.cpblts); } } @@ -402,8 +530,21 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) data_free(session->data); } + if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { + /* CH LOCK */ + pthread_mutex_lock(session->opts.server.ch_lock); + } + /* mark session for closing */ session->status = NC_STATUS_CLOSING; + + if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { + pthread_cond_signal(session->opts.server.ch_cond); + + /* CH UNLOCK */ + pthread_mutex_unlock(session->opts.server.ch_lock); + } + connected = nc_session_is_connected(session); /* transport implementation cleanup */ @@ -490,7 +631,9 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) } SSL_free(session->ti.tls); - X509_free(session->tls_cert); + if (session->side == NC_SERVER) { + X509_free(session->opts.server.client_cert); + } break; #endif case NC_TI_NONE: @@ -504,11 +647,14 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) /* final cleanup */ if (session->ti_lock) { if (locked) { - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, NC_SESSION_LOCK_TIMEOUT, __func__); } if (!multisession) { pthread_mutex_destroy(session->ti_lock); + pthread_cond_destroy(session->ti_cond); free(session->ti_lock); + free(session->ti_cond); + free((int *)session->ti_inuse); } } @@ -516,12 +662,44 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) ly_ctx_destroy(session->ctx, NULL); } + if (session->side == NC_SERVER) { + if (session->opts.server.ch_cond) { + pthread_cond_destroy(session->opts.server.ch_cond); + free(session->opts.server.ch_cond); + } + if (session->opts.server.ch_lock) { + pthread_mutex_destroy(session->opts.server.ch_lock); + free(session->opts.server.ch_lock); + } + } + free(session); } static void add_cpblt(struct ly_ctx *ctx, const char *capab, const char ***cpblts, int *size, int *count) { + size_t len; + int i; + char *p; + + if (capab) { + /* check if already present */ + p = strchr(capab, '?'); + if (p) { + len = p - capab; + } else { + len = strlen(capab); + } + for (i = 0; i < *count; i++) { + if (!strncmp((*cpblts)[i], capab, len) && ((*cpblts)[i][len] == '\0' || (*cpblts)[i][len] == '?')) { + /* already present, do not duplicate it */ + return; + } + } + } + + /* add another capability */ if (*count == *size) { *size += 5; *cpblts = nc_realloc(*cpblts, *size * sizeof **cpblts); @@ -547,6 +725,7 @@ nc_server_get_cpblts(struct ly_ctx *ctx) const char **cpblts; const struct lys_module *mod; int size = 10, count, feat_count = 0, dev_count = 0, i, str_len; + unsigned int u; #define NC_CPBLT_BUF_LEN 512 char str[NC_CPBLT_BUF_LEN]; @@ -642,12 +821,9 @@ nc_server_get_cpblts(struct ly_ctx *ctx) } } - mod = ly_ctx_get_module(ctx, "nc-notifications", NULL); - if (mod) { - add_cpblt(ctx, "urn:ietf:params:netconf:capability:notification:1.0", &cpblts, &size, &count); - if (server_opts.interleave_capab) { - add_cpblt(ctx, "urn:ietf:params:netconf:capability:interleave:1.0", &cpblts, &size, &count); - } + /* other capabilities */ + for (u = 0; u < server_opts.capabilities_count; u++) { + add_cpblt(ctx, server_opts.capabilities[u], &cpblts, &size, &count); } /* models */ @@ -729,7 +905,7 @@ nc_server_get_cpblts(struct ly_ctx *ctx) } } if (!strcmp(name->value_str, "ietf-yang-library")) { - str_len += sprintf(str + str_len, "&module-set-id=%s", module_set_id->value_str); + sprintf(str + str_len, "&module-set-id=%s", module_set_id->value_str); } add_cpblt(ctx, str, &cpblts, &size, &count); @@ -916,7 +1092,7 @@ nc_recv_client_hello(struct nc_session *session) } flag = 1; - if ((ver = parse_cpblts(node, &session->cpblts)) < 0) { + if ((ver = parse_cpblts(node, &session->opts.client.cpblts)) < 0) { goto error; } session->version = ver; @@ -1053,6 +1229,8 @@ nc_ssh_destroy(void) #ifdef NC_ENABLED_TLS +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 + struct CRYPTO_dynlock_value { pthread_mutex_t lock; }; @@ -1090,11 +1268,13 @@ tls_dyn_destroy_func(struct CRYPTO_dynlock_value *l, const char *UNUSED(file), i pthread_mutex_destroy(&l->lock); free(l); } +#endif #endif /* NC_ENABLED_TLS */ #if defined(NC_ENABLED_TLS) && !defined(NC_ENABLED_SSH) +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 static pthread_mutex_t *tls_locks; static void @@ -1112,6 +1292,7 @@ tls_thread_id_func(CRYPTO_THREADID *tid) { CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self()); } +#endif static void nc_tls_init(void) @@ -1122,6 +1303,7 @@ nc_tls_init(void) ERR_load_BIO_strings(); SSL_library_init(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 tls_locks = malloc(CRYPTO_num_locks() * sizeof *tls_locks); if (!tls_locks) { ERRMEM; @@ -1137,6 +1319,7 @@ nc_tls_init(void) CRYPTO_set_dynlock_create_callback(tls_dyn_create_func); CRYPTO_set_dynlock_lock_callback(tls_dyn_lock_func); CRYPTO_set_dynlock_destroy_callback(tls_dyn_destroy_func); +#endif } static void @@ -1149,14 +1332,13 @@ nc_tls_destroy(void) nc_thread_destroy(); EVP_cleanup(); ERR_free_strings(); -#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 - // no de-init needed -#elif OPENSSL_VERSION_NUMBER >= 0x10002000L // >= 1.0.2 - SSL_COMP_free_compression_methods(); -#else +#if OPENSSL_VERSION_NUMBER < 0x10002000L // < 1.0.2 sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); +#elif OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 + SSL_COMP_free_compression_methods(); #endif +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 CRYPTO_THREADID_set_callback(NULL); CRYPTO_set_locking_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); ++i) { @@ -1167,6 +1349,7 @@ nc_tls_destroy(void) CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); +#endif } #endif /* NC_ENABLED_TLS && !NC_ENABLED_SSH */ @@ -1182,28 +1365,30 @@ nc_ssh_tls_init(void) nc_ssh_init(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 CRYPTO_set_dynlock_create_callback(tls_dyn_create_func); CRYPTO_set_dynlock_lock_callback(tls_dyn_lock_func); CRYPTO_set_dynlock_destroy_callback(tls_dyn_destroy_func); +#endif } static void nc_ssh_tls_destroy(void) { ERR_free_strings(); -#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 - // no de-init needed -#elif OPENSSL_VERSION_NUMBER >= 0x10002000L // >= 1.0.2 - SSL_COMP_free_compression_methods(); -#else +#if OPENSSL_VERSION_NUMBER < 0x10002000L // < 1.0.2 sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); +#elif OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 + SSL_COMP_free_compression_methods(); #endif nc_ssh_destroy(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); +#endif } #endif /* NC_ENABLED_SSH && NC_ENABLED_TLS */ @@ -1213,13 +1398,15 @@ nc_ssh_tls_destroy(void) API void nc_thread_destroy(void) { - CRYPTO_THREADID crypto_tid; - /* caused data-races and seems not neccessary for avoiding valgrind reachable memory */ //CRYPTO_cleanup_all_ex_data(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 + CRYPTO_THREADID crypto_tid; + CRYPTO_THREADID_current(&crypto_tid); ERR_remove_thread_state(&crypto_tid); +#endif } #endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ diff --git a/src/session.h b/src/session.h index 7482061e..d41cbd92 100644 --- a/src/session.h +++ b/src/session.h @@ -15,6 +15,8 @@ #ifndef NC_SESSION_H_ #define NC_SESSION_H_ +#include "netconf.h" + #ifdef NC_ENABLED_SSH /** @@ -70,6 +72,33 @@ typedef enum { #endif } NC_TRANSPORT_IMPL; +/** + * @brief Enumeration of Call Home connection types. + */ +typedef enum { + NC_CH_CT_NOT_SET = 0, + NC_CH_PERSIST, + NC_CH_PERIOD +} NC_CH_CONN_TYPE; + +/** + * @brief Enumeration of Call Home client priority policy. + */ +typedef enum { + NC_CH_FIRST_LISTED = 0, //default + NC_CH_LAST_CONNECTED +} 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 */ @@ -83,6 +112,14 @@ struct nc_session; */ NC_STATUS nc_session_get_status(const struct nc_session *session); +/** + * @brief Get session termination reason. + * + * @param[in] session Session to get the information from. + * @return Session termination reason enum value. + */ +NC_SESSION_TERM_REASON nc_session_get_termreason(const struct nc_session *session); + /** * @brief Get session ID. * diff --git a/src/session_client.c b/src/session_client.c index a44edf8e..2614bf98 100644 --- a/src/session_client.c +++ b/src/session_client.c @@ -105,6 +105,12 @@ ctx_check_and_load_model(struct nc_session *session, const char *module_cpblt) } free(model_name); + /* make it implemented */ + if (!module->implemented && lys_set_implemented(module)) { + WRN("Failed to implement model \"%s\".", module->name); + return 1; + } + /* parse features */ ptr = strstr(module_cpblt, "features="); if (ptr) { @@ -216,7 +222,7 @@ libyang_module_clb(const char *mod_name, const char *mod_rev, const char *submod } do { - msg = nc_recv_reply(session, rpc, msgid, NC_READ_TIMEOUT * 1000, 0, &reply); + msg = nc_recv_reply(session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, 0, &reply); } while (msg == NC_MSG_NOTIF); nc_rpc_free(rpc); if (msg == NC_MSG_WOULDBLOCK) { @@ -284,7 +290,6 @@ libyang_module_clb(const char *mod_name, const char *mod_rev, const char *submod return model_data; } -/* return 0 - ok, 1 - some models failed to load, -1 - error */ int nc_ctx_check_and_fill(struct nc_session *session) { @@ -293,11 +298,11 @@ nc_ctx_check_and_fill(struct nc_session *session) ly_module_imp_clb old_clb = NULL; void *old_data = NULL; - assert(session->cpblts && session->ctx); + assert(session->opts.client.cpblts && session->ctx); /* check if get-schema is supported */ - for (i = 0; session->cpblts[i]; ++i) { - if (!strncmp(session->cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) { + for (i = 0; session->opts.client.cpblts[i]; ++i) { + if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) { get_schema_support = 1; break; } @@ -315,7 +320,7 @@ nc_ctx_check_and_fill(struct nc_session *session) } /* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */ - if (ctx_check_and_load_ietf_netconf(session->ctx, session->cpblts)) { + if (ctx_check_and_load_ietf_netconf(session->ctx, session->opts.client.cpblts)) { if (old_clb) { ly_ctx_set_module_imp_clb(session->ctx, old_clb, old_data); } @@ -323,8 +328,8 @@ nc_ctx_check_and_fill(struct nc_session *session) } /* load all other models */ - for (i = 0; session->cpblts[i]; ++i) { - module_cpblt = strstr(session->cpblts[i], "module="); + for (i = 0; session->opts.client.cpblts[i]; ++i) { + module_cpblt = strstr(session->opts.client.cpblts[i], "module="); /* this capability requires a module */ if (module_cpblt) { r = ctx_check_and_load_model(session, module_cpblt); @@ -377,7 +382,7 @@ nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx) } /* prepare session structure */ - session = calloc(1, sizeof *session); + session = nc_new_session(0); if (!session) { ERRMEM; return NULL; @@ -387,6 +392,10 @@ nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx) /* transport specific data */ session->ti_type = NC_TI_FD; + pthread_mutex_init(session->ti_lock, NULL); + pthread_cond_init(session->ti_cond, NULL); + *session->ti_inuse = 0; + session->ti.fd.in = fdin; session->ti.fd.out = fdout; @@ -440,20 +449,18 @@ nc_sock_connect(const char* host, uint16_t port) return -1; } - for (i = 0, res = res_list; res != NULL; res = res->ai_next) { + for (res = res_list; res != NULL; res = res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock == -1) { /* socket was not created, try another resource */ - i = errno; - goto errloop; + continue; } if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { /* network connection failed, try another resource */ - i = errno; close(sock); sock = -1; - goto errloop; + continue; } /* make the socket non-blocking */ @@ -466,15 +473,9 @@ nc_sock_connect(const char* host, uint16_t port) /* we're done, network connection established */ break; -errloop: - VRB("Unable to connect to %s:%s over %s (%s).", host, port_s, - (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", strerror(i)); - continue; } - if (sock == -1) { - ERR("Unable to connect to %s:%s.", host, port_s); - } else { + if (sock != -1) { VRB("Successfully connected to %s:%s over %s.", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4"); } freeaddrinfo(res_list); @@ -493,7 +494,7 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el struct nc_msg_cont *cont, **cont_ptr; NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */ - r = nc_timedlock(session->ti_lock, timeout, __func__); + r = nc_session_lock(session, timeout, __func__); if (r == -1) { /* error */ return NC_MSG_ERROR; @@ -503,9 +504,9 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el } /* try to get notification from the session's queue */ - if (!msgid && session->notifs) { - cont = session->notifs; - session->notifs = cont->next; + if (!msgid && session->opts.client.notifs) { + cont = session->opts.client.notifs; + session->opts.client.notifs = cont->next; xml = cont->msg; free(cont); @@ -514,9 +515,9 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el } /* try to get rpc-reply from the session's queue */ - if (msgid && session->replies) { - cont = session->replies; - session->replies = cont->next; + if (msgid && session->opts.client.replies) { + cont = session->opts.client.replies; + session->opts.client.replies = cont->next; xml = cont->msg; free(cont); @@ -531,14 +532,14 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el /* we read rpc-reply, want a notif */ if (!msgid && (msgtype == NC_MSG_REPLY)) { - cont_ptr = &session->replies; + cont_ptr = &session->opts.client.replies; while (*cont_ptr) { cont_ptr = &((*cont_ptr)->next); } *cont_ptr = malloc(sizeof **cont_ptr); if (!*cont_ptr) { ERRMEM; - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); lyxml_free(session->ctx, xml); return NC_MSG_ERROR; } @@ -548,22 +549,21 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el /* we read notif, want a rpc-reply */ if (msgid && (msgtype == NC_MSG_NOTIF)) { - /* TODO check whether the session is even subscribed */ - /*if (!session->notif) { + if (!session->opts.client.ntf_tid) { pthread_mutex_unlock(session->ti_lock); ERR("Session %u: received a but session is not subscribed.", session->id); lyxml_free(session->ctx, xml); return NC_MSG_ERROR; - }*/ + } - cont_ptr = &session->notifs; + cont_ptr = &session->opts.client.notifs; while (*cont_ptr) { cont_ptr = &((*cont_ptr)->next); } *cont_ptr = malloc(sizeof **cont_ptr); if (!cont_ptr) { ERRMEM; - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); lyxml_free(session->ctx, xml); return NC_MSG_ERROR; } @@ -571,7 +571,7 @@ get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_el (*cont_ptr)->next = NULL; } - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); switch (msgtype) { case NC_MSG_NOTIF: @@ -864,8 +864,9 @@ parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_rpc *rpc, int } /* special treatment */ - data = lyd_parse_xml(ctx, &xml->child->child, LYD_OPT_DESTRUCT - | (rpc->type == NC_RPC_GETCONFIG ? LYD_OPT_GETCONFIG : LYD_OPT_GET) | parseroptions); + data = lyd_parse_xml(ctx, &xml->child->child, + LYD_OPT_DESTRUCT | (rpc->type == NC_RPC_GETCONFIG ? LYD_OPT_GETCONFIG : LYD_OPT_GET) + | parseroptions); if (!data) { ERR("Failed to parse <%s> reply.", (rpc->type == NC_RPC_GETCONFIG ? "get-config" : "get")); return NULL; @@ -952,6 +953,14 @@ nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IM return -1; } + client_opts.ch_bind_ti = nc_realloc(client_opts.ch_bind_ti, client_opts.ch_bind_count * sizeof *client_opts.ch_bind_ti); + if (!client_opts.ch_bind_ti) { + ERRMEM; + close(sock); + return -1; + } + client_opts.ch_bind_ti[client_opts.ch_bind_count - 1] = ti; + client_opts.ch_binds[client_opts.ch_bind_count - 1].address = strdup(address); if (!client_opts.ch_binds[client_opts.ch_bind_count - 1].address) { ERRMEM; @@ -960,7 +969,7 @@ nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IM } client_opts.ch_binds[client_opts.ch_bind_count - 1].port = port; client_opts.ch_binds[client_opts.ch_bind_count - 1].sock = sock; - client_opts.ch_binds[client_opts.ch_bind_count - 1].ti = ti; + client_opts.ch_binds[client_opts.ch_bind_count - 1].pollin = 0; return 0; } @@ -985,12 +994,18 @@ nc_client_ch_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti) for (i = 0; i < client_opts.ch_bind_count; ++i) { if ((!address || !strcmp(client_opts.ch_binds[i].address, address)) && (!port || (client_opts.ch_binds[i].port == port)) - && (!ti || (client_opts.ch_binds[i].ti == ti))) { + && (!ti || (client_opts.ch_bind_ti[i] == ti))) { close(client_opts.ch_binds[i].sock); free((char *)client_opts.ch_binds[i].address); --client_opts.ch_bind_count; - memcpy(&client_opts.ch_binds[i], &client_opts.ch_binds[client_opts.ch_bind_count], sizeof *client_opts.ch_binds); + if (!client_opts.ch_bind_count) { + free(client_opts.ch_binds); + client_opts.ch_binds = NULL; + } else if (i < client_opts.ch_bind_count) { + memcpy(&client_opts.ch_binds[i], &client_opts.ch_binds[client_opts.ch_bind_count], sizeof *client_opts.ch_binds); + client_opts.ch_bind_ti[i] = client_opts.ch_bind_ti[client_opts.ch_bind_count]; + } ret = 0; } @@ -1023,12 +1038,12 @@ nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session) } #ifdef NC_ENABLED_SSH - if (client_opts.ch_binds[idx].ti == NC_TI_LIBSSH) { + if (client_opts.ch_bind_ti[idx] == NC_TI_LIBSSH) { *session = nc_accept_callhome_ssh_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT); } else #endif #ifdef NC_ENABLED_TLS - if (client_opts.ch_binds[idx].ti == NC_TI_OPENSSL) { + if (client_opts.ch_bind_ti[idx] == NC_TI_OPENSSL) { *session = nc_accept_callhome_tls_sock(sock, host, port, ctx, NC_TRANSPORT_TIMEOUT); } else #endif @@ -1056,7 +1071,7 @@ nc_session_get_cpblts(const struct nc_session *session) return NULL; } - return session->cpblts; + return session->opts.client.cpblts; } API const char * @@ -1073,9 +1088,9 @@ nc_session_cpblt(const struct nc_session *session, const char *capab) } len = strlen(capab); - for (i = 0; session->cpblts[i]; ++i) { - if (!strncmp(session->cpblts[i], capab, len)) { - return session->cpblts[i]; + for (i = 0; session->opts.client.cpblts[i]; ++i) { + if (!strncmp(session->opts.client.cpblts[i], capab, len)) { + return session->opts.client.cpblts[i]; } } @@ -1085,12 +1100,12 @@ nc_session_cpblt(const struct nc_session *session, const char *capab) API int nc_session_ntf_thread_running(const struct nc_session *session) { - if (!session) { + if (!session || (session->side != NC_CLIENT)) { ERRARG("session"); return 0; } - return session->ntf_tid ? 1 : 0; + return session->opts.client.ntf_tid ? 1 : 0; } API void @@ -1141,6 +1156,8 @@ nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, in if (!(session->flags & NC_SESSION_CLIENT_NOT_STRICT)) { parseroptions &= LYD_OPT_STRICT; } + /* no mechanism to check external dependencies is provided */ + parseroptions|= LYD_OPT_NOEXTDEPS; *reply = NULL; msgtype = get_msg(session, timeout, msgid, &xml); @@ -1198,7 +1215,7 @@ nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif) } /* notification body */ - (*notif)->tree = lyd_parse_xml(session->ctx, &xml->child, LYD_OPT_NOTIF | LYD_OPT_DESTRUCT + (*notif)->tree = lyd_parse_xml(session->ctx, &xml->child, LYD_OPT_NOTIF | LYD_OPT_DESTRUCT | LYD_OPT_NOEXTDEPS | (session->flags & NC_SESSION_CLIENT_NOT_STRICT ? 0 : LYD_OPT_STRICT), NULL); lyxml_free(session->ctx, xml); xml = NULL; @@ -1234,7 +1251,7 @@ nc_recv_notif_thread(void *arg) notif_clb = ntarg->notif_clb; free(ntarg); - while (session->ntf_tid) { + while (session->opts.client.ntf_tid) { msgtype = nc_recv_notif(session, NC_CLIENT_NOTIF_THREAD_SLEEP / 1000, ¬if); if (msgtype == NC_MSG_NOTIF) { notif_clb(session, notif); @@ -1252,7 +1269,7 @@ nc_recv_notif_thread(void *arg) } VRB("Session %u: notification thread exit.", session->id); - session->ntf_tid = NULL; + session->opts.client.ntf_tid = NULL; return NULL; } @@ -1271,7 +1288,7 @@ nc_recv_notif_dispatch(struct nc_session *session, void (*notif_clb)(struct nc_s } else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) { ERR("Session %u: invalid session to receive Notifications.", session->id); return -1; - } else if (session->ntf_tid) { + } else if (session->opts.client.ntf_tid) { ERR("Session %u: separate notification thread is already running.", session->id); return -1; } @@ -1285,19 +1302,19 @@ nc_recv_notif_dispatch(struct nc_session *session, void (*notif_clb)(struct nc_s ntarg->notif_clb = notif_clb; /* just so that nc_recv_notif_thread() does not immediately exit, the value does not matter */ - session->ntf_tid = malloc(sizeof *session->ntf_tid); - if (!session->ntf_tid) { + session->opts.client.ntf_tid = malloc(sizeof *session->opts.client.ntf_tid); + if (!session->opts.client.ntf_tid) { ERRMEM; free(ntarg); return -1; } - ret = pthread_create((pthread_t *)session->ntf_tid, NULL, nc_recv_notif_thread, ntarg); + ret = pthread_create((pthread_t *)session->opts.client.ntf_tid, NULL, nc_recv_notif_thread, ntarg); if (ret) { ERR("Session %u: failed to create a new thread (%s).", strerror(errno)); free(ntarg); - free((pthread_t *)session->ntf_tid); - session->ntf_tid = NULL; + free((pthread_t *)session->opts.client.ntf_tid); + session->opts.client.ntf_tid = NULL; return -1; } @@ -1344,7 +1361,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ if ((rpc->type != NC_RPC_GETSCHEMA) && (rpc->type != NC_RPC_ACT_GENERIC) && (rpc->type != NC_RPC_SUBSCRIBE)) { ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL); if (!ietfnc) { - ERR("Session %u: missing ietf-netconf schema in the context.", session->id); + ERR("Session %u: missing \"ietf-netconf\" schema in the context.", session->id); return NC_MSG_ERROR; } } @@ -1356,7 +1373,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ if (rpc_gen->has_data) { data = rpc_gen->content.data; } else { - data = lyd_parse_mem(session->ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_RPC + data = lyd_parse_mem(session->ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_RPC | LYD_OPT_NOEXTDEPS | (session->flags & NC_SESSION_CLIENT_NOT_STRICT ? 0 : LYD_OPT_STRICT), NULL); } break; @@ -1390,7 +1407,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ if (!ietfncwd) { ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL); if (!ietfncwd) { - ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id); + ERR("Session %u: missing \"ietf-netconf-with-defaults\" schema in the context.", session->id); return NC_MSG_ERROR; } } @@ -1498,7 +1515,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ if (!ietfncwd) { ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL); if (!ietfncwd) { - ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id); + ERR("Session %u: missing \"ietf-netconf-with-defaults\" schema in the context.", session->id); return NC_MSG_ERROR; } } @@ -1589,7 +1606,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ if (!ietfncwd) { ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL); if (!ietfncwd) { - ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id); + ERR("Session %u: missing \"ietf-netconf-with-defaults\" schema in the context.", session->id); return NC_MSG_ERROR; } } @@ -1695,7 +1712,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ case NC_RPC_GETSCHEMA: ietfncmon = ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL); if (!ietfncmon) { - ERR("Session %u: missing ietf-netconf-monitoring schema in the context.", session->id); + ERR("Session %u: missing \"ietf-netconf-monitoring\" schema in the context.", session->id); return NC_MSG_ERROR; } @@ -1726,7 +1743,7 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ case NC_RPC_SUBSCRIBE: notifs = ly_ctx_get_module(session->ctx, "notifications", NULL); if (!notifs) { - ERR("Session %u: missing notifications schema in the context.", session->id); + ERR("Session %u: missing \"notifications\" schema in the context.", session->id); return NC_MSG_ERROR; } @@ -1777,12 +1794,13 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ return NC_MSG_ERROR; } - if (lyd_validate(&data, LYD_OPT_RPC | (session->flags & NC_SESSION_CLIENT_NOT_STRICT ? 0 : LYD_OPT_STRICT), NULL)) { + if (lyd_validate(&data, LYD_OPT_RPC | LYD_OPT_NOEXTDEPS + | (session->flags & NC_SESSION_CLIENT_NOT_STRICT ? 0 : LYD_OPT_STRICT), NULL)) { lyd_free(data); return NC_MSG_ERROR; } - ret = nc_timedlock(session->ti_lock, timeout, __func__); + ret = nc_session_lock(session, timeout, __func__); if (ret == -1) { /* error */ r = NC_MSG_ERROR; @@ -1792,9 +1810,9 @@ nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_ } else { /* send RPC, store its message ID */ r = nc_send_msg(session, data); - cur_msgid = session->msgid; + cur_msgid = session->opts.client.msgid; } - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); lyd_free(data); diff --git a/src/session_client.h b/src/session_client.h index ffab6e34..3e4e1278 100644 --- a/src/session_client.h +++ b/src/session_client.h @@ -369,9 +369,11 @@ int nc_session_ntf_thread_running(const struct nc_session *session); /** * @brief Receive NETCONF RPC reply. * - * If a reply to \ or \ RPCs is received, the data are the whole configuration - * parsed (usually results in more top-level nodes), not just a single 'data' anyxml node with - * the configuration unparsed inside (which would strictly be according to the model). + * Be careful, normally there is a whole RPC reply (output) of an RPC in the \p reply. + * However, if a reply to \ or \ RPC is received, the \p reply is + * actually the configuration (with either state data or not). This means, for example, + * that the reply data in these cases should not be validated with \b LYD_OPT_RPCREPLY, + * but \b LYD_OPT_GET and \b LYD_OPT_GETCONFIG, respectively. * * @param[in] session NETCONF session from which the function gets data. It must be the * client side session object. @@ -380,7 +382,7 @@ int nc_session_ntf_thread_running(const struct nc_session *session); * @param[in] timeout Timeout for reading in milliseconds. Use negative value for infinite * waiting and 0 for immediate return if data are not available on the wire. * @param[in] parseroptions libyang parseroptions flags, do not set the data type, it is set - * internally. LYD_OPT_DESTRUCT and LYD_OPT_NOSIBLINGS is ignored. + * internally. \b LYD_OPT_DESTRUCT and \b LYD_OPT_NOSIBLINGS is ignored. * @param[out] reply Resulting object of NETCONF RPC reply. * @return #NC_MSG_REPLY for success, * #NC_MSG_WOULDBLOCK if \p timeout has elapsed, diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c index bd555188..a68c39ee 100644 --- a/src/session_client_ssh.c +++ b/src/session_client_ssh.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #ifdef ENABLE_DNSSEC # include @@ -945,20 +947,23 @@ nc_client_ssh_ch_del_bind(const char *address, uint16_t port) static int connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, int timeout) { - int j, ret_auth, userauthlist, ret, elapsed_usec = 0; + int j, ret_auth, userauthlist, ret; NC_SSH_AUTH_TYPE auth; int16_t pref; const char* prompt; char *s, *answer, echo; ssh_key pubkey, privkey; ssh_session ssh_sess; + struct timespec ts_timeout, ts_cur; ssh_sess = session->ti.libssh.session; + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, NC_TRANSPORT_TIMEOUT); while ((ret = ssh_connect(ssh_sess)) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if (elapsed_usec / 1000 >= NC_TRANSPORT_TIMEOUT) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { break; } } @@ -976,12 +981,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, return -1; } - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret_auth = ssh_userauth_none(ssh_sess, NULL)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } if (ret_auth == SSH_AUTH_AGAIN) { @@ -1037,12 +1047,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, VRB("Password authentication (host \"%s\", user \"%s\").", session->host, session->username); s = opts->auth_password(session->username, session->host); - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret_auth = ssh_userauth_password(ssh_sess, session->username, s)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } memset(s, 0, strlen(s)); @@ -1054,14 +1069,19 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, VRB("Keyboard-interactive authentication."); - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while (((ret_auth = ssh_userauth_kbdint(ssh_sess, NULL, NULL)) == SSH_AUTH_INFO) || (ret_auth == SSH_AUTH_AGAIN)) { if (ret_auth == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } continue; } @@ -1089,7 +1109,10 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, if (ret_auth == SSH_AUTH_ERROR) { break; } - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } } break; @@ -1118,13 +1141,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, continue; } - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret_auth = ssh_userauth_try_publickey(ssh_sess, NULL, pubkey)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - ssh_key_free(pubkey); - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } ssh_key_free(pubkey); @@ -1154,13 +1181,17 @@ connect_ssh_session(struct nc_session *session, struct nc_client_ssh_opts *opts, continue; } - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret_auth = ssh_userauth_publickey(ssh_sess, NULL, privkey)) == SSH_AUTH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - ssh_key_free(privkey); - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } ssh_key_free(privkey); @@ -1204,7 +1235,8 @@ static int open_netconf_channel(struct nc_session *session, int timeout) { ssh_session ssh_sess; - int ret, elapsed_usec = 0; + int ret; + struct timespec ts_timeout, ts_cur; ssh_sess = session->ti.libssh.session; @@ -1219,12 +1251,18 @@ open_netconf_channel(struct nc_session *session, int timeout) } /* open a channel */ + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } session->ti.libssh.channel = ssh_channel_new(ssh_sess); while ((ret = ssh_channel_open_session(session->ti.libssh.channel)) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } if (ret == SSH_AGAIN) { @@ -1240,12 +1278,17 @@ open_netconf_channel(struct nc_session *session, int timeout) } /* execute the NETCONF subsystem on the channel */ - elapsed_usec = 0; + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret = ssh_channel_request_subsystem(session->ti.libssh.channel, "netconf")) == SSH_AGAIN) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } if (ret == SSH_AGAIN) { @@ -1278,7 +1321,7 @@ _nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_client } /* prepare session structure */ - session = calloc(1, sizeof *session); + session = nc_new_session(0); if (!session) { ERRMEM; return NULL; @@ -1287,12 +1330,9 @@ _nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_client session->side = NC_CLIENT; /* transport lock */ - session->ti_lock = malloc(sizeof *session->ti_lock); - if (!session->ti_lock) { - ERRMEM; - goto fail; - } pthread_mutex_init(session->ti_lock, NULL); + pthread_cond_init(session->ti_cond, NULL); + *session->ti_inuse = 0; session->ti_type = NC_TI_LIBSSH; session->ti.libssh.session = ssh_session; @@ -1317,6 +1357,7 @@ _nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx, struct nc_client /* create and connect socket */ sock = nc_sock_connect(host, port); if (sock == -1) { + ERR("Unable to connect to %s:%u (%s).", host, port, strerror(errno)); goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock); @@ -1447,7 +1488,7 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) } /* prepare session structure */ - session = calloc(1, sizeof *session); + session = nc_new_session(0); if (!session) { ERRMEM; return NULL; @@ -1456,12 +1497,9 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) session->side = NC_CLIENT; /* transport lock */ - session->ti_lock = malloc(sizeof *session->ti_lock); - if (!session->ti_lock) { - ERRMEM; - goto fail; - } pthread_mutex_init(session->ti_lock, NULL); + pthread_cond_init(session->ti_cond, NULL); + *session->ti_inuse = 0; /* other transport-specific data */ session->ti_type = NC_TI_LIBSSH; @@ -1486,6 +1524,7 @@ nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx) /* create and assign communication socket */ sock = nc_sock_connect(host, port); if (sock == -1) { + ERR("Unable to connect to %s:%u (%s).", host, port, strerror(errno)); goto fail; } ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_FD, &sock); @@ -1555,7 +1594,7 @@ nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx) } /* prepare session structure */ - new_session = calloc(1, sizeof *new_session); + new_session = nc_new_session(1); if (!new_session) { ERRMEM; return NULL; @@ -1566,10 +1605,14 @@ nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx) /* share some parameters including the session lock */ new_session->ti_type = NC_TI_LIBSSH; new_session->ti_lock = session->ti_lock; + new_session->ti_cond = session->ti_cond; + new_session->ti_inuse = session->ti_inuse; new_session->ti.libssh.session = session->ti.libssh.session; /* create the channel safely */ - pthread_mutex_lock(new_session->ti_lock); + if (nc_session_lock(new_session, -1, __func__)) { + goto fail; + } /* open a channel */ if (open_netconf_channel(new_session, NC_TRANSPORT_TIMEOUT) != 1) { @@ -1594,7 +1637,7 @@ nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx) } new_session->status = NC_STATUS_RUNNING; - pthread_mutex_unlock(new_session->ti_lock); + nc_session_unlock(new_session, NC_SESSION_LOCK_TIMEOUT, __func__); if (nc_ctx_check_and_fill(new_session) == -1) { goto fail; @@ -1626,6 +1669,7 @@ struct nc_session * nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly_ctx *ctx, int timeout) { const long ssh_timeout = NC_SSH_TIMEOUT; + unsigned int uint_port; struct passwd *pw; struct nc_session *session; ssh_session sess; @@ -1640,7 +1684,8 @@ nc_accept_callhome_ssh_sock(int sock, const char *host, uint16_t port, struct ly ssh_options_set(sess, SSH_OPTIONS_FD, &sock); ssh_set_blocking(sess, 0); ssh_options_set(sess, SSH_OPTIONS_HOST, host); - ssh_options_set(sess, SSH_OPTIONS_PORT, &port); + uint_port = port; + ssh_options_set(sess, SSH_OPTIONS_PORT, &uint_port); ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout); if (!ssh_ch_opts.username) { pw = getpwuid(getuid()); diff --git a/src/session_client_tls.c b/src/session_client_tls.c index 649614b1..f6435b1d 100644 --- a/src/session_client_tls.c +++ b/src/session_client_tls.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -34,6 +35,104 @@ static struct nc_client_tls_opts 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_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_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("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) { @@ -126,6 +225,8 @@ tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) return 1; /* success */ } +#endif + static void _nc_client_tls_destroy_opts(struct nc_client_tls_opts *opts) { @@ -392,8 +493,14 @@ nc_client_tls_update_opts(struct nc_client_tls_opts *opts) if (!opts->tls_ctx || opts->tls_ctx_change) { SSL_CTX_free(opts->tls_ctx); +#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()))) { + if (!(opts->tls_ctx = SSL_CTX_new(TLSv1_2_client_method()))) +#endif + { ERR("Unable to create OpenSSL context (%s).", ERR_reason_error_string(ERR_get_error())); return -1; } @@ -431,7 +538,11 @@ nc_client_tls_update_opts(struct nc_client_tls_opts *opts) ERR("Unable to create a certificate store (%s).", ERR_reason_error_string(ERR_get_error())); return -1; } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 + /* whaveter this does... */ opts->crl_store->cache = 0; +#endif if (opts->crl_file) { if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_file()))) { @@ -464,7 +575,7 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) { struct nc_session *session = NULL; int sock, verify, ret; - uint32_t elapsed_usec = 0; + struct timespec ts_timeout, ts_cur; if (!tls_opts.cert_path || (!tls_opts.ca_file && !tls_opts.ca_dir)) { ERRINIT; @@ -486,7 +597,7 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) } /* prepare session structure */ - session = calloc(1, sizeof *session); + session = nc_new_session(0); if (!session) { ERRMEM; return NULL; @@ -495,12 +606,9 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) session->side = NC_CLIENT; /* transport lock */ - session->ti_lock = malloc(sizeof *session->ti_lock); - if (!session->ti_lock) { - ERRMEM; - goto fail; - } pthread_mutex_init(session->ti_lock, NULL); + pthread_cond_init(session->ti_cond, NULL); + *session->ti_inuse = 0; /* fill the session */ session->ti_type = NC_TI_OPENSSL; @@ -512,6 +620,7 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) /* create and assign socket */ sock = nc_sock_connect(host, port); if (sock == -1) { + ERR("Unable to connect to %s:%u (%s).", host, port, strerror(errno)); goto fail; } SSL_set_fd(session->ti.tls, sock); @@ -520,11 +629,13 @@ nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx) SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY); /* connect and perform the handshake */ + nc_gettimespec(&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); - elapsed_usec += NC_TIMEOUT_STEP; - if (elapsed_usec / 1000 >= NC_TRANSPORT_TIMEOUT) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { ERR("SSL_connect timeout."); goto fail; } @@ -607,7 +718,7 @@ nc_connect_libssl(SSL *tls, struct ly_ctx *ctx) } /* prepare session structure */ - session = calloc(1, sizeof *session); + session = nc_new_session(0); if (!session) { ERRMEM; return NULL; @@ -616,12 +727,9 @@ nc_connect_libssl(SSL *tls, struct ly_ctx *ctx) session->side = NC_CLIENT; /* transport lock */ - session->ti_lock = malloc(sizeof *session->ti_lock); - if (!session->ti_lock) { - ERRMEM; - goto fail; - } pthread_mutex_init(session->ti_lock, NULL); + pthread_cond_init(session->ti_cond, NULL); + *session->ti_inuse = 0; session->ti_type = NC_TI_OPENSSL; session->ti.tls = tls; @@ -663,9 +771,10 @@ nc_connect_libssl(SSL *tls, struct ly_ctx *ctx) 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, elapsed_usec = 0; + int verify, ret; SSL *tls; struct nc_session *session; + struct timespec ts_timeout, ts_cur; if (nc_client_tls_update_opts(&tls_ch_opts)) { close(sock); @@ -684,14 +793,20 @@ nc_accept_callhome_tls_sock(int sock, const char *host, uint16_t port, struct ly SSL_set_mode(tls, SSL_MODE_AUTO_RETRY); /* connect and perform the handshake */ + if (timeout > -1) { + nc_gettimespec(&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); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - ERR("SSL_connect timeout."); - SSL_free(tls); - return NULL; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + ERR("SSL_connect timeout."); + SSL_free(tls); + return NULL; + } } } if (ret != 1) { diff --git a/src/session_p.h b/src/session_p.h index 4f3e790b..053d0d92 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -4,7 +4,7 @@ * \author Michal Vasko * \brief libnetconf2 session manipulation * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2017 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. @@ -69,12 +69,6 @@ struct nc_server_ssh_opts { uint8_t hostkey_count; const char *banner; - struct { - const char *path; - const char *username; - } *authkeys; - uint16_t authkey_count; - int auth_methods; uint16_t auth_attempts; uint16_t auth_timeout; @@ -104,13 +98,9 @@ struct nc_client_tls_opts { /* ACCESS locked, separate locks */ struct nc_server_tls_opts { - EVP_PKEY *server_key; - X509 *server_cert; - struct nc_cert { - const char *name; - X509 *cert; - } *trusted_certs; - uint16_t trusted_cert_count; + const char *server_cert; + const char **trusted_cert_lists; + uint16_t trusted_cert_list_count; const char *trusted_ca_file; const char *trusted_ca_dir; X509_STORE *crl_store; @@ -134,8 +124,9 @@ struct nc_client_opts { const char *address; uint16_t port; int sock; - NC_TRANSPORT_IMPL ti; + int pollin; } *ch_binds; + NC_TRANSPORT_IMPL *ch_bind_ti; uint16_t ch_bind_count; }; @@ -146,27 +137,100 @@ struct nc_server_opts { /* ACCESS unlocked */ NC_WD_MODE wd_basic_mode; int wd_also_supported; - int interleave_capab; + unsigned int capabilities_count; + const char **capabilities; /* ACCESS unlocked */ uint16_t hello_timeout; uint16_t idle_timeout; +#ifdef NC_ENABLED_TLS + int (*user_verify_clb)(const struct nc_session *session); - /* ACCESS locked, add/remove binds/endpts - WRITE lock endpt_array_lock - * modify binds/endpts - READ lock endpt_array_lock + endpt_lock */ + int (*server_cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data,char **privkey_path, + char **privkey_data, int *privkey_data_rsa); + void *server_cert_data; + void (*server_cert_data_free)(void *data); + + int (*trusted_cert_list_clb)(const char *name, void *user_data, char ***cert_paths, int *cert_path_count, + char ***cert_data, int *cert_data_count); + void *trusted_cert_list_data; + void (*trusted_cert_list_data_free)(void *data); +#endif + +#ifdef NC_ENABLED_SSH + /* ACCESS locked with authkey_lock */ + struct { + const char *path; + const char *base64; + NC_SSH_KEY_TYPE type; + const char *username; + } *authkeys; + uint16_t authkey_count; + pthread_mutex_t authkey_lock; + + int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, char **privkey_data, int *privkey_data_rsa); + void *hostkey_data; + void (*hostkey_data_free)(void *data); +#endif + + /* ACCESS locked, add/remove endpts/binds - bind_lock + WRITE endpt_lock (strict order!) + * modify endpts - WRITE endpt_lock + * access endpts - READ endpt_lock + * modify/poll binds - bind_lock */ struct nc_bind *binds; + pthread_mutex_t bind_lock; struct nc_endpt { const char *name; + NC_TRANSPORT_IMPL ti; + union { #ifdef NC_ENABLED_SSH - struct nc_server_ssh_opts *ssh_opts; + struct nc_server_ssh_opts *ssh; #endif #ifdef NC_ENABLED_TLS - struct nc_server_tls_opts *tls_opts; + struct nc_server_tls_opts *tls; #endif - pthread_mutex_t endpt_lock; + } opts; } *endpts; uint16_t endpt_count; - pthread_rwlock_t endpt_array_lock; + pthread_rwlock_t endpt_lock; + + /* ACCESS locked, add/remove CH clients - WRITE lock ch_client_lock + * modify CH clients - READ lock ch_client_lock + ch_client_lock */ + struct nc_ch_client { + const char *name; + NC_TRANSPORT_IMPL ti; + struct nc_ch_endpt { + const char *name; + const char *address; + uint16_t port; + } *ch_endpts; + uint16_t ch_endpt_count; + union { +#ifdef NC_ENABLED_SSH + struct nc_server_ssh_opts *ssh; +#endif +#ifdef NC_ENABLED_TLS + struct nc_server_tls_opts *tls; +#endif + } opts; + NC_CH_CONN_TYPE conn_type; + union { + struct { + uint32_t idle_timeout; + uint16_t ka_max_wait; + uint8_t ka_max_attempts; + } persist; + struct { + uint16_t idle_timeout; + uint16_t reconnect_timeout; + } period; + } conn; + NC_CH_START_WITH start_with; + uint8_t max_attempts; + pthread_mutex_t lock; + } *ch_clients; + uint16_t ch_client_count; + pthread_rwlock_t ch_client_lock; /* ACCESS locked with sid_lock */ uint32_t new_session_id; @@ -180,13 +244,40 @@ struct nc_server_opts { /** * Timeout in msec for transport-related data to arrive (ssh_handle_key_exchange(), SSL_accept(), SSL_connect()). + * It can be quite a lot on slow machines (waiting for TLS cert-to-name resolution, ...). + */ +#define NC_TRANSPORT_TIMEOUT 10000 + +/** + * Timeout in msec for acquiring a lock of a session (used with a condition, so higher numbers could be required + * only in case of extreme concurrency). + */ +#define NC_SESSION_LOCK_TIMEOUT 500 + +/** + * Timeout in msec for acquiring a lock of a session that is supposed to be freed. + */ +#define NC_SESSION_FREE_LOCK_TIMEOUT 1000 + +/** + * Timeout in msec for acquiring a lock of a pollsession structure. */ -#define NC_TRANSPORT_TIMEOUT 500 +#define NC_PS_LOCK_TIMEOUT 500 + +/** + * Time slept in msec if no endpoint was created for a running Call Home client. + */ +#define NC_CH_NO_ENDPT_WAIT 1000 + +/** + * Time slept in msec after a failed Call Home endpoint session creation. + */ +#define NC_CH_ENDPT_FAIL_WAIT 1000 /** * Number of sockets kept waiting to be accepted. */ -#define NC_REVERSE_QUEUE 1 +#define NC_REVERSE_QUEUE 5 /** * @brief Type of the session @@ -226,12 +317,14 @@ struct nc_session { /* NETCONF data */ uint32_t id; /**< NETCONF session ID (session-id-type) */ NC_VERSION version; /**< NETCONF protocol version */ - volatile pthread_t *ntf_tid; /**< running notifications thread - TODO client-side only for now */ /* Transport implementation */ NC_TRANSPORT_IMPL ti_type; /**< transport implementation type to select items from ti union */ pthread_mutex_t *ti_lock; /**< lock to access ti. Note that in case of libssh TI, it can be shared with other NETCONF sessions on the same SSH session (but different SSH channel) */ + pthread_cond_t *ti_cond; /**< ti_inuse condition */ + volatile int *ti_inuse; /**< variable indicating whether TI is being communicated on or not, protected by + ti_cond and ti_lock */ union { struct { int in; /**< input file descriptor */ @@ -261,38 +354,47 @@ struct nc_session { #define NC_SESSION_SHAREDCTX 0x01 #define NC_SESSION_CALLHOME 0x02 - /* client side only data */ - uint64_t msgid; - const char **cpblts; /**< list of server's capabilities on client side */ - struct nc_msg_cont *replies; /**< queue for RPC replies received instead of notifications */ - struct nc_msg_cont *notifs; /**< queue for notifications received instead of RPC reply */ - - /* some server modules failed to load so the data from them will be ignored - not use strict flag for parsing */ -# define NC_SESSION_CLIENT_NOT_STRICT 0x40 - - /* server side only data */ - time_t session_start; /**< time the session was created */ - time_t last_rpc; /**< time the last RPC was received on this session */ - + union { + struct { + /* client side only data */ + uint64_t msgid; + const char **cpblts; /**< list of server's capabilities on client side */ + struct nc_msg_cont *replies; /**< queue for RPC replies received instead of notifications */ + struct nc_msg_cont *notifs; /**< queue for notifications received instead of RPC reply */ + volatile pthread_t *ntf_tid; /**< running notifications receiving thread */ + + /* client flags */ + /* some server modules failed to load so the data from them will be ignored - not use strict flag for parsing */ +# define NC_SESSION_CLIENT_NOT_STRICT 0x40 + } client; + struct { + /* server side only data */ + time_t session_start; /**< time the session was created */ + time_t last_rpc; /**< time the last RPC was received on this session */ + int ntf_status; /**< flag whether the session is subscribed to any stream */ + pthread_mutex_t *ch_lock; /**< Call Home thread lock */ + pthread_cond_t *ch_cond; /**< Call Home thread condition */ + + /* server flags */ #ifdef NC_ENABLED_SSH - /* SSH session authenticated */ -# define NC_SESSION_SSH_AUTHENTICATED 0x04 - /* netconf subsystem requested */ -# define NC_SESSION_SSH_SUBSYS_NETCONF 0x08 - /* new SSH message arrived */ -# define NC_SESSION_SSH_NEW_MSG 0x10 - /* this session is passed to nc_sshcb_msg() */ -# define NC_SESSION_SSH_MSG_CB 0x20 - - uint16_t ssh_auth_attempts; /**< number of failed SSH authentication attempts */ + /* SSH session authenticated */ +# define NC_SESSION_SSH_AUTHENTICATED 0x04 + /* netconf subsystem requested */ +# define NC_SESSION_SSH_SUBSYS_NETCONF 0x08 + /* new SSH message arrived */ +# define NC_SESSION_SSH_NEW_MSG 0x10 + /* this session is passed to nc_sshcb_msg() */ +# define NC_SESSION_SSH_MSG_CB 0x20 + + uint16_t ssh_auth_attempts; /**< number of failed SSH authentication attempts */ #endif #ifdef NC_ENABLED_TLS - X509 *tls_cert; /**< TLS client certificate it used for authentication */ + X509 *client_cert; /**< TLS client certificate if used for authentication */ #endif + } server; + } opts; }; -#define NC_PS_QUEUE_SIZE 6 - /* ACCESS locked */ struct nc_pollsession { struct nc_session **sessions; @@ -321,9 +423,15 @@ int pthread_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *absti int nc_gettimespec(struct timespec *ts); -uint32_t nc_difftimespec(struct timespec *ts1, struct timespec *ts2); +int32_t nc_difftimespec(struct timespec *ts1, struct timespec *ts2); + +void nc_addtimespec(struct timespec *ts, uint32_t msec); + +struct nc_session *nc_new_session(int not_allocate_ti); + +int nc_session_lock(struct nc_session *session, int timeout, const char *func); -int nc_timedlock(pthread_mutex_t *lock, int timeout, const char *func); +int nc_session_unlock(struct nc_session *session, int timeout, const char *func); int nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func); @@ -391,40 +499,38 @@ int nc_sock_listen(const char *address, uint16_t port); int nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, char **host, uint16_t *port, uint16_t *idx); /** - * @brief Change an existing endpoint bind. - * - * On error the listening socket is left untouched. + * @brief Lock endpoint structures for reading and the specific endpoint. * - * @param[in] endpt_name Name of the endpoint. - * @param[in] address New address. NULL if \p port. - * @param[in] port New port. NULL if \p address. + * @param[in] name Name of the endpoint. * @param[in] ti Expected transport. - * @return 0 on success, -1 on error. + * @param[out] idx Index of the endpoint. Optional. + * @return Endpoint structure. */ -int nc_server_endpt_set_address_port(const char *endpt_name, const char *address, uint16_t port, NC_TRANSPORT_IMPL ti); +struct nc_endpt *nc_server_endpt_lock_get(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx); /** - * @brief Lock endpoint structures for reading and the specific endpoint. + * @brief Lock CH client structures for reading and the specific client. * - * @param[in] name Name of the endpoint. - * @param[out] idx Index of the endpoint. Optional. - * @return Endpoint structure. + * @param[in] name Name of the CH client. + * @param[in] ti Expected transport. + * @param[out] idx Index of the client. Optional. + * @return CH client structure. */ -struct nc_endpt *nc_server_endpt_lock(const char *name, uint16_t *idx); +struct nc_ch_client *nc_server_ch_client_lock(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx); /** - * @brief Unlock endpoint strcutures and the specific endpoint. + * @brief Unlock CH client strcutures and the specific client. * - * @param[in] endpt Locked endpoint structure. + * @param[in] endpt Locked CH client structure. */ -void nc_server_endpt_unlock(struct nc_endpt *endpt); +void nc_server_ch_client_unlock(struct nc_ch_client *client); /** * @brief Add a client Call Home bind, listen on it. * * @param[in] address Address to bind to. - * @param[in] port to bind to. - * @param[in] ti Expected transport. + * @param[in] port Port to bind to. + * @param[in] ti Transport to use. * @return 0 on success, -1 on error. */ int nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti); @@ -434,7 +540,7 @@ int nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPOR * * @param[in] address Address of the bind. NULL matches any address. * @param[in] port Port of the bind. 0 matches all ports. - * @param[in] ti Expected transport of the bind. 0 matches any. + * @param[in] ti Transport of the bind. 0 matches all transports. * @return 0 on success, -1 on no matches found. */ int nc_client_ch_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti); @@ -554,7 +660,7 @@ NC_MSG_TYPE nc_read_msg(struct nc_session* session, struct lyxml_elem **data); * @brief Write message into wire. * * @param[in] session NETCONF session to which the message will be written. - * @param[in] type Type of the message to write. According to the type, the + * @param[in] type The type of the message to write, specified as #NC_MSG_TYPE value. According to the type, the * specific additional parameters are required or accepted: * - #NC_MSG_RPC * - `struct lyd_node *op;` - operation (content of the \ to be sent. Required parameter. @@ -569,7 +675,7 @@ NC_MSG_TYPE nc_read_msg(struct nc_session* session, struct lyxml_elem **data); * - TODO: content * @return 0 on success */ -int nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...); +int nc_write_msg(struct nc_session *session, int type, ...); /** * @brief Check whether a session is still connected (on transport layer). diff --git a/src/session_server.c b/src/session_server.c index 61f954d0..b69783f6 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -3,7 +3,7 @@ * \author Michal Vasko * \brief libnetconf2 server session manipulation functions * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2017 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. @@ -11,6 +11,7 @@ * * https://opensource.org/licenses/BSD-3-Clause */ +#define _POSIX_SOUCE /* signals */ #include #include @@ -25,33 +26,33 @@ #include #include #include +#include #include "libnetconf.h" #include "session_server.h" struct nc_server_opts server_opts = { - .endpt_array_lock = PTHREAD_RWLOCK_INITIALIZER +#ifdef NC_ENABLED_SSH + .authkey_lock = PTHREAD_MUTEX_INITIALIZER, +#endif + .bind_lock = PTHREAD_MUTEX_INITIALIZER, + .endpt_lock = PTHREAD_RWLOCK_INITIALIZER, + .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER }; static nc_rpc_clb global_rpc_clb = NULL; -extern struct nc_server_ssh_opts ssh_ch_opts; -extern pthread_mutex_t ssh_ch_opts_lock; - -extern struct nc_server_tls_opts tls_ch_opts; -extern pthread_mutex_t tls_ch_opts_lock; - struct nc_endpt * -nc_server_endpt_lock(const char *name, uint16_t *idx) +nc_server_endpt_lock_get(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx) { uint16_t i; struct nc_endpt *endpt = NULL; - /* READ LOCK */ - pthread_rwlock_rdlock(&server_opts.endpt_array_lock); + /* WRITE LOCK */ + pthread_rwlock_wrlock(&server_opts.endpt_lock); for (i = 0; i < server_opts.endpt_count; ++i) { - if (!strcmp(server_opts.endpts[i].name, name)) { + if (!strcmp(server_opts.endpts[i].name, name) && (!ti || (server_opts.endpts[i].ti == ti))) { endpt = &server_opts.endpts[i]; break; } @@ -59,29 +60,59 @@ nc_server_endpt_lock(const char *name, uint16_t *idx) if (!endpt) { ERR("Endpoint \"%s\" was not found.", name); + /* UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); + return NULL; + } + + if (idx) { + *idx = i; + } + + return endpt; +} + +struct nc_ch_client * +nc_server_ch_client_lock(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx) +{ + uint16_t i; + struct nc_ch_client *client = NULL; + + /* READ LOCK */ + pthread_rwlock_rdlock(&server_opts.ch_client_lock); + + for (i = 0; i < server_opts.ch_client_count; ++i) { + if (!strcmp(server_opts.ch_clients[i].name, name) && (!ti || (server_opts.ch_clients[i].ti == ti))) { + client = &server_opts.ch_clients[i]; + break; + } + } + + if (!client) { + ERR("Call Home client \"%s\" was not found.", name); /* READ UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + pthread_rwlock_unlock(&server_opts.ch_client_lock); return NULL; } - /* ENDPT LOCK */ - pthread_mutex_lock(&endpt->endpt_lock); + /* CH CLIENT LOCK */ + pthread_mutex_lock(&client->lock); if (idx) { *idx = i; } - return endpt; + return client; } void -nc_server_endpt_unlock(struct nc_endpt *endpt) +nc_server_ch_client_unlock(struct nc_ch_client *client) { - /* ENDPT UNLOCK */ - pthread_mutex_unlock(&endpt->endpt_lock); + /* CH CLIENT UNLOCK */ + pthread_mutex_unlock(&client->lock); /* READ UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + pthread_rwlock_unlock(&server_opts.ch_client_lock); } API void @@ -179,7 +210,8 @@ nc_sock_listen(const char *address, uint16_t port) int nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, char **host, uint16_t *port, uint16_t *idx) { - uint16_t i; + sigset_t sigmask, origmask; + uint16_t i, j, pfd_count; struct pollfd *pfd; struct sockaddr_storage saddr; socklen_t saddr_len = sizeof(saddr); @@ -191,37 +223,59 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch return -1; } - i = 0; - while (i < bind_count) { + for (i = 0, pfd_count = 0; i < bind_count; ++i) { if (binds[i].sock < 0) { /* invalid socket */ - --bind_count; continue; } - pfd[i].fd = binds[i].sock; - pfd[i].events = POLLIN; - pfd[i].revents = 0; + if (binds[i].pollin) { + binds[i].pollin = 0; + /* leftover pollin */ + sock = binds[i].sock; + break; + } + pfd[pfd_count].fd = binds[i].sock; + pfd[pfd_count].events = POLLIN; + pfd[pfd_count].revents = 0; - ++i; + ++pfd_count; } - /* poll for a new connection */ - errno = 0; - ret = poll(pfd, bind_count, timeout); - if (!ret) { - /* we timeouted */ - free(pfd); - return 0; - } else if (ret == -1) { - ERR("Poll failed (%s).", strerror(errno)); - free(pfd); - return -1; - } + if (sock == -1) { + /* poll for a new connection */ + sigfillset(&sigmask); + pthread_sigmask(SIG_SETMASK, &sigmask, &origmask); + ret = poll(pfd, pfd_count, timeout); + pthread_sigmask(SIG_SETMASK, &origmask, NULL); - for (i = 0; i < bind_count; ++i) { - if (pfd[i].revents & POLLIN) { - sock = pfd[i].fd; - break; + if (!ret) { + /* we timeouted */ + free(pfd); + return 0; + } else if (ret == -1) { + ERR("Poll failed (%s).", strerror(errno)); + free(pfd); + return -1; + } + + for (i = 0, j = 0; j < pfd_count; ++i, ++j) { + /* adjust i so that indices in binds and pfd always match */ + while (binds[i].sock != pfd[j].fd) { + ++i; + } + + if (pfd[j].revents & POLLIN) { + --ret; + + if (!ret) { + /* the last socket with an event, use it */ + sock = pfd[j].fd; + break; + } else { + /* just remember the event for next time */ + binds[i].pollin = 1; + } + } } } free(pfd); @@ -236,6 +290,7 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch ERR("Accept failed (%s).", strerror(errno)); return -1; } + VRB("Accepted a connection on %s:%u.", binds[i].address, binds[i].port); /* make the socket non-blocking */ if (((flags = fcntl(ret, F_GETFL)) == -1) || (fcntl(ret, F_SETFL, flags | O_NONBLOCK) == -1)) { @@ -303,6 +358,9 @@ nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *UNUSED(sessio identifier = ((struct lyd_node_leaf_list *)child)->value_str; } else if (!strcmp(child->schema->name, "version")) { version = ((struct lyd_node_leaf_list *)child)->value_str; + if (version && version[0] == '\0') { + version = NULL; + } } else if (!strcmp(child->schema->name, "format")) { format = ((struct lyd_node_leaf_list *)child)->value_str; } @@ -350,7 +408,7 @@ nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *UNUSED(sessio data = lyd_new_path(NULL, server_opts.ctx, "/ietf-netconf-monitoring:get-schema/data", model_data, LYD_ANYDATA_STRING, LYD_PATH_OPT_OUTPUT); - if (!data) { + if (!data || lyd_validate(&data, LYD_OPT_RPCREPLY, NULL)) { ERRINT; free(model_data); return NULL; @@ -401,10 +459,31 @@ nc_server_init(struct ly_ctx *ctx) API void nc_server_destroy(void) { + unsigned int i; + + for (i = 0; i < server_opts.capabilities_count; i++) { + lydict_remove(server_opts.ctx, server_opts.capabilities[i]); + } + free(server_opts.capabilities); pthread_spin_destroy(&server_opts.sid_lock); #if defined(NC_ENABLED_SSH) || defined(NC_ENABLED_TLS) - nc_server_del_endpt(NULL); + nc_server_del_endpt(NULL, 0); +#endif +#ifdef NC_ENABLED_SSH + nc_server_ssh_del_authkey(NULL, NULL, 0, NULL); + + if (server_opts.hostkey_data && server_opts.hostkey_data_free) { + server_opts.hostkey_data_free(server_opts.hostkey_data); + } +#endif +#ifdef NC_ENABLED_TLS + if (server_opts.server_cert_data && server_opts.server_cert_data_free) { + server_opts.server_cert_data_free(server_opts.server_cert_data); + } + if (server_opts.trusted_cert_list_data && server_opts.trusted_cert_list_data_free) { + server_opts.trusted_cert_list_data_free(server_opts.trusted_cert_list_data); + } #endif nc_destroy(); } @@ -441,20 +520,26 @@ nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported) } } -API void -nc_server_set_capab_interleave(int interleave_support) +API int +nc_server_set_capability(const char *value) { - if (interleave_support) { - server_opts.interleave_capab = 1; - } else { - server_opts.interleave_capab = 0; + const char **new; + + if (!value || !value[0]) { + ERRARG("value must not be empty"); + return EXIT_FAILURE; } -} -API int -nc_server_get_capab_interleave(void) -{ - return server_opts.interleave_capab; + server_opts.capabilities_count++; + new = realloc(server_opts.capabilities, server_opts.capabilities_count * sizeof *server_opts.capabilities); + if (!new) { + ERRMEM; + return EXIT_FAILURE; + } + server_opts.capabilities = new; + server_opts.capabilities[server_opts.capabilities_count - 1] = lydict_insert(server_opts.ctx, value, 0); + + return EXIT_SUCCESS; } API void @@ -504,7 +589,7 @@ nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **s } /* prepare session structure */ - *session = calloc(1, sizeof **session); + *session = nc_new_session(0); if (!(*session)) { ERRMEM; return NC_MSG_ERROR; @@ -512,6 +597,11 @@ nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **s (*session)->status = NC_STATUS_STARTING; (*session)->side = NC_SERVER; + /* transport lock */ + pthread_mutex_init((*session)->ti_lock, NULL); + pthread_cond_init((*session)->ti_cond, NULL); + *(*session)->ti_inuse = 0; + /* transport specific data */ (*session)->ti_type = NC_TI_FD; (*session)->ti.fd.in = fdin; @@ -533,7 +623,7 @@ nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **s *session = NULL; return msgtype; } - (*session)->session_start = (*session)->last_rpc = time(NULL); + (*session)->opts.server.session_start = (*session)->opts.server.last_rpc = time(NULL); (*session)->status = NC_STATUS_RUNNING; return msgtype; @@ -582,7 +672,7 @@ nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func) struct timespec ts; nc_gettimespec(&ts); - ts.tv_sec += NC_READ_TIMEOUT; + nc_addtimespec(&ts, NC_PS_LOCK_TIMEOUT); /* LOCK */ ret = pthread_mutex_timedlock(&ps->lock, &ts); @@ -618,7 +708,7 @@ nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func) /* is it our turn? */ while (ps->queue[ps->queue_begin] != *id) { nc_gettimespec(&ts); - ts.tv_sec += NC_READ_TIMEOUT; + nc_addtimespec(&ts, NC_PS_LOCK_TIMEOUT); ret = pthread_cond_timedwait(&ps->cond, &ps->lock, &ts); if (ret) { @@ -643,7 +733,7 @@ nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func) struct timespec ts; nc_gettimespec(&ts); - ts.tv_sec += NC_READ_TIMEOUT; + nc_addtimespec(&ts, NC_PS_LOCK_TIMEOUT); /* LOCK */ ret = pthread_mutex_timedlock(&ps->lock, &ts); @@ -757,13 +847,11 @@ _nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int in --ps->session_count; if (i < ps->session_count) { ps->sessions[i] = ps->sessions[ps->session_count]; - if (ps->last_event_session == i) { - ps->last_event_session = 0; - } } else if (!ps->session_count) { free(ps->sessions); ps->sessions = NULL; } + ps->last_event_session = 0; return 0; } } @@ -798,28 +886,45 @@ nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session) return (ret || ret2 ? -1 : 0); } -API uint16_t -nc_ps_session_count(struct nc_pollsession *ps) +API struct nc_session * +nc_ps_get_session_by_sid(const struct nc_pollsession *ps, uint32_t sid) { uint8_t q_id; - uint16_t count; + uint16_t i; + struct nc_session *ret = NULL; if (!ps) { ERRARG("ps"); - return 0; + return NULL; } /* LOCK */ - if (nc_ps_lock(ps, &q_id, __func__)) { - return -1; + if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) { + return NULL; } - count = ps->session_count; + for (i = 0; i < ps->session_count; ++i) { + if (ps->sessions[i]->id == sid) { + ret = ps->sessions[i]; + break; + } + } /* UNLOCK */ - nc_ps_unlock(ps, q_id, __func__); + nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__); + + return ret; +} + +API uint16_t +nc_ps_session_count(struct nc_pollsession *ps) +{ + if (!ps) { + ERRARG("ps"); + return 0; + } - return count; + return ps->session_count; } /* must be called holding the session lock! @@ -829,7 +934,7 @@ nc_ps_session_count(struct nc_pollsession *ps) * NC_PSPOLL_RPC */ static int -nc_recv_rpc(struct nc_session *session, struct nc_server_rpc **rpc) +nc_server_recv_rpc(struct nc_session *session, struct nc_server_rpc **rpc) { struct lyxml_elem *xml = NULL; NC_MSG_TYPE msgtype; @@ -858,7 +963,8 @@ nc_recv_rpc(struct nc_session *session, struct nc_server_rpc **rpc) } ly_errno = LY_SUCCESS; - (*rpc)->tree = lyd_parse_xml(server_opts.ctx, &xml->child, LYD_OPT_RPC | LYD_OPT_DESTRUCT, NULL); + (*rpc)->tree = lyd_parse_xml(server_opts.ctx, &xml->child, + LYD_OPT_RPC | LYD_OPT_DESTRUCT | LYD_OPT_NOEXTDEPS | LYD_OPT_STRICT, NULL); if (!(*rpc)->tree) { /* parsing RPC failed */ reply = nc_server_reply_err(nc_err_libyang()); @@ -908,6 +1014,40 @@ nc_set_global_rpc_clb(nc_rpc_clb clb) global_rpc_clb = clb; } +API NC_MSG_TYPE +nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout) +{ + NC_MSG_TYPE result = NC_MSG_NOTIF; + int ret; + + /* check parameters */ + if (!session || (session->side != NC_SERVER) || !session->opts.server.ntf_status) { + ERRARG("session"); + return NC_MSG_ERROR; + } else if (!notif || !notif->tree || !notif->eventtime) { + ERRARG("notif"); + return NC_MSG_ERROR; + } + + /* reading an RPC and sending a reply must be atomic (no other RPC should be read) */ + ret = nc_session_lock(session, timeout, __func__); + if (ret < 0) { + return NC_MSG_ERROR; + } else if (!ret) { + return NC_MSG_WOULDBLOCK; + } + + ret = nc_write_msg(session, NC_MSG_NOTIF, notif); + if (ret == -1) { + ERR("Session %u: failed to write notification.", session->id); + result = NC_MSG_ERROR; + } + + nc_session_unlock(session, timeout, __func__); + + return result; +} + /* must be called holding the session lock! * returns: NC_PSPOLL_ERROR, * NC_PSPOLL_ERROR | NC_PSPOLL_REPLY_ERROR, @@ -915,7 +1055,7 @@ nc_set_global_rpc_clb(nc_rpc_clb clb) * 0 */ static int -nc_send_reply(struct nc_session *session, struct nc_server_rpc *rpc) +nc_server_send_reply(struct nc_session *session, struct nc_server_rpc *rpc) { nc_rpc_clb clb; struct nc_server_reply *reply; @@ -985,38 +1125,34 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) char msg[256]; NC_SESSION_TERM_REASON term_reason; struct pollfd pfd; - struct timespec begin_ts, cur_ts; + struct timespec ts_timeout, ts_cur; struct nc_session *cur_session; struct nc_server_rpc *rpc = NULL; #ifdef NC_ENABLED_SSH struct nc_session *new; #endif - if (!ps || !ps->session_count) { + if (!ps) { ERRARG("ps"); return NC_PSPOLL_ERROR; } - nc_gettimespec(&begin_ts); - - /* LOCK */ + /* PS LOCK */ if (nc_ps_lock(ps, &q_id, __func__)) { return NC_PSPOLL_ERROR; } - /* check that all session are fine */ - for (i = 0; i < ps->session_count; ++i) { - if (ps->sessions[i]->status != NC_STATUS_RUNNING) { - ERR("Session %u: session not running.", ps->sessions[i]->id); - ret = NC_PSPOLL_ERROR; - if (session) { - *session = ps->sessions[i]; - } - goto finish; - } + if (!ps->session_count) { + ret = NC_PSPOLL_NOSESSIONS; + goto ps_unlock_finish; + } - /* TODO invalidate only sessions without subscription */ - if (server_opts.idle_timeout && (begin_ts.tv_sec >= ps->sessions[i]->last_rpc + server_opts.idle_timeout)) { + /* check timeout of all the sessions */ + nc_gettimespec(&ts_cur); + for (i = 0; i < ps->session_count; ++i) { + if (!(ps->sessions[i]->flags & NC_SESSION_CALLHOME) && !ps->sessions[i]->opts.server.ntf_status + && server_opts.idle_timeout + && (ts_cur.tv_sec >= ps->sessions[i]->opts.server.last_rpc + server_opts.idle_timeout)) { ERR("Session %u: session idle timeout elapsed.", ps->sessions[i]->id); ps->sessions[i]->status = NC_STATUS_INVALID; ps->sessions[i]->term_reason = NC_SESSION_TERM_TIMEOUT; @@ -1024,11 +1160,16 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) if (session) { *session = ps->sessions[i]; } - goto finish; + goto ps_unlock_finish; } } - /* poll on all the sessions one-by-one */ + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } + + /* poll all the sessions one-by-one */ do { /* loop from i to j */ if (ps->last_event_session == ps->session_count - 1) { @@ -1039,91 +1180,120 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) do { cur_session = ps->sessions[i]; - switch (cur_session->ti_type) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - r = ssh_channel_poll_timeout(cur_session->ti.libssh.channel, 0, 0); - if (r < 1) { - if (r == SSH_EOF) { - sprintf(msg, "SSH channel unexpectedly closed"); - term_reason = NC_SESSION_TERM_DROPPED; - poll_ret = -2; - } else if (r == SSH_ERROR) { - sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(cur_session->ti.libssh.session)); - poll_ret = -1; - } else { - poll_ret = 0; - } - break; + /* SESSION LOCK */ + if ((cur_session->status == NC_STATUS_RUNNING) + && !*cur_session->ti_inuse && ((r = nc_session_lock(cur_session, 0, __func__)))) { + /* we go here if we successfully lock the session or there was an error, on timeout we simply skip it */ + if (r == -1) { + ret = NC_PSPOLL_ERROR; + goto ps_unlock_finish; + } + /* damn race condition */ + if (cur_session->status != NC_STATUS_RUNNING) { + /* SESSION UNLOCK */ + nc_session_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__); + goto next_iteration; } - /* we have some data, but it may be just an SSH message */ - r = nc_timedlock(cur_session->ti_lock, timeout, __func__); - if (r < 0) { - if (session) { - *session = cur_session; - } - ret = NC_PSPOLL_ERROR; - goto finish; - } else if (!r) { - if (session) { - *session = cur_session; + switch (cur_session->ti_type) { +#ifdef NC_ENABLED_SSH + case NC_TI_LIBSSH: + r = ssh_channel_poll_timeout(cur_session->ti.libssh.channel, 0, 0); + if (r < 1) { + if (r == SSH_EOF) { + sprintf(msg, "SSH channel unexpected EOF"); + term_reason = NC_SESSION_TERM_DROPPED; + poll_ret = -2; + } else if (r == SSH_ERROR) { + sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(cur_session->ti.libssh.session)); + term_reason = NC_SESSION_TERM_OTHER; + poll_ret = -2; + } else { + poll_ret = 0; + } + break; } - ret = NC_PSPOLL_TIMEOUT; - goto finish; - } - r = ssh_execute_message_callbacks(cur_session->ti.libssh.session); - pthread_mutex_unlock(cur_session->ti_lock); - - if (r != SSH_OK) { - sprintf(msg, "failed to receive SSH messages (%s)", ssh_get_error(cur_session->ti.libssh.session)); - term_reason = NC_SESSION_TERM_OTHER; - poll_ret = -1; - } else if (cur_session->flags & NC_SESSION_SSH_NEW_MSG) { - /* new SSH message */ - cur_session->flags &= ~NC_SESSION_SSH_NEW_MSG; - if (cur_session->ti.libssh.next) { - for (new = cur_session->ti.libssh.next; new != cur_session; new = new->ti.libssh.next) { - if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel - && (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { - /* new NETCONF SSH channel */ - if (session) { - *session = cur_session; + + /* we have some data, but it may be just an SSH message */ + r = ssh_execute_message_callbacks(cur_session->ti.libssh.session); + if (r != SSH_OK) { + sprintf(msg, "failed to receive SSH messages (%s)", ssh_get_error(cur_session->ti.libssh.session)); + term_reason = NC_SESSION_TERM_OTHER; + poll_ret = -2; + } else if (cur_session->flags & NC_SESSION_SSH_NEW_MSG) { + /* new SSH message */ + cur_session->flags &= ~NC_SESSION_SSH_NEW_MSG; + if (cur_session->ti.libssh.next) { + for (new = cur_session->ti.libssh.next; new != cur_session; new = new->ti.libssh.next) { + if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel + && (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { + /* new NETCONF SSH channel */ + if (session) { + *session = cur_session; + } + ret = NC_PSPOLL_SSH_CHANNEL; + goto session_ps_unlock_finish; } - ret = NC_PSPOLL_SSH_CHANNEL; - goto finish; } } - } - /* just some SSH message */ - if (session) { - *session = cur_session; + /* just some SSH message */ + if (session) { + *session = cur_session; + } + ret = NC_PSPOLL_SSH_MSG; + goto session_ps_unlock_finish; + } else { + /* we have some application data */ + poll_ret = 1; } - ret = NC_PSPOLL_SSH_MSG; - goto finish; - } else { - /* we have some application data */ - poll_ret = 1; - } - break; + break; #endif #ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - r = SSL_pending(cur_session->ti.tls); - if (!r) { - /* no data pending in the SSL buffer, poll fd */ - pfd.fd = SSL_get_rfd(cur_session->ti.tls); - if (pfd.fd < 0) { - ERRINT; - ret = NC_PSPOLL_ERROR; - goto finish; + case NC_TI_OPENSSL: + r = SSL_pending(cur_session->ti.tls); + if (!r) { + /* no data pending in the SSL buffer, poll fd */ + pfd.fd = SSL_get_rfd(cur_session->ti.tls); + if (pfd.fd < 0) { + ERRINT; + ret = NC_PSPOLL_ERROR; + goto session_ps_unlock_finish; + } + pfd.events = POLLIN; + pfd.revents = 0; + r = poll(&pfd, 1, 0); + + if ((r < 0) && (errno != EINTR)) { + sprintf(msg, "poll failed (%s)", strerror(errno)); + poll_ret = -1; + } else if (r > 0) { + if (pfd.revents & (POLLHUP | POLLNVAL)) { + sprintf(msg, "communication socket unexpectedly closed"); + term_reason = NC_SESSION_TERM_DROPPED; + poll_ret = -2; + } else if (pfd.revents & POLLERR) { + sprintf(msg, "communication socket error"); + term_reason = NC_SESSION_TERM_OTHER; + poll_ret = -2; + } else { + poll_ret = 1; + } + } else { + poll_ret = 0; + } + } else { + poll_ret = 1; } + break; +#endif + case NC_TI_FD: + pfd.fd = cur_session->ti.fd.in; pfd.events = POLLIN; pfd.revents = 0; r = poll(&pfd, 1, 0); - if (r < 0) { + if ((r < 0) && (errno != EINTR)) { sprintf(msg, "poll failed (%s)", strerror(errno)); poll_ret = -1; } else if (r > 0) { @@ -1141,64 +1311,43 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) } else { poll_ret = 0; } - } else { - poll_ret = 1; + break; + case NC_TI_NONE: + ERRINT; + ret = NC_PSPOLL_ERROR; + goto session_ps_unlock_finish; } - break; -#endif - case NC_TI_FD: - pfd.fd = cur_session->ti.fd.in; - pfd.events = POLLIN; - pfd.revents = 0; - r = poll(&pfd, 1, 0); - - if (r < 0) { - sprintf(msg, "poll failed (%s)", strerror(errno)); - poll_ret = 0; - } else if (r > 0) { - if (pfd.revents & (POLLHUP | POLLNVAL)) { - sprintf(msg, "communication socket unexpectedly closed"); - term_reason = NC_SESSION_TERM_DROPPED; - poll_ret = -2; - } else if (pfd.revents & POLLERR) { - sprintf(msg, "communication socket error"); - term_reason = NC_SESSION_TERM_OTHER; - poll_ret = -2; - } else { - poll_ret = 1; + /* we have some data, but it may be just an SSH message */ + /* here: poll_ret == -2 - session error, session terminated, + * poll_ret == -1 - generic error, + * poll_ret == 0 - nothing to read, + * poll_ret > 0 - data available + */ + if (poll_ret == -2) { + ERR("Session %u: %s.", cur_session->id, msg); + cur_session->status = NC_STATUS_INVALID; + cur_session->term_reason = term_reason; + if (session) { + *session = cur_session; } - } else { - poll_ret = 0; + ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR; + goto session_ps_unlock_finish; + } else if (poll_ret == -1) { + ERR("Session %u: %s.", cur_session->id, msg); + ret = NC_PSPOLL_ERROR; + goto session_ps_unlock_finish; + } else if (poll_ret > 0) { + break; } - break; - case NC_TI_NONE: - ERRINT; - ret = NC_PSPOLL_ERROR; - goto finish; - } - /* here: poll_ret == -2 - session error, session terminated, - * poll_ret == -1 - generic error, - * poll_ret == 0 - nothing to read, - * poll_ret > 0 - data available */ - if (poll_ret == -2) { - ERR("Session %u: %s.", cur_session->id, msg); - cur_session->status = NC_STATUS_INVALID; - cur_session->term_reason = term_reason; - if (session) { - *session = cur_session; - } - ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR; - goto finish; - } else if (poll_ret == -1) { - ERR("Session %u: %s.", cur_session->id, msg); - ret = NC_PSPOLL_ERROR; - goto finish; - } else if (poll_ret > 0) { - break; + /* SESSION UNLOCK */ + nc_session_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__); + } else { + /* timeout */ + poll_ret = 0; } - /* next iteration */ +next_iteration: if (i == ps->session_count - 1) { i = 0; } else { @@ -1206,50 +1355,42 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) } } while (i != j); - /* no event */ + /* no event, no session remains locked */ if (!poll_ret && (timeout > -1)) { usleep(NC_TIMEOUT_STEP); - nc_gettimespec(&cur_ts); + nc_gettimespec(&ts_cur); /* final timeout */ - if (nc_difftimespec(&begin_ts, &cur_ts) >= (unsigned)timeout) { + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { ret = NC_PSPOLL_TIMEOUT; - goto finish; + goto ps_unlock_finish; } } } while (!poll_ret); - /* this is the session with some data available for reading */ + /* this is the session with some data available for reading, it is still locked */ if (session) { *session = cur_session; } ps->last_event_session = i; - /* reading an RPC and sending a reply must be atomic (no other RPC should be read) */ - r = nc_timedlock(cur_session->ti_lock, timeout, __func__); - if (r < 0) { - ret = NC_PSPOLL_ERROR; - goto finish; - } else if (!r) { - ret = NC_PSPOLL_TIMEOUT; - goto finish; - } + /* PS UNLOCK */ + nc_ps_unlock(ps, q_id, __func__); - ret = nc_recv_rpc(cur_session, &rpc); + ret = nc_server_recv_rpc(cur_session, &rpc); if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) { - pthread_mutex_unlock(cur_session->ti_lock); if (cur_session->status != NC_STATUS_RUNNING) { ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR; } - goto finish; + goto session_unlock_finish; } - cur_session->last_rpc = time(NULL); + cur_session->opts.server.last_rpc = time(NULL); - /* process RPC */ - ret |= nc_send_reply(cur_session, rpc); + /* process RPC, not needed afterwards */ + ret |= nc_server_send_reply(cur_session, rpc); + nc_server_rpc_free(rpc, server_opts.ctx); - pthread_mutex_unlock(cur_session->ti_lock); if (cur_session->status != NC_STATUS_RUNNING) { ret |= NC_PSPOLL_SESSION_TERM; if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) { @@ -1257,10 +1398,17 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) } } - nc_server_rpc_free(rpc, server_opts.ctx); +session_unlock_finish: + /* SESSION UNLOCK */ + nc_session_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__); + return ret; -finish: - /* UNLOCK */ +session_ps_unlock_finish: + /* SESSION UNLOCK */ + nc_session_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__); + +ps_unlock_finish: + /* PS UNLOCK */ nc_ps_unlock(ps, q_id, __func__); return ret; } @@ -1310,31 +1458,28 @@ nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *)) #if defined(NC_ENABLED_SSH) || defined(NC_ENABLED_TLS) API int -nc_server_add_endpt(const char *name) +nc_server_add_endpt(const char *name, NC_TRANSPORT_IMPL ti) { uint16_t i; -#ifdef NC_ENABLED_SSH - uint16_t bind_ssh_idx; -#endif -#ifdef NC_ENABLED_TLS - uint16_t bind_tls_idx; -#endif + int ret = 0; if (!name) { ERRARG("name"); return -1; } - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_array_lock); + /* BIND LOCK */ + pthread_mutex_lock(&server_opts.bind_lock); + + /* ENDPT WRITE LOCK */ + pthread_rwlock_wrlock(&server_opts.endpt_lock); /* check name uniqueness */ for (i = 0; i < server_opts.endpt_count; ++i) { if (!strcmp(server_opts.endpts[i].name, name)) { ERR("Endpoint \"%s\" already exists.", name); - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); - return -1; + ret = -1; + goto cleanup; } } @@ -1342,82 +1487,72 @@ nc_server_add_endpt(const char *name) server_opts.endpts = nc_realloc(server_opts.endpts, server_opts.endpt_count * sizeof *server_opts.endpts); if (!server_opts.endpts) { ERRMEM; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); - return -1; + ret = -1; + goto cleanup; } server_opts.endpts[server_opts.endpt_count - 1].name = lydict_insert(server_opts.ctx, name, 0); + server_opts.endpts[server_opts.endpt_count - 1].ti = ti; -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - server_opts.binds = nc_realloc(server_opts.binds, 2 * server_opts.endpt_count * sizeof *server_opts.binds); - bind_ssh_idx = (server_opts.endpt_count - 1) * 2; - bind_tls_idx = (server_opts.endpt_count - 1) * 2 + 1; -#else server_opts.binds = nc_realloc(server_opts.binds, server_opts.endpt_count * sizeof *server_opts.binds); -# ifdef NC_ENABLED_SSH - bind_ssh_idx = server_opts.endpt_count - 1; -# endif -# ifdef NC_ENABLED_TLS - bind_tls_idx = server_opts.endpt_count - 1; -# endif -#endif if (!server_opts.binds) { ERRMEM; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); - return -1; + ret = -1; + goto cleanup; } -#ifdef NC_ENABLED_SSH - server_opts.binds[bind_ssh_idx].address = NULL; - server_opts.binds[bind_ssh_idx].port = 0; - server_opts.binds[bind_ssh_idx].sock = -1; - server_opts.binds[bind_ssh_idx].ti = NC_TI_LIBSSH; + server_opts.binds[server_opts.endpt_count - 1].address = NULL; + server_opts.binds[server_opts.endpt_count - 1].port = 0; + server_opts.binds[server_opts.endpt_count - 1].sock = -1; + server_opts.binds[server_opts.endpt_count - 1].pollin = 0; - server_opts.endpts[server_opts.endpt_count - 1].ssh_opts = calloc(1, sizeof(struct nc_server_ssh_opts)); - if (!server_opts.endpts[server_opts.endpt_count - 1].ssh_opts) { - ERRMEM; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); - return -1; - } - /* set default values */ - server_opts.endpts[server_opts.endpt_count - 1].ssh_opts->auth_methods = - NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE; - server_opts.endpts[server_opts.endpt_count - 1].ssh_opts->auth_attempts = 3; - server_opts.endpts[server_opts.endpt_count - 1].ssh_opts->auth_timeout = 10; + switch (ti) { +#ifdef NC_ENABLED_SSH + case NC_TI_LIBSSH: + server_opts.endpts[server_opts.endpt_count - 1].opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); + if (!server_opts.endpts[server_opts.endpt_count - 1].opts.ssh) { + ERRMEM; + ret = -1; + goto cleanup; + } + server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_methods = + NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE; + server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_attempts = 3; + server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_timeout = 10; + break; #endif - #ifdef NC_ENABLED_TLS - server_opts.binds[bind_tls_idx].address = NULL; - server_opts.binds[bind_tls_idx].port = 0; - server_opts.binds[bind_tls_idx].sock = -1; - server_opts.binds[bind_tls_idx].ti = NC_TI_OPENSSL; - - server_opts.endpts[server_opts.endpt_count - 1].tls_opts = calloc(1, sizeof(struct nc_server_tls_opts)); - if (!server_opts.endpts[server_opts.endpt_count - 1].tls_opts) { - ERRMEM; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); - return -1; - } + case NC_TI_OPENSSL: + server_opts.endpts[server_opts.endpt_count - 1].opts.tls = calloc(1, sizeof(struct nc_server_tls_opts)); + if (!server_opts.endpts[server_opts.endpt_count - 1].opts.tls) { + ERRMEM; + ret = -1; + goto cleanup; + } + break; #endif + default: + ERRINT; + ret = -1; + goto cleanup; + } - pthread_mutex_init(&server_opts.endpts[server_opts.endpt_count - 1].endpt_lock, NULL); - - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); +cleanup: + /* ENDPT UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); - return 0; + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); + + return ret; } int -nc_server_endpt_set_address_port(const char *endpt_name, const char *address, uint16_t port, NC_TRANSPORT_IMPL ti) +nc_server_endpt_set_address_port(const char *endpt_name, const char *address, uint16_t port) { struct nc_endpt *endpt; struct nc_bind *bind = NULL; uint16_t i; - int sock = -1, set_addr; + int sock = -1, set_addr, ret = 0; if (!endpt_name) { ERRARG("endpt_name"); @@ -1425,9 +1560,6 @@ nc_server_endpt_set_address_port(const char *endpt_name, const char *address, ui } else if ((!address && !port) || (address && port)) { ERRARG("address and port"); return -1; - } else if (!ti) { - ERRARG("ti"); - return -1; } if (address) { @@ -1436,25 +1568,18 @@ nc_server_endpt_set_address_port(const char *endpt_name, const char *address, ui set_addr = 0; } - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, &i); + /* BIND LOCK */ + pthread_mutex_lock(&server_opts.bind_lock); + + /* ENDPT LOCK */ + endpt = nc_server_endpt_lock_get(endpt_name, 0, &i); if (!endpt) { + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); return -1; } -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - if (ti == NC_TI_LIBSSH) { - bind = &server_opts.binds[2 * i]; - } else { - bind = &server_opts.binds[2 * i + 1]; - } -#else bind = &server_opts.binds[i]; - if (bind->ti != ti) { - ERRINT; - goto fail; - } -#endif if (set_addr) { port = bind->port; @@ -1467,7 +1592,8 @@ nc_server_endpt_set_address_port(const char *endpt_name, const char *address, ui /* create new socket, close the old one */ sock = nc_sock_listen(address, port); if (sock == -1) { - goto fail; + ret = -1; + goto cleanup; } if (bind->sock > -1) { @@ -1485,7 +1611,7 @@ nc_server_endpt_set_address_port(const char *endpt_name, const char *address, ui if (sock > -1) { #if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - VRB("Listening on %s:%u for %s connections.", address, port, (ti == NC_TI_LIBSSH ? "SSH" : "TLS")); + VRB("Listening on %s:%u for %s connections.", address, port, (endpt->ti == NC_TI_LIBSSH ? "SSH" : "TLS")); #elif defined(NC_ENABLED_SSH) VRB("Listening on %s:%u for SSH connections.", address, port); #else @@ -1493,38 +1619,62 @@ nc_server_endpt_set_address_port(const char *endpt_name, const char *address, ui #endif } - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - return 0; +cleanup: + /* ENDPT UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); -fail: - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - return -1; + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); + + return ret; +} + +API int +nc_server_endpt_set_address(const char *endpt_name, const char *address) +{ + return nc_server_endpt_set_address_port(endpt_name, address, 0); +} + +API int +nc_server_endpt_set_port(const char *endpt_name, uint16_t port) +{ + return nc_server_endpt_set_address_port(endpt_name, NULL, port); } API int -nc_server_del_endpt(const char *name) +nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti) { uint32_t i; int ret = -1; - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_array_lock); + /* BIND LOCK */ + pthread_mutex_lock(&server_opts.bind_lock); - if (!name) { + /* ENDPT WRITE LOCK */ + pthread_rwlock_wrlock(&server_opts.endpt_lock); + + if (!name && !ti) { /* remove all endpoints */ for (i = 0; i < server_opts.endpt_count; ++i) { lydict_remove(server_opts.ctx, server_opts.endpts[i].name); - pthread_mutex_destroy(&server_opts.endpts[i].endpt_lock); + switch (server_opts.endpts[i].ti) { #ifdef NC_ENABLED_SSH - nc_server_ssh_clear_opts(server_opts.endpts[i].ssh_opts); - free(server_opts.endpts[i].ssh_opts); + case NC_TI_LIBSSH: + nc_server_ssh_clear_opts(server_opts.endpts[i].opts.ssh); + free(server_opts.endpts[i].opts.ssh); + break; #endif #ifdef NC_ENABLED_TLS - nc_server_tls_clear_opts(server_opts.endpts[i].tls_opts); - free(server_opts.endpts[i].tls_opts); + case NC_TI_OPENSSL: + nc_server_tls_clear_opts(server_opts.endpts[i].opts.tls); + free(server_opts.endpts[i].opts.tls); + break; #endif + default: + ERRINT; + /* won't get here ...*/ + break; + } ret = 0; } free(server_opts.endpts); @@ -1537,44 +1687,36 @@ nc_server_del_endpt(const char *name) close(server_opts.binds[i].sock); } } -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - for (; i < 2 * server_opts.endpt_count; ++i) { - lydict_remove(server_opts.ctx, server_opts.binds[i].address); - if (server_opts.binds[i].sock > -1) { - close(server_opts.binds[i].sock); - } - } -#endif free(server_opts.binds); server_opts.binds = NULL; server_opts.endpt_count = 0; } else { - /* remove one endpoint with bind(s) */ + /* remove one endpoint with bind(s) or all endpoints using one transport protocol */ for (i = 0; i < server_opts.endpt_count; ++i) { - if (!strcmp(server_opts.endpts[i].name, name)) { + if ((name && !strcmp(server_opts.endpts[i].name, name)) || (!name && (server_opts.endpts[i].ti == ti))) { /* remove endpt */ lydict_remove(server_opts.ctx, server_opts.endpts[i].name); - pthread_mutex_destroy(&server_opts.endpts[i].endpt_lock); + switch (server_opts.endpts[i].ti) { #ifdef NC_ENABLED_SSH - nc_server_ssh_clear_opts(server_opts.endpts[i].ssh_opts); - free(server_opts.endpts[i].ssh_opts); + case NC_TI_LIBSSH: + nc_server_ssh_clear_opts(server_opts.endpts[i].opts.ssh); + free(server_opts.endpts[i].opts.ssh); + break; #endif #ifdef NC_ENABLED_TLS - nc_server_tls_clear_opts(server_opts.endpts[i].tls_opts); - free(server_opts.endpts[i].tls_opts); + case NC_TI_OPENSSL: + nc_server_tls_clear_opts(server_opts.endpts[i].opts.tls); + free(server_opts.endpts[i].opts.tls); + break; #endif + default: + ERRINT; + break; + } /* remove bind(s) */ -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - i *= 2; - lydict_remove(server_opts.ctx, server_opts.binds[i].address); - if (server_opts.binds[i].sock > -1) { - close(server_opts.binds[i].sock); - } - ++i; -#endif lydict_remove(server_opts.ctx, server_opts.binds[i].address); if (server_opts.binds[i].sock > -1) { close(server_opts.binds[i].sock); @@ -1582,34 +1724,29 @@ nc_server_del_endpt(const char *name) /* move last endpt and bind(s) to the empty space */ --server_opts.endpt_count; -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - --i; - i /= 2; - if (i < server_opts.endpt_count) { - memcpy(&server_opts.binds[2 * i], &server_opts.binds[2 * server_opts.endpt_count], 2 * sizeof *server_opts.binds); - memcpy(&server_opts.endpts[i], &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); - } -#else - if (i < server_opts.endpt_count) { - memcpy(&server_opts.binds[i], &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds); - memcpy(&server_opts.endpts[i], &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); - } -#endif - else if (!server_opts.endpt_count) { + if (!server_opts.endpt_count) { free(server_opts.binds); server_opts.binds = NULL; free(server_opts.endpts); server_opts.endpts = NULL; + } else if (i < server_opts.endpt_count) { + memcpy(&server_opts.binds[i], &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds); + memcpy(&server_opts.endpts[i], &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); } ret = 0; - break; + if (name) { + break; + } } } } - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + /* ENDPT UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); + + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); return ret; } @@ -1620,7 +1757,7 @@ nc_accept(int timeout, struct nc_session **session) NC_MSG_TYPE msgtype; int sock, ret; char *host = NULL; - uint16_t port, endpt_idx, bind_idx; + uint16_t port, bind_idx; if (!server_opts.ctx) { ERRINIT; @@ -1630,36 +1767,37 @@ nc_accept(int timeout, struct nc_session **session) return NC_MSG_ERROR; } - /* we have to hold WRITE for the whole time, since there is not - * a way of downgrading the lock to READ */ - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_array_lock); + /* BIND LOCK */ + pthread_mutex_lock(&server_opts.bind_lock); if (!server_opts.endpt_count) { ERR("No endpoints to accept sessions on."); - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); return NC_MSG_ERROR; } -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count * 2, timeout, &host, &port, &bind_idx); -#else ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, timeout, &host, &port, &bind_idx); -#endif - if (ret < 1) { - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); free(host); if (!ret) { return NC_MSG_WOULDBLOCK; } return NC_MSG_ERROR; } + + /* switch bind_lock for endpt_lock, so that another thread can accept another session */ + /* ENDPT READ LOCK */ + pthread_rwlock_rdlock(&server_opts.endpt_lock); + + /* BIND UNLOCK */ + pthread_mutex_unlock(&server_opts.bind_lock); + sock = ret; - *session = calloc(1, sizeof **session); + *session = nc_new_session(0); if (!(*session)) { ERRMEM; close(sock); @@ -1675,29 +1813,15 @@ nc_accept(int timeout, struct nc_session **session) (*session)->port = port; /* transport lock */ - (*session)->ti_lock = malloc(sizeof *(*session)->ti_lock); - if (!(*session)->ti_lock) { - ERRMEM; - close(sock); - msgtype = NC_MSG_ERROR; - goto cleanup; - } pthread_mutex_init((*session)->ti_lock, NULL); - - endpt_idx = bind_idx; - /* transform index as needed */ -#if defined(NC_ENABLED_SSH) && defined(NC_ENABLED_TLS) - if (server_opts.binds[bind_idx].ti == NC_TI_OPENSSL) { - --endpt_idx; - } - endpt_idx /= 2; -#endif + pthread_cond_init((*session)->ti_cond, NULL); + *(*session)->ti_inuse = 0; /* sock gets assigned to session or closed */ #ifdef NC_ENABLED_SSH - if (server_opts.binds[bind_idx].ti == NC_TI_LIBSSH) { - (*session)->data = server_opts.endpts[endpt_idx].ssh_opts; - ret = nc_accept_ssh_session(*session, sock, timeout); + if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) { + (*session)->data = server_opts.endpts[bind_idx].opts.ssh; + ret = nc_accept_ssh_session(*session, sock, NC_TRANSPORT_TIMEOUT); if (ret < 0) { msgtype = NC_MSG_ERROR; goto cleanup; @@ -1708,9 +1832,9 @@ nc_accept(int timeout, struct nc_session **session) } else #endif #ifdef NC_ENABLED_TLS - if (server_opts.binds[bind_idx].ti == NC_TI_OPENSSL) { - (*session)->data = server_opts.endpts[endpt_idx].tls_opts; - ret = nc_accept_tls_session(*session, sock, timeout); + if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) { + (*session)->data = server_opts.endpts[bind_idx].opts.tls; + ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); if (ret < 0) { msgtype = NC_MSG_ERROR; goto cleanup; @@ -1729,8 +1853,8 @@ nc_accept(int timeout, struct nc_session **session) (*session)->data = NULL; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + /* ENDPT UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); /* assign new SID atomically */ /* LOCK */ @@ -1746,150 +1870,1058 @@ nc_accept(int timeout, struct nc_session **session) *session = NULL; return msgtype; } - (*session)->session_start = (*session)->last_rpc = time(NULL); + (*session)->opts.server.session_start = (*session)->opts.server.last_rpc = time(NULL); (*session)->status = NC_STATUS_RUNNING; return msgtype; cleanup: - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_array_lock); + /* ENDPT UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); nc_session_free(*session, NULL); *session = NULL; return msgtype; } -NC_MSG_TYPE -nc_connect_callhome(const char *host, uint16_t port, NC_TRANSPORT_IMPL ti, struct nc_session **session) +API int +nc_server_ch_add_client(const char *name, NC_TRANSPORT_IMPL ti) { - NC_MSG_TYPE msgtype; - int sock, ret; + uint16_t i; - if (!host) { - ERRARG("host"); - return NC_MSG_ERROR; - } else if (!port) { - ERRARG("port"); - return NC_MSG_ERROR; + if (!name) { + ERRARG("name"); + return -1; } else if (!ti) { ERRARG("ti"); - return NC_MSG_ERROR; - } else if (!session) { - ERRARG("session"); - return NC_MSG_ERROR; + return -1; } - sock = nc_sock_connect(host, port); - if (sock < 0) { - return NC_MSG_ERROR; - } + /* WRITE LOCK */ + pthread_rwlock_wrlock(&server_opts.ch_client_lock); - *session = calloc(1, sizeof **session); - if (!(*session)) { - ERRMEM; - close(sock); - return NC_MSG_ERROR; + /* check name uniqueness */ + for (i = 0; i < server_opts.ch_client_count; ++i) { + if (!strcmp(server_opts.ch_clients[i].name, name)) { + ERR("Call Home client \"%s\" already exists.", name); + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return -1; + } } - (*session)->status = NC_STATUS_STARTING; - (*session)->side = NC_SERVER; - (*session)->ctx = server_opts.ctx; - (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME; - (*session)->host = lydict_insert(server_opts.ctx, host, 0); - (*session)->port = port; - /* transport lock */ - (*session)->ti_lock = malloc(sizeof *(*session)->ti_lock); - if (!(*session)->ti_lock) { + ++server_opts.ch_client_count; + server_opts.ch_clients = nc_realloc(server_opts.ch_clients, server_opts.ch_client_count * sizeof *server_opts.ch_clients); + if (!server_opts.ch_clients) { ERRMEM; - close(sock); - msgtype = NC_MSG_ERROR; - goto fail; + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return -1; } - pthread_mutex_init((*session)->ti_lock, NULL); + server_opts.ch_clients[server_opts.ch_client_count - 1].name = lydict_insert(server_opts.ctx, name, 0); + server_opts.ch_clients[server_opts.ch_client_count - 1].ti = ti; + server_opts.ch_clients[server_opts.ch_client_count - 1].ch_endpts = NULL; + server_opts.ch_clients[server_opts.ch_client_count - 1].ch_endpt_count = 0; - /* sock gets assigned to session or closed */ + switch (ti) { #ifdef NC_ENABLED_SSH - if (ti == NC_TI_LIBSSH) { - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); + case NC_TI_LIBSSH: + server_opts.ch_clients[server_opts.ch_client_count - 1].opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); + if (!server_opts.ch_clients[server_opts.ch_client_count - 1].opts.ssh) { + ERRMEM; + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return -1; + } + server_opts.ch_clients[server_opts.ch_client_count - 1].opts.ssh->auth_methods = + NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE; + server_opts.ch_clients[server_opts.ch_client_count - 1].opts.ssh->auth_attempts = 3; + server_opts.ch_clients[server_opts.ch_client_count - 1].opts.ssh->auth_timeout = 10; + break; +#endif +#ifdef NC_ENABLED_TLS + case NC_TI_OPENSSL: + server_opts.ch_clients[server_opts.ch_client_count - 1].opts.tls = calloc(1, sizeof(struct nc_server_tls_opts)); + if (!server_opts.ch_clients[server_opts.ch_client_count - 1].opts.tls) { + ERRMEM; + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return -1; + } + break; +#endif + default: + ERRINT; + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return -1; + } - (*session)->data = &ssh_ch_opts; - ret = nc_accept_ssh_session(*session, sock, NC_TRANSPORT_TIMEOUT); - (*session)->data = NULL; + server_opts.ch_clients[server_opts.ch_client_count - 1].conn_type = 0; - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* set CH default options */ + server_opts.ch_clients[server_opts.ch_client_count - 1].start_with = NC_CH_FIRST_LISTED; + server_opts.ch_clients[server_opts.ch_client_count - 1].max_attempts = 3; - if (ret < 0) { - msgtype = NC_MSG_ERROR; - goto fail; - } else if (!ret) { - msgtype = NC_MSG_WOULDBLOCK; - goto fail; - } - } else + pthread_mutex_init(&server_opts.ch_clients[server_opts.ch_client_count - 1].lock, NULL); + + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); + + return 0; +} + +API int +nc_server_ch_del_client(const char *name, NC_TRANSPORT_IMPL ti) +{ + uint16_t i, j; + int ret = -1; + + /* WRITE LOCK */ + pthread_rwlock_wrlock(&server_opts.ch_client_lock); + + if (!name && !ti) { + /* remove all CH clients */ + for (i = 0; i < server_opts.ch_client_count; ++i) { + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].name); + + /* remove all endpoints */ + for (j = 0; j < server_opts.ch_clients[i].ch_endpt_count; ++j) { + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].ch_endpts[j].name); + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].ch_endpts[j].address); + } + free(server_opts.ch_clients[i].ch_endpts); + + switch (server_opts.ch_clients[i].ti) { +#ifdef NC_ENABLED_SSH + case NC_TI_LIBSSH: + nc_server_ssh_clear_opts(server_opts.ch_clients[i].opts.ssh); + free(server_opts.ch_clients[i].opts.ssh); + break; #endif #ifdef NC_ENABLED_TLS - if (ti == NC_TI_OPENSSL) { - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - - (*session)->data = &tls_ch_opts; - ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); - (*session)->data = NULL; + case NC_TI_OPENSSL: + nc_server_tls_clear_opts(server_opts.ch_clients[i].opts.tls); + free(server_opts.ch_clients[i].opts.tls); + break; +#endif + default: + ERRINT; + /* won't get here ...*/ + break; + } - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + pthread_mutex_destroy(&server_opts.ch_clients[i].lock); - if (ret < 0) { - msgtype = NC_MSG_ERROR; - goto fail; - } else if (!ret) { - msgtype = NC_MSG_WOULDBLOCK; - goto fail; + ret = 0; } - } else + free(server_opts.ch_clients); + server_opts.ch_clients = NULL; + + server_opts.ch_client_count = 0; + + } else { + /* remove one client with endpoint(s) or all clients using one protocol */ + for (i = 0; i < server_opts.ch_client_count; ++i) { + if ((name && !strcmp(server_opts.ch_clients[i].name, name)) || (!name && (server_opts.ch_clients[i].ti == ti))) { + /* remove endpt */ + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].name); + + switch (server_opts.ch_clients[i].ti) { +#ifdef NC_ENABLED_SSH + case NC_TI_LIBSSH: + nc_server_ssh_clear_opts(server_opts.ch_clients[i].opts.ssh); + free(server_opts.ch_clients[i].opts.ssh); + break; #endif - { - ERRINT; - close(sock); - msgtype = NC_MSG_ERROR; - goto fail; - } +#ifdef NC_ENABLED_TLS + case NC_TI_OPENSSL: + nc_server_tls_clear_opts(server_opts.ch_clients[i].opts.tls); + free(server_opts.ch_clients[i].opts.tls); + break; +#endif + default: + ERRINT; + break; + } - /* assign new SID atomically */ - /* LOCK */ - pthread_spin_lock(&server_opts.sid_lock); - (*session)->id = server_opts.new_session_id++; - /* UNLOCK */ - pthread_spin_unlock(&server_opts.sid_lock); + /* remove all endpoints */ + for (j = 0; j < server_opts.ch_clients[i].ch_endpt_count; ++j) { + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].ch_endpts[j].name); + lydict_remove(server_opts.ctx, server_opts.ch_clients[i].ch_endpts[j].address); + } + free(server_opts.ch_clients[i].ch_endpts); + + pthread_mutex_destroy(&server_opts.ch_clients[i].lock); + + /* move last client and endpoint(s) to the empty space */ + --server_opts.ch_client_count; + if (i < server_opts.ch_client_count) { + memcpy(&server_opts.ch_clients[i], &server_opts.ch_clients[server_opts.ch_client_count], + sizeof *server_opts.ch_clients); + } else if (!server_opts.ch_client_count) { + free(server_opts.ch_clients); + server_opts.ch_clients = NULL; + } - /* NETCONF handshake */ - msgtype = nc_handshake(*session); - if (msgtype != NC_MSG_HELLO) { - goto fail; + ret = 0; + if (name) { + break; + } + } + } } - (*session)->session_start = (*session)->last_rpc = time(NULL); - (*session)->status = NC_STATUS_RUNNING; - return msgtype; + /* WRITE UNLOCK */ + pthread_rwlock_unlock(&server_opts.ch_client_lock); -fail: - nc_session_free(*session, NULL); - *session = NULL; - return msgtype; + return ret; } -#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ +API int +nc_server_ch_client_add_endpt(const char *client_name, const char *endpt_name) +{ + uint16_t i; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, endpt_name)) { + ERR("Call Home client \"%s\" endpoint \"%s\" already exists.", client_name, endpt_name); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + } + + ++client->ch_endpt_count; + client->ch_endpts = realloc(client->ch_endpts, client->ch_endpt_count * sizeof *client->ch_endpts); + if (!client->ch_endpts) { + ERRMEM; + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->ch_endpts[client->ch_endpt_count - 1].name = lydict_insert(server_opts.ctx, endpt_name, 0); + client->ch_endpts[client->ch_endpt_count - 1].address = NULL; + client->ch_endpts[client->ch_endpt_count - 1].port = 0; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_del_endpt(const char *client_name, const char *endpt_name) +{ + uint16_t i; + int ret = -1; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (!endpt_name) { + /* remove all endpoints */ + for (i = 0; i < client->ch_endpt_count; ++i) { + lydict_remove(server_opts.ctx, client->ch_endpts[i].name); + lydict_remove(server_opts.ctx, client->ch_endpts[i].address); + } + free(client->ch_endpts); + client->ch_endpts = NULL; + client->ch_endpt_count = 0; + + ret = 0; + } else { + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, endpt_name)) { + lydict_remove(server_opts.ctx, client->ch_endpts[i].name); + lydict_remove(server_opts.ctx, client->ch_endpts[i].address); + + /* move last endpoint to the empty space */ + --client->ch_endpt_count; + if (i < client->ch_endpt_count) { + memcpy(&client->ch_endpts[i], &client->ch_endpts[client->ch_endpt_count], sizeof *client->ch_endpts); + } else if (!server_opts.ch_client_count) { + free(server_opts.ch_clients); + server_opts.ch_clients = NULL; + } + + ret = 0; + break; + } + } + } + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return ret; +} + +API int +nc_server_ch_client_endpt_set_address(const char *client_name, const char *endpt_name, const char *address) +{ + uint16_t i; + int ret = -1; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } else if (!address) { + ERRARG("address"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, endpt_name)) { + lydict_remove(server_opts.ctx, client->ch_endpts[i].address); + client->ch_endpts[i].address = lydict_insert(server_opts.ctx, address, 0); + + ret = 0; + break; + } + } + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + if (ret == -1) { + ERR("Call Home client \"%s\" endpoint \"%s\" not found.", client_name, endpt_name); + } + + return ret; +} + +API int +nc_server_ch_client_endpt_set_port(const char *client_name, const char *endpt_name, uint16_t port) +{ + uint16_t i; + int ret = -1; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } else if (!port) { + ERRARG("port"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, endpt_name)) { + client->ch_endpts[i].port = port; + + ret = 0; + break; + } + } + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + if (ret == -1) { + ERR("Call Home client \"%s\" endpoint \"%s\" not found.", client_name, endpt_name); + } + + return ret; +} + +API int +nc_server_ch_client_set_conn_type(const char *client_name, NC_CH_CONN_TYPE conn_type) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!conn_type) { + ERRARG("conn_type"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != conn_type) { + client->conn_type = conn_type; + + /* set default options */ + switch (conn_type) { + case NC_CH_PERSIST: + client->conn.persist.idle_timeout = 86400; + client->conn.persist.ka_max_wait = 30; + client->conn.persist.ka_max_attempts = 3; + break; + case NC_CH_PERIOD: + client->conn.period.idle_timeout = 300; + client->conn.period.reconnect_timeout = 60; + break; + default: + ERRINT; + break; + } + } + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_persist_set_idle_timeout(const char *client_name, uint32_t idle_timeout) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != NC_CH_PERSIST) { + ERR("Call Home client \"%s\" is not of persistent connection type."); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->conn.persist.idle_timeout = idle_timeout; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_persist_set_keep_alive_max_wait(const char *client_name, uint16_t max_wait) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!max_wait) { + ERRARG("max_wait"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != NC_CH_PERSIST) { + ERR("Call Home client \"%s\" is not of persistent connection type."); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->conn.persist.ka_max_wait = max_wait; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_persist_set_keep_alive_max_attempts(const char *client_name, uint8_t max_attempts) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != NC_CH_PERSIST) { + ERR("Call Home client \"%s\" is not of persistent connection type."); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->conn.persist.ka_max_attempts = max_attempts; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_period_set_idle_timeout(const char *client_name, uint16_t idle_timeout) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != NC_CH_PERIOD) { + ERR("Call Home client \"%s\" is not of periodic connection type."); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->conn.period.idle_timeout = idle_timeout; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_period_set_reconnect_timeout(const char *client_name, uint16_t reconnect_timeout) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!reconnect_timeout) { + ERRARG("reconnect_timeout"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + if (client->conn_type != NC_CH_PERIOD) { + ERR("Call Home client \"%s\" is not of periodic connection type."); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + return -1; + } + + client->conn.period.reconnect_timeout = reconnect_timeout; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_set_start_with(const char *client_name, NC_CH_START_WITH start_with) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + client->start_with = start_with; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +API int +nc_server_ch_client_set_max_attempts(const char *client_name, uint8_t max_attempts) +{ + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!max_attempts) { + ERRARG("max_attempts"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, 0, NULL); + if (!client) { + return -1; + } + + client->max_attempts = max_attempts; + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return 0; +} + +/* client lock is expected to be held */ +static NC_MSG_TYPE +nc_connect_ch_client_endpt(struct nc_ch_client *client, struct nc_ch_endpt *endpt, struct nc_session **session) +{ + NC_MSG_TYPE msgtype; + int sock, ret; + + sock = nc_sock_connect(endpt->address, endpt->port); + if (sock < 0) { + return NC_MSG_ERROR; + } + + *session = nc_new_session(0); + if (!(*session)) { + ERRMEM; + close(sock); + return NC_MSG_ERROR; + } + (*session)->status = NC_STATUS_STARTING; + (*session)->side = NC_SERVER; + (*session)->ctx = server_opts.ctx; + (*session)->flags = NC_SESSION_SHAREDCTX; + (*session)->host = lydict_insert(server_opts.ctx, endpt->address, 0); + (*session)->port = endpt->port; + + /* transport lock */ + pthread_mutex_init((*session)->ti_lock, NULL); + pthread_cond_init((*session)->ti_cond, NULL); + *(*session)->ti_inuse = 0; + + /* sock gets assigned to session or closed */ +#ifdef NC_ENABLED_SSH + if (client->ti == NC_TI_LIBSSH) { + (*session)->data = client->opts.ssh; + ret = nc_accept_ssh_session(*session, sock, NC_TRANSPORT_TIMEOUT); + (*session)->data = NULL; + + if (ret < 0) { + msgtype = NC_MSG_ERROR; + goto fail; + } else if (!ret) { + msgtype = NC_MSG_WOULDBLOCK; + goto fail; + } + } else +#endif +#ifdef NC_ENABLED_TLS + if (client->ti == NC_TI_OPENSSL) { + (*session)->data = client->opts.tls; + ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); + (*session)->data = NULL; + + if (ret < 0) { + msgtype = NC_MSG_ERROR; + goto fail; + } else if (!ret) { + msgtype = NC_MSG_WOULDBLOCK; + goto fail; + } + } else +#endif + { + ERRINT; + close(sock); + msgtype = NC_MSG_ERROR; + goto fail; + } + + /* assign new SID atomically */ + /* LOCK */ + pthread_spin_lock(&server_opts.sid_lock); + (*session)->id = server_opts.new_session_id++; + /* UNLOCK */ + pthread_spin_unlock(&server_opts.sid_lock); + + /* NETCONF handshake */ + msgtype = nc_handshake(*session); + if (msgtype != NC_MSG_HELLO) { + goto fail; + } + (*session)->opts.server.session_start = (*session)->opts.server.last_rpc = time(NULL); + (*session)->status = NC_STATUS_RUNNING; + + return msgtype; + +fail: + nc_session_free(*session, NULL); + *session = NULL; + return msgtype; +} + +struct nc_ch_client_thread_arg { + char *client_name; + void (*session_clb)(const char *client_name, struct nc_session *new_session); +}; + +static struct nc_ch_client * +nc_server_ch_client_with_endpt_lock(const char *name) +{ + struct nc_ch_client *client; + + while (1) { + /* LOCK */ + client = nc_server_ch_client_lock(name, 0, NULL); + if (!client) { + return NULL; + } + if (client->ch_endpt_count) { + return client; + } + /* no endpoints defined yet */ + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + usleep(NC_CH_NO_ENDPT_WAIT * 1000); + } + + return NULL; +} + +static int +nc_server_ch_client_thread_session_cond_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data) +{ + int ret; + uint32_t idle_timeout; + struct timespec ts; + struct nc_ch_client *client; + + /* session created, initialize condition */ + session->opts.server.ch_lock = malloc(sizeof *session->opts.server.ch_lock); + session->opts.server.ch_cond = malloc(sizeof *session->opts.server.ch_cond); + if (!session->opts.server.ch_lock || !session->opts.server.ch_cond) { + ERRMEM; + nc_session_free(session, NULL); + return -1; + } + pthread_mutex_init(session->opts.server.ch_lock, NULL); + pthread_cond_init(session->opts.server.ch_cond, NULL); + + session->flags |= NC_SESSION_CALLHOME; + + /* CH LOCK */ + pthread_mutex_lock(session->opts.server.ch_lock); + + /* give the session to the user */ + data->session_clb(data->client_name, session); + + do { + nc_gettimespec(&ts); + nc_addtimespec(&ts, NC_CH_NO_ENDPT_WAIT); + + ret = pthread_cond_timedwait(session->opts.server.ch_cond, session->opts.server.ch_lock, &ts); + if (ret && (ret != ETIMEDOUT)) { + ERR("Pthread condition timedwait failed (%s).", strerror(ret)); + goto ch_client_remove; + } + + /* check whether the client was not removed */ + /* LOCK */ + client = nc_server_ch_client_lock(data->client_name, 0, NULL); + if (!client) { + /* client was removed, finish thread */ + VRB("Call Home client \"%s\" removed, but an established session will not be terminated.", + data->client_name); + goto ch_client_remove; + } + + if (client->conn_type == NC_CH_PERSIST) { + /* TODO keep-alives */ + idle_timeout = client->conn.persist.idle_timeout; + } else { + idle_timeout = client->conn.period.idle_timeout; + } + + if (!session->opts.server.ntf_status && idle_timeout && (ts.tv_sec >= session->opts.server.last_rpc + idle_timeout)) { + VRB("Call Home client \"%s\" session %u: session idle timeout elapsed.", client->name, session->id); + session->status = NC_STATUS_INVALID; + session->term_reason = NC_SESSION_TERM_TIMEOUT; + } + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + } while (session->status == NC_STATUS_RUNNING); + + /* CH UNLOCK */ + pthread_mutex_unlock(session->opts.server.ch_lock); + + return 0; + +ch_client_remove: + /* make the session a standard one */ + pthread_cond_destroy(session->opts.server.ch_cond); + free(session->opts.server.ch_cond); + session->opts.server.ch_cond = NULL; + + session->flags &= ~NC_SESSION_CALLHOME; + + /* CH UNLOCK */ + pthread_mutex_unlock(session->opts.server.ch_lock); + + pthread_mutex_destroy(session->opts.server.ch_lock); + free(session->opts.server.ch_lock); + session->opts.server.ch_lock = NULL; + + return 1; +} + +static void * +nc_ch_client_thread(void *arg) +{ + struct nc_ch_client_thread_arg *data = (struct nc_ch_client_thread_arg *)arg; + NC_MSG_TYPE msgtype; + uint8_t cur_attempts = 0; + uint16_t i; + char *cur_endpt_name = NULL; + struct nc_ch_endpt *cur_endpt; + struct nc_session *session; + struct nc_ch_client *client; + + /* LOCK */ + client = nc_server_ch_client_with_endpt_lock(data->client_name); + if (!client) { + goto cleanup; + } + + cur_endpt = &client->ch_endpts[0]; + cur_endpt_name = strdup(cur_endpt->name); + + VRB("Call Home client \"%s\" connecting...", data->client_name); + while (1) { + msgtype = nc_connect_ch_client_endpt(client, cur_endpt, &session); + + if (msgtype == NC_MSG_HELLO) { + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + VRB("Call Home client \"%s\" session %u established.", data->client_name, session->id); + if (nc_server_ch_client_thread_session_cond_wait(session, data)) { + goto cleanup; + } + VRB("Call Home client \"%s\" session terminated, reconnecting...", client->name); + + /* LOCK */ + client = nc_server_ch_client_with_endpt_lock(data->client_name); + if (!client) { + goto cleanup; + } + + /* session changed status -> it was disconnected for whatever reason, + * persistent connection immediately tries to reconnect, periodic waits some first */ + if (client->conn_type == NC_CH_PERIOD) { + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + /* TODO wake up sometimes to check for new notifications */ + usleep(client->conn.period.reconnect_timeout * 60 * 1000000); + + /* LOCK */ + client = nc_server_ch_client_with_endpt_lock(data->client_name); + if (!client) { + goto cleanup; + } + } + + /* set next endpoint to try */ + if (client->start_with == NC_CH_FIRST_LISTED) { + cur_endpt = &client->ch_endpts[0]; + free(cur_endpt_name); + cur_endpt_name = strdup(cur_endpt->name); + } /* else we keep the current one */ + } else { + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + /* session was not created */ + usleep(NC_CH_ENDPT_FAIL_WAIT * 1000); + + /* LOCK */ + client = nc_server_ch_client_with_endpt_lock(data->client_name); + if (!client) { + goto cleanup; + } + + ++cur_attempts; + if (cur_attempts == client->max_attempts) { + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, cur_endpt_name)) { + break; + } + } + if (i < client->ch_endpt_count - 1) { + /* just go to the next endpoint */ + cur_endpt = &client->ch_endpts[i + 1]; + free(cur_endpt_name); + cur_endpt_name = strdup(cur_endpt->name); + } else { + /* cur_endpoint was removed or is the last, either way start with the first one */ + cur_endpt = &client->ch_endpts[0]; + free(cur_endpt_name); + cur_endpt_name = strdup(cur_endpt->name); + } + + cur_attempts = 0; + } /* else we keep the current one */ + } + } + +cleanup: + VRB("Call Home client \"%s\" thread exit.", data->client_name); + free(cur_endpt_name); + free(data->client_name); + free(data); + return NULL; +} + +API int +nc_connect_ch_client_dispatch(const char *client_name, + void (*session_clb)(const char *client_name, struct nc_session *new_session)) { + int ret; + pthread_t tid; + struct nc_ch_client_thread_arg *arg; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } else if (!session_clb) { + ERRARG("session_clb"); + return -1; + } + + arg = malloc(sizeof *arg); + if (!arg) { + ERRMEM; + return -1; + } + arg->client_name = strdup(client_name); + if (!arg->client_name) { + ERRMEM; + free(arg); + return -1; + } + arg->session_clb = session_clb; + + ret = pthread_create(&tid, NULL, nc_ch_client_thread, arg); + if (ret) { + ERR("Creating a new thread failed (%s).", strerror(ret)); + free(arg->client_name); + free(arg); + return -1; + } + /* the thread now manages arg */ + + pthread_detach(tid); + + return 0; +} + +#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ + +API int +nc_server_endpt_count(void) +{ + return server_opts.endpt_count; +} API time_t nc_session_get_start_time(const struct nc_session *session) { - if (!session) { + if (!session || (session->side != NC_SERVER)) { + ERRARG("session"); + return 0; + } + + return session->opts.server.session_start; +} + +API void +nc_session_set_notif_status(struct nc_session *session, int notif_status) +{ + if (!session || (session->side != NC_SERVER)) { + ERRARG("session"); + return; + } + + session->opts.server.ntf_status = (notif_status ? 1 : 0); +} + +API int +nc_session_get_notif_status(const struct nc_session *session) +{ + if (!session || (session->side != NC_SERVER)) { ERRARG("session"); return 0; } - return session->session_start; + return session->opts.server.ntf_status; } diff --git a/src/session_server.h b/src/session_server.h index dc0e5566..2f56495f 100644 --- a/src/session_server.h +++ b/src/session_server.h @@ -18,6 +18,10 @@ #include #include +#ifdef NC_ENABLED_TLS +# include +#endif + #include "session.h" #include "netconf.h" @@ -105,24 +109,16 @@ int nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported); void nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported); /** - * @brief Set the interleave capability. - * - * For the capability to be actually advertised, the server context must also - * include the nc-notifications model. + * @brief Set capability of the server. * - * Changing this option has the same ill effects as changing capabilities while - * sessions are already established. + * Capability can be used when some behavior or extension of the server is not defined + * as a YANG module. The provided value will be advertised in the server's \ + * messages. Note, that libnetconf only checks that the provided value is non-empty + * string. * - * @param[in] interleave_support 1 to suport interleave, 0 to not. + * @param[in] value Capability string to be advertised in server's \ messages. */ -void nc_server_set_capab_interleave(int interleave_support); - -/** - * @brief Get the interleave capability state. - * - * @return 1 for supported, 0 for not supported. - */ -int nc_server_get_capab_interleave(void); +int nc_server_set_capability(const char *value); /** * @brief Set server timeout for receiving a hello message. @@ -220,21 +216,33 @@ int nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session); */ int nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session); +/** + * @brief Get a session from a pollsession structure matching the session ID. + * + * @param[in] ps Pollsession structure to read from. + * @param[in] sid Session ID of the session. + * @return Matching session or NULL on not found. + */ +struct nc_session *nc_ps_get_session_by_sid(const struct nc_pollsession *ps, uint32_t sid); + /** * @brief Learn the number of sessions in a pollsession structure. * + * Does not lock \p ps structure for efficiency. + * * @param[in] ps Pollsession structure to check. * @return Number of sessions (even invalid ones) in \p ps, -1 on error. */ uint16_t nc_ps_session_count(struct nc_pollsession *ps); -#define NC_PSPOLL_TIMEOUT 0x0001 /**< Timeout elapsed. */ -#define NC_PSPOLL_RPC 0x0002 /**< RPC was correctly parsed and processed. */ -#define NC_PSPOLL_BAD_RPC 0x0004 /**< RPC was received, but failed to be parsed. */ -#define NC_PSPOLL_REPLY_ERROR 0x0008 /**< Response to the RPC was a \ of type error. */ -#define NC_PSPOLL_SESSION_TERM 0x0010 /**< Some session was terminated. */ -#define NC_PSPOLL_SESSION_ERROR 0x0020 /**< Some session was terminated incorrectly (not by a \ or \ RPC). */ -#define NC_PSPOLL_ERROR 0x0040 /**< Other fatal errors (they are printed). */ +#define NC_PSPOLL_NOSESSIONS 0x0001 /**< No sessions to poll. */ +#define NC_PSPOLL_TIMEOUT 0x0002 /**< Timeout elapsed. */ +#define NC_PSPOLL_RPC 0x0004 /**< RPC was correctly parsed and processed. */ +#define NC_PSPOLL_BAD_RPC 0x0008 /**< RPC was received, but failed to be parsed. */ +#define NC_PSPOLL_REPLY_ERROR 0x0010 /**< Response to the RPC was a \ of type error. */ +#define NC_PSPOLL_SESSION_TERM 0x0020 /**< Some session was terminated. */ +#define NC_PSPOLL_SESSION_ERROR 0x0040 /**< Some session was terminated incorrectly (not by a \ or \ RPC). */ +#define NC_PSPOLL_ERROR 0x0080 /**< Other fatal errors (they are printed). */ #ifdef NC_ENABLED_SSH # define NC_PSPOLL_SSH_MSG 0x0080 /**< SSH message received (and processed, if relevant, only with SSH support). */ @@ -252,7 +260,7 @@ uint16_t nc_ps_session_count(struct nc_pollsession *ps); * infinite waiting. * @param[in] session Session that was processed and that specific return bits concern. * Can be NULL. - * @return Bitfield of NC_PSPOLL_* macros, almost any combination can be returned. + * @return Bitfield of NC_PSPOLL_* macros. */ int nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session); @@ -260,7 +268,7 @@ int nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **sessi * @brief Remove sessions from a pollsession structure and * call nc_session_free() on them. * - * Calling this function with \p all false makes sense if nc_ps_poll() returned 3. + * Calling this function with \p all false makes sense if nc_ps_poll() returned #NC_PSPOLL_SESSION_TERM. * * @param[in] ps Pollsession structure to clear. * @param[in] all Whether to free all sessions, or only the invalid ones. @@ -274,24 +282,63 @@ void nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *)); * @brief Add a new endpoint. * * Before the endpoint can accept any connections, its address and port must - * be set on at least one transport protocol. + * be set. * * @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); +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); +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 + * even though it is not, in fact, listening. + * + * @return Number of added listening endpoints. + */ +int nc_server_endpt_count(void); + +/** + * @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); + +/** + * @brief Change endpoint listening port. + * + * 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); /** * @brief Accept new sessions on all the listening endpoints. * + * Once a new (TCP/IP) conection is established a different (quite long) timeout + * 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. + * * @param[in] timeout Timeout for receiving a new connection in milliseconds, 0 for * non-blocking call, -1 for infinite waiting. * @param[out] session New session. @@ -306,7 +353,7 @@ NC_MSG_TYPE nc_accept(int timeout, struct nc_session **session); /** * @brief Accept a new NETCONF session on an SSH session of a running NETCONF \p orig_session. - * Call this function only when nc_ps_poll() returns NC_PSPOLL_SSH_CHANNEL on \p orig_session. + * Call this function only when nc_ps_poll() returns #NC_PSPOLL_SSH_CHANNEL on \p orig_session. * * @param[in] orig_session Session that has a new SSH channel ready. * @param[out] session New session. @@ -317,7 +364,7 @@ NC_MSG_TYPE nc_session_accept_ssh_channel(struct nc_session *orig_session, struc /** * @brief Accept a new NETCONF session on an SSH session of a running NETCONF session - * that was polled in \p ps. Call this function only when nc_ps_poll() on \p ps returns NC_PSPOLL_SSH_CHANNEL. + * that was polled in \p ps. Call this function only when nc_ps_poll() on \p ps returns #NC_PSPOLL_SSH_CHANNEL. * The new session is only returned in \p session, it is not added to \p ps. * * @param[in] ps Unmodified pollsession structure from the previous nc_ps_poll() call. @@ -328,46 +375,96 @@ NC_MSG_TYPE nc_session_accept_ssh_channel(struct nc_session *orig_session, struc NC_MSG_TYPE nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session); /** - * @brief Change SSH endpoint listening address. + * @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. * - * On error the previous listening socket (if any) is left untouched. + * @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. + */ +int nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username); + +/** + * @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. * - * @param[in] endpt_name Existing endpoint name. - * @param[in] address New listening address. + * @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. */ -int nc_server_ssh_endpt_set_address(const char *endpt_name, const char *address); +int nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username); /** - * @brief Change SSH endpoint listening port. + * @brief Remove an authorized client SSH public key. * - * On error the previous listening socket (if any) is left untouched. + * @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 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] endpt_name Existing endpoint name. - * @param[in] port New listening port. + * @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_endpt_set_port(const char *endpt_name, uint16_t port); +int nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx); /** - * @brief Add endpoint SSH host keys the server will identify itself with. 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. + * @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. * - * @param[in] endpt_name Existing endpoint name. - * @param[in] privkey_path Path to a private key. - * @return 0 on success, -1 on error. + * @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_data_rsa flag whether \p privkey_data are the data of an RSA (1) or a DSA (0) key. + * @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. */ -int nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *privkey_path); +void nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, + char **privkey_data, int *privkey_data_rsa), + void *user_data, void (*free_user_data)(void *user_data)); /** - * @brief Delete endpoint SSH host keys. Their order is preserved. + * @brief Delete endpoint SSH host key. Their order is preserved. * * @param[in] endpt_name Existing endpoint name. - * @param[in] privkey_path Path to a private key. NULL matches all the keys. + * @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 *privkey_path); +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 SSH banner the server will send to every client. @@ -406,119 +503,73 @@ int nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_ */ int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout); -/** - * @brief Add an endpoint authorized client SSH public key. This public key can be used for - * publickey authentication afterwards. - * - * @param[in] endpt_name Existing endpoint name. - * @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. - */ -int nc_server_ssh_endpt_add_authkey(const char *endpt_name, const char *pubkey_path, const char *username); - -/** - * @brief Remove an endpoint authorized client SSH public key. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys. - * @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_endpt_del_authkey(const char *endpt_name, const char *pubkey_path, const char *username); - #endif /* NC_ENABLED_SSH */ #ifdef NC_ENABLED_TLS /** - * @brief Change TLS 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_tls_endpt_set_address(const char *endpt_name, const char *address); - -/** - * @brief Change TLS endpoint listening port. - * - * 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_tls_endpt_set_port(const char *endpt_name, uint16_t port); - -/** - * @brief Set server TLS certificate. Alternative to nc_tls_server_set_cert_path(). - * There can only be one certificate for each key type, it is replaced if - * already set. + * @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] cert Base64-encoded certificate in ASN.1 DER encoding. + * @param[in] name Arbitrary certificate name. * @return 0 on success, -1 on error. */ -int nc_server_tls_endpt_set_cert(const char *endpt_name, const char *cert); +int nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name); /** - * @brief Set server TLS certificate. Alternative to nc_tls_server_set_cert(). - * There can only be one certificate for each key type, it is replaced if - * already set. + * @brief Set the callback for retrieving server certificate and matching private key. * - * @param[in] endpt_name Existing endpoint name. - * @param[in] cert_path Path to a certificate file in PEM format. - * @return 0 on success, -1 on error. + * @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_data_rsa flag whether \p privkey_data are the data of an RSA (1) or a DSA (0) key. + * @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. */ -int nc_server_tls_endpt_set_cert_path(const char *endpt_name, const char *cert_path); +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, int *privkey_data_rsa), + void *user_data, void (*free_user_data)(void *user_data)); /** - * @brief Set server TLS private key matching the certificate. - * Alternative to nc_tls_server_set_key_path(). There can only be one of - * every key type, it is replaced if already set. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] privkey Base64-encoded certificate in ASN.1 DER encoding. - * @param[in] is_rsa Whether \p privkey are the data of an RSA (1) or DSA (0) key. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_key(const char *endpt_name, const char *privkey, int is_rsa); - -/** - * @brief Set server TLS private key matching the certificate. - * Alternative to nc_tls_server_set_key_path(). There can only be one of - * every key type, it is replaced if already set. + * @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] privkey_path Path to a private key file in PEM format. + * @param[in] name Arbitary name identifying this certificate list. * @return 0 on success, -1 on error. */ -int nc_server_tls_endpt_set_key_path(const char *endpt_name, const char *privkey_path); +int nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name); /** - * @brief Add a trusted certificate. Can be both a CA or a client one. Can be - * safely used together with nc_server_tls_endpt_set_trusted_ca_paths(). + * @brief Set the callback for retrieving trusted certificates. * - * @param[in] endpt_name Existing endpoint name. - * @param[in] cert_name Arbitary name identifying this certificate. - * @param[in] cert Base64-enocded certificate in ASN.1 DER encoding. - * @return 0 on success, -1 on error. + * @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. */ -int nc_server_tls_endpt_add_trusted_cert(const char *endpt_name, const char *cert_name, const char *cert); +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 Add a trusted certificate. Can be both a CA or a client one. Can be - * safely used together with nc_server_tls_endpt_set_trusted_ca_paths(). + * @brief Remove a trusted certificate. * * @param[in] endpt_name Existing endpoint name. - * @param[in] cert_name Arbitary name identifying this certificate. - * @param[in] cert_path Path to a trusted certificate file in PEM format. - * @return 0 on success, -1 on error. + * @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_add_trusted_cert_path(const char *endpt_name, const char *cert_name, const char *cert_path); +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 @@ -533,16 +584,6 @@ int nc_server_tls_endpt_add_trusted_cert_path(const char *endpt_name, const char */ int nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_file, const char *ca_dir); -/** - * @brief Destroy and clean all the set certificates and private keys. CRLs and - * CTN entries are not affected. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] cert_name Name of the certificate to delete. NULL deletes all the certificates. - * @return 0 on success, -1 on not found. - */ -int nc_server_tls_endpt_del_trusted_cert(const char *endpt_name, const char *cert_name); - /** * @brief Set Certificate Revocation List locations. There can only be one file * and one directory, they are replaced if already set. @@ -564,20 +605,24 @@ int nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_fi void nc_server_tls_endpt_clear_crls(const char *endpt_name); /** - * @brief Add a Cert-to-name entry. + * @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. - * @param[in] fingerprint Matching certificate fingerprint. - * @param[in] map_type Type of username-certificate mapping. - * @param[in] name Specific username if \p map_type == NC_TLS_CTN_SPECIFED. Must be NULL otherwise. + * @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. + * @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. @@ -589,6 +634,42 @@ int nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char 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); + +/** + * @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. + * + * @param[in] verify_clb Additional user verify callback. + */ +void nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *session)); + #endif /* NC_ENABLED_TLS */ /** @@ -599,4 +680,23 @@ int nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char * */ time_t nc_session_get_start_time(const struct nc_session *session); +/** + * @brief Set session notification subscription flag. + * + * It is used only to ignore timeouts, because they are + * ignored for sessions with active subscriptions. + * + * @param[in] session Session to modify. + * @param[in] notif_status 0 for no active subscriptions, non-zero for an active subscription. + */ +void nc_session_set_notif_status(struct nc_session *session, int notif_status); + +/** + * @brief Get session notification subscription flag. + * + * @param[in] session Session to get the information from. + * @return 0 for no active subscription, non-zero for an active subscription. + */ +int nc_session_get_notif_status(const struct nc_session *session); + #endif /* NC_SESSION_SERVER_H_ */ diff --git a/src/session_server_ch.h b/src/session_server_ch.h index 40ac0d4f..2bb369c0 100644 --- a/src/session_server_ch.h +++ b/src/session_server_ch.h @@ -21,171 +21,272 @@ #include "session.h" #include "netconf.h" -#ifdef NC_ENABLED_SSH +#if defined(NC_ENABLED_SSH) || defined(NC_ENABLED_TLS) /** - * @brief Establish an SSH Call Home connection with a listening NETCONF client. + * @brief Add a new Call Home client. * - * @param[in] host Host the client is listening on. - * @param[in] port Port the client is listening on. - * @param[out] session New Call Home session. - * @return NC_MSG_HELLO on success, NC_MSG_BAD_HELLO on client \ message - * parsing fail, NC_MSG_WOULDBLOCK on timeout, NC_MSG_ERROR on other errors. + * @param[in] name Arbitrary unique client name. + * @param[in] ti Transport protocol to use. + * @return 0 on success, -1 on error. */ -NC_MSG_TYPE nc_connect_callhome_ssh(const char *host, uint16_t port, struct nc_session **session); +int nc_server_ch_add_client(const char *name, NC_TRANSPORT_IMPL ti); /** - * @brief Add Call Home SSH host keys the server will identify itself with. 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 earlier ones. + * @brief Drop any connections, stop connecting and remove a client. * - * @param[in] privkey_path Path to a private key. + * @param[in] name Client name. NULL matches all the clients. + * @param[in] ti Client transport protocol. NULL matches any protocol. + * Redundant to set if \p name is set, client names are + * unique disregarding their protocol. + * @return 0 on success, -1 on not finding any match. + */ +int nc_server_ch_del_client(const char *name, NC_TRANSPORT_IMPL ti); + +/** + * @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. * @return 0 on success, -1 on error. */ -int nc_server_ssh_ch_add_hostkey(const char *privkey_path); +int nc_server_ch_client_add_endpt(const char *client_name, const char *endpt_name); /** - * @brief Delete Call Home SSH host keys. Their order is preserved. + * @brief Remove a Call Home client endpoint. * - * @param[in] privkey_path Path to a private key. NULL matches all the keys. + * @param[in] client_name Existing client name. + * @param[in] endpt_name Existing endpoint of \p client_name. NULL matches all endpoints. * @return 0 on success, -1 on error. */ -int nc_server_ssh_ch_del_hostkey(const char *privkey_path); +int nc_server_ch_client_del_endpt(const char *client_name, const char *endpt_name); /** - * @brief Set Call Home SSH banner the server will send to every client. + * @brief Change Call Home client endpoint listening address. * - * @param[in] banner SSH banner. + * 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. */ -int nc_server_ssh_ch_set_banner(const char *banner); +int nc_server_ch_client_endpt_set_address(const char *client_name, const char *endpt_name, const char *address); /** - * @brief Set accepted Call Home SSH authentication methods. All (publickey, password, interactive) - * are supported by default. + * @brief Change Call Home client endpoint listening port. * - * @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE. + * 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. */ -int nc_server_ssh_ch_set_auth_methods(int auth_methods); +int nc_server_ch_client_endpt_set_port(const char *client_name, const char *endpt_name, uint16_t port); /** - * @brief Set Call Home SSH authentication attempts of every client. 3 by default. + * @brief Set Call Home client connection type. * - * @param[in] auth_attempts Failed authentication attempts before a client is dropped. + * @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_ssh_ch_set_auth_attempts(uint16_t auth_attempts); +int nc_server_ch_client_set_conn_type(const char *client_name, NC_CH_CONN_TYPE conn_type); /** - * @brief Set Call Home SSH authentication timeout. 10 seconds by default. + * @brief Set Call Home client persistent connection idle timeout. * - * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped. + * @param[in] client_name Existing Call Home client name. + * @param[in] idle_timeout Call Home persistent idle timeout. * @return 0 on success, -1 on error. */ -int nc_server_ssh_ch_set_auth_timeout(uint16_t auth_timeout); +int nc_server_ch_client_persist_set_idle_timeout(const char *client_name, uint32_t idle_timeout); /** - * @brief Add an authorized Call Home client SSH public key. This public key can be used for - * publickey authentication afterwards. + * @brief Set Call Home client persistent connection keep-alive max wait time. * - * @param[in] pubkey_path Path to the public key. - * @param[in] username Username that the client with the public key must use. + * @param[in] client_name Existing Call Home client name. + * @param[in] max_wait Call Home persistent max wait time for keep-alive reply. * @return 0 on success, -1 on error. */ -int nc_server_ssh_ch_add_authkey(const char *pubkey_path, const char *username); +int nc_server_ch_client_persist_set_keep_alive_max_wait(const char *client_name, uint16_t max_wait); /** - * @brief Remove an authorized Call Home client SSH public key. + * @brief Set Call Home client persistent connection keep-alive max attempts. * - * @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys. - * @param[in] username Username for an authorized public key. NULL matches all the usernames. - * @return 0 on success, -1 on not finding any match. + * @param[in] client_name Existing Call Home client name. + * @param[in] max_attempts Call Home persistent keep-alive maximum contact attempts. + * @return 0 on success, -1 on error. + */ +int nc_server_ch_client_persist_set_keep_alive_max_attempts(const char *client_name, uint8_t max_attempts); + +/** + * @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_ssh_ch_del_authkey(const char *pubkey_path, const char *username); +int nc_server_ch_client_period_set_idle_timeout(const char *client_name, uint16_t idle_timeout); /** - * @brief Clear all the SSH Call Home options. Afterwards a new set of options - * can be set for the next client to connect to. + * @brief Set Call Home client periodic reconnect timeout. + * + * @param[in] client_name Existing Call Home client name. + * @param[in] reconnect_timeout Call Home periodic reconnect timeout. + * @return 0 on success, -1 on error. */ -void nc_server_ssh_ch_clear_opts(void); +int nc_server_ch_client_period_set_reconnect_timeout(const char *client_name, uint16_t reconnect_timeout); -#endif /* NC_ENABLED_SSH */ +/** + * @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); -#ifdef NC_ENABLED_TLS +/** + * @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. + * + * @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. + * @return 0 if the thread was successfully created, -1 on error. + */ +int nc_connect_ch_client_dispatch(const char *client_name, + void (*session_clb)(const char *client_name, struct nc_session *new_session)); + +#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ + +#ifdef NC_ENABLED_SSH + +/** + * @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] 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_add_hostkey(const char *client_name, const char *name, int16_t idx); + +/** + * @brief Delete Call Home SSH host keys. Their order is preserved. + * + * @param[in] client_name Existing Call Home client 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_ch_client_del_hostkey(const char *client_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] 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_mov_hostkey(const char *client_name, const char *key_mov, const char *key_after); /** - * @brief Establish a TLS Call Home connection with a listening NETCONF client. + * @brief Modify Call Home SSH host key. * - * @param[in] host Host the client is listening on. - * @param[in] port Port the client is listening on. - * @param[out] session New Call Home session. - * @return NC_MSG_HELLO on success, NC_MSG_BAD_HELLO on client \ message - * parsing fail, NC_MSG_WOULDBLOCK on timeout, NC_MSG_ERROR on other errors. + * @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. */ -NC_MSG_TYPE nc_connect_callhome_tls(const char *host, uint16_t port, struct nc_session **session); +int nc_server_ssh_ch_client_mod_hostkey(const char *endpt_name, const char *name, const char *new_name); /** - * @brief Set server Call Home TLS certificate. Alternative to nc_tls_server_set_cert_path(). - * There can only be one certificate for each key type, it is replaced if already set. + * @brief Set Call Home SSH banner the server will send to every client. * - * @param[in] cert Base64-encoded certificate in ASN.1 DER encoding. + * @param[in] client_name Existing Call Home client name. + * @param[in] banner SSH banner. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_set_cert(const char *cert); +int nc_server_ssh_ch_client_set_banner(const char *client_name, const char *banner); /** - * @brief Set server Call Home TLS certificate. Alternative to nc_tls_server_set_cert(). - * There can only be one certificate for each key type, it is replaced if already set. + * @brief Set accepted Call Home SSH authentication methods. All (publickey, password, interactive) + * are supported by default. * - * @param[in] cert_path Path to a certificate file in PEM format. + * @param[in] client_name Existing Call Home client 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_tls_ch_set_cert_path(const char *cert_path); +int nc_server_ssh_ch_client_set_auth_methods(const char *client_name, int auth_methods); /** - * @brief Set server Call Home TLS private key matching the certificate. - * Alternative to nc_tls_server_set_key_path(). There can only be one of every key - * type, it is replaced if already set. + * @brief Set Call Home SSH authentication attempts of every client. 3 by default. * - * @param[in] privkey Base64-encoded certificate in ASN.1 DER encoding. - * @param[in] is_rsa Whether \p privkey are the data of an RSA (1) or DSA (0) key. + * @param[in] client_name Existing Call Home client name. + * @param[in] auth_attempts Failed authentication attempts before a client is dropped. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_set_key(const char *privkey, int is_rsa); +int nc_server_ssh_ch_client_set_auth_attempts(const char *client_name, uint16_t auth_attempts); /** - * @brief Set server Call Home TLS private key matching the certificate. - * Alternative to nc_tls_server_set_key_path(). There can only be one of every key - * type, it is replaced if already set. + * @brief Set Call Home SSH authentication timeout. 10 seconds by default. * - * @param[in] privkey_path Path to a private key file in PEM format. + * @param[in] client_name Existing Call Home client name. + * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_set_key_path(const char *privkey_path); +int nc_server_ssh_ch_client_set_auth_timeout(const char *client_name, uint16_t auth_timeout); + +#endif /* NC_ENABLED_SSH */ + +#ifdef NC_ENABLED_TLS /** - * @brief Add a Call Home trusted certificate. Can be both a CA or a client one. + * @brief Set the server Call Home TLS certificate. Only the name is set, the certificate itself + * wil be retrieved using a callback. * - * @param[in] cert_name Arbitary name identifying this certificate. - * @param[in] cert Base64-enocded certificate in ASN.1 DER encoding. + * @param[in] client_name Existing Call Home client name. + * @param[in] name Arbitrary certificate name. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_add_trusted_cert(const char *cert_name, const char *cert); +int nc_server_tls_ch_client_set_server_cert(const char *client_name, const char *name); /** - * @brief Add a Call Home trusted certificate. Can be both a CA or a client one. + * @brief Add a Call Home trusted certificate list. Can be both a CA or a client one. * - * @param[in] cert_name Arbitary name identifying this certificate. - * @param[in] cert_path Path to a trusted certificate file in PEM format. + * @param[in] client_name Existing Call Home client name. + * @param[in] name Arbitary name identifying this certificate list. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_add_trusted_cert_path(const char *cert_name, const char *cert_path); +int nc_server_tls_ch_client_add_trusted_cert_list(const char *client_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] 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_del_trusted_cert_list(const char *client_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] 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 @@ -193,61 +294,74 @@ int nc_server_tls_ch_add_trusted_cert_path(const char *cert_name, const char *ce * with PEM files. Can be NULL. * @return 0 on success, -1 on error. */ -int nc_server_tls_ch_set_trusted_ca_paths(const char *ca_file, const char *ca_dir); - -/** - * @brief Destroy and clean all the set Call Home certificates and private keys. - * CRLs and CTN entries are not affected. - * - * @param[in] cert_name Name of the certificate to delete. NULL deletes all the certificates. - * @return 0 on success, -1 on not found. - */ -int nc_server_tls_ch_del_trusted_cert(const char *cert_name); +int nc_server_tls_ch_client_set_trusted_ca_paths(const char *client_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] 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_set_crl_paths(const char *crl_file, const char *crl_dir); +int nc_server_tls_ch_client_set_crl_paths(const char *client_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. */ -void nc_server_tls_ch_clear_crls(void); +void nc_server_tls_ch_client_clear_crls(const char *client_name); /** - * @brief Add a Call Home Cert-to-name entry. + * @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] id Priority of the entry. - * @param[in] fingerprint Matching certificate fingerprint. - * @param[in] map_type Type of username-certificate mapping. - * @param[in] name Specific username if \p map_type == NC_TLS_CTN_SPECIFED. Must be NULL otherwise. + * @param[in] client_name Existing Call Home client 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_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name); +int nc_server_tls_ch_client_add_ctn(const char *client_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. + * @brief Remove a Call Home cert-to-name entry. * + * @param[in] client_name Existing Call Home client 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_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name); +int nc_server_tls_ch_client_del_ctn(const char *client_name, int64_t id, const char *fingerprint, + NC_TLS_CTN_MAPTYPE map_type, const char *name); /** - * @brief Clear all the TLS Call Home options. Afterwards a new set of options - * can be set for the next client to connect. + * @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,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. */ -void nc_server_tls_ch_clear_opts(void); +int nc_server_tls_ch_client_get_ctn(const char *client_name, uint32_t *id, char **fingerprint, + NC_TLS_CTN_MAPTYPE *map_type, char **name); #endif /* NC_ENABLED_TLS */ diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index c0ea6548..86637e6e 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -3,7 +3,7 @@ * \author Michal Vasko * \brief libnetconf2 SSH server session manipulation functions * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2017 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,10 +13,12 @@ */ #define _GNU_SOURCE +#define _POSIX_SOURCE #include #include #include +#include #include #include #include @@ -26,37 +28,69 @@ #include "session_server_ch.h" #include "libnetconf.h" -struct nc_server_ssh_opts ssh_ch_opts = { - .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE, - .auth_attempts = 3, - .auth_timeout = 10 -}; -pthread_mutex_t ssh_ch_opts_lock = PTHREAD_MUTEX_INITIALIZER; extern struct nc_server_opts server_opts; -API int -nc_server_ssh_endpt_set_address(const char *endpt_name, const char *address) +static char * +base64der_key_to_tmp_file(const char *in, int rsa) { - return nc_server_endpt_set_address_port(endpt_name, address, 0, NC_TI_LIBSSH); -} + char path[12] = "/tmp/XXXXXX"; + int fd, written; + mode_t umode; + FILE *file; -API int -nc_server_ssh_endpt_set_port(const char *endpt_name, uint16_t port) -{ - return nc_server_endpt_set_address_port(endpt_name, NULL, port, NC_TI_LIBSSH); + if (in == NULL) { + return NULL; + } + + umode = umask(0600); + fd = mkstemp(path); + umask(umode); + if (fd == -1) { + return NULL; + } + + file = fdopen(fd, "r"); + if (!file) { + close(fd); + return NULL; + } + + /* write the key into the file */ + written = fwrite("-----BEGIN ", 1, 11, file); + written += fwrite((rsa ? "RSA" : "DSA"), 1, 3, file); + written += fwrite(" PRIVATE KEY-----\n", 1, 18, file); + written += fwrite(in, 1, strlen(in), file); + written += fwrite("\n-----END ", 1, 10, file); + written += fwrite((rsa ? "RSA" : "DSA"), 1, 3, file); + written += fwrite(" PRIVATE KEY-----", 1, 17, file); + + fclose(file); + if ((unsigned)written != 62 + strlen(in)) { + unlink(path); + return NULL; + } + + return strdup(path); } static int -nc_server_ssh_add_hostkey(const char *privkey_path, struct nc_server_ssh_opts *opts) +nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts) { - if (!privkey_path) { - ERRARG("privkey_path"); + uint8_t i; + + if (!name) { + ERRARG("name"); + return -1; + } else if (idx > opts->hostkey_count) { + ERRARG("idx"); return -1; } - if (eaccess(privkey_path, R_OK)) { - ERR("Host key \"%s\" cannot be read (%s).", privkey_path, strerror(errno)); - return -1; + for (i = 0; i < opts->hostkey_count; ++i) { + if (!strcmp(opts->hostkeys[i], name)) { + ERRARG("name"); + return -1; + } } ++opts->hostkey_count; @@ -65,102 +99,295 @@ nc_server_ssh_add_hostkey(const char *privkey_path, struct nc_server_ssh_opts *o ERRMEM; return -1; } - opts->hostkeys[opts->hostkey_count - 1] = lydict_insert(server_opts.ctx, privkey_path, 0); + + if (idx < 0) { + idx = opts->hostkey_count - 1; + } + if (idx != opts->hostkey_count - 1) { + memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx); + } + opts->hostkeys[idx] = lydict_insert(server_opts.ctx, name, 0); return 0; } API int -nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *privkey_path) +nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx) { int ret; struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_add_hostkey(privkey_path, endpt->ssh_opts); + ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_ch_add_hostkey(const char *privkey_path) +nc_server_ssh_ch_client_add_hostkey(const char *client_name, const char *name, int16_t idx) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_add_hostkey(privkey_path, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_add_hostkey(name, idx, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } +API void +nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, + char **privkey_data, int *privkey_data_rsa), + void *user_data, void (*free_user_data)(void *user_data)) +{ + if (!hostkey_clb) { + ERRARG("hostkey_clb"); + return; + } + + server_opts.hostkey_clb = hostkey_clb; + server_opts.hostkey_data = user_data; + server_opts.hostkey_data_free = free_user_data; +} + static int -nc_server_ssh_del_hostkey(const char *privkey_path, struct nc_server_ssh_opts *opts) +nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts) { uint8_t i; - if (!privkey_path) { + if (name && (idx > -1)) { + ERRARG("name and idx"); + return -1; + } else if (idx >= opts->hostkey_count) { + ERRARG("idx"); + } + + if (!name && (idx < 0)) { for (i = 0; i < opts->hostkey_count; ++i) { lydict_remove(server_opts.ctx, opts->hostkeys[i]); } free(opts->hostkeys); opts->hostkeys = NULL; opts->hostkey_count = 0; - } else { + } else if (name) { for (i = 0; i < opts->hostkey_count; ++i) { - if (!strcmp(opts->hostkeys[i], privkey_path)) { - --opts->hostkey_count; - lydict_remove(server_opts.ctx, opts->hostkeys[i]); - if (i < opts->hostkey_count - 1) { - memmove(opts->hostkeys + i, opts->hostkeys + i + 1, (opts->hostkey_count - i) * sizeof *opts->hostkeys); - } - return 0; + if (!strcmp(opts->hostkeys[i], name)) { + idx = i; + goto remove_idx; } } - ERR("Host key \"%s\" not found.", privkey_path); + ERRARG("name"); + return -1; + } else { +remove_idx: + --opts->hostkey_count; + lydict_remove(server_opts.ctx, opts->hostkeys[idx]); + if (idx < opts->hostkey_count - 1) { + memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys); + } + if (!opts->hostkey_count) { + free(opts->hostkeys); + opts->hostkeys = NULL; + } + } + + return 0; +} + +API int +nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx) +{ + int ret; + struct nc_endpt *endpt; + + /* LOCK */ + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); + if (!endpt) { + return -1; + } + ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh); + /* UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); + + return ret; +} + +API int +nc_server_ssh_ch_client_del_hostkey(const char *client_name, const char *name, int16_t idx) +{ + int ret; + struct nc_ch_client *client; + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_del_hostkey(name, idx, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return ret; +} + +static int +nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts) +{ + uint8_t i; + int16_t mov_idx = -1, after_idx = -1; + const char *bckup; + + if (!key_mov) { + ERRARG("key_mov"); + return -1; + } + + for (i = 0; i < opts->hostkey_count; ++i) { + if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) { + after_idx = i; + } + if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) { + mov_idx = i; + } + + if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) { + break; + } + } + + if (key_after && (after_idx == -1)) { + ERRARG("key_after"); + return -1; + } + if (mov_idx == -1) { + ERRARG("key_mov"); return -1; } + if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) { + /* nothing to do */ + return 0; + } + + /* finally move the key */ + bckup = opts->hostkeys[mov_idx]; + if (mov_idx > after_idx) { + memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1, + ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys); + opts->hostkeys[after_idx + 1] = bckup; + } else { + memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys); + opts->hostkeys[after_idx] = bckup; + } return 0; } API int -nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *privkey_path) +nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after) { int ret; struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_del_hostkey(privkey_path, endpt->ssh_opts); + ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_ch_del_hostkey(const char *privkey_path) +nc_server_ssh_ch_client_mov_hostkey(const char *client_name, const char *key_mov, const char *key_after) { int ret; + struct nc_ch_client *client; + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_mov_hostkey(key_mov, key_after, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_del_hostkey(privkey_path, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + return ret; +} + +static int +nc_server_ssh_mod_hostkey(const char *name, const char *new_name, struct nc_server_ssh_opts *opts) +{ + uint8_t i; + + if (!name) { + ERRARG("name"); + return -1; + } else if (!new_name) { + ERRARG("new_name"); + return -1; + } + + for (i = 0; i < opts->hostkey_count; ++i) { + if (!strcmp(opts->hostkeys[i], name)) { + lydict_remove(server_opts.ctx, opts->hostkeys[i]); + opts->hostkeys[i] = lydict_insert(server_opts.ctx, new_name, 0); + return 0; + } + } + + ERRARG("name"); + return -1; +} + +API int +nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name) +{ + int ret; + struct nc_endpt *endpt; + + /* LOCK */ + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); + if (!endpt) { + return -1; + } + ret = nc_server_ssh_mov_hostkey(name, new_name, endpt->opts.ssh); + /* UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); + + return ret; +} + +API int +nc_server_ssh_ch_client_mod_hostkey(const char *client_name, const char *name, const char *new_name) +{ + int ret; + struct nc_ch_client *client; + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_mod_hostkey(name, new_name, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -187,27 +414,31 @@ nc_server_ssh_endpt_set_banner(const char *endpt_name, const char *banner) struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_set_banner(banner, endpt->ssh_opts); + ret = nc_server_ssh_set_banner(banner, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_ch_set_banner(const char *banner) +nc_server_ssh_ch_client_set_banner(const char *client_name, const char *banner) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_set_banner(banner, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_set_banner(banner, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -232,27 +463,31 @@ nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods) struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->ssh_opts); + ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_ch_set_auth_methods(int auth_methods) +nc_server_ssh_ch_client_set_auth_methods(const char *client_name, int auth_methods) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_set_auth_methods(auth_methods, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_set_auth_methods(auth_methods, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -276,27 +511,31 @@ nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_atte struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->ssh_opts); + ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_set_ch_auth_attempts(uint16_t auth_attempts) +nc_server_ssh_set_ch_client_auth_attempts(const char *client_name, uint16_t auth_attempts) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_set_auth_attempts(auth_attempts, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_set_auth_attempts(auth_attempts, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -320,115 +559,128 @@ nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeo struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); if (!endpt) { return -1; } - ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->ssh_opts); + ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_ssh_ch_set_auth_timeout(uint16_t auth_timeout) +nc_server_ssh_ch_client_set_auth_timeout(const char *client_name, uint16_t auth_timeout) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_set_auth_timeout(auth_timeout, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_LIBSSH, NULL); + if (!client) { + return -1; + } + ret = nc_server_ssh_set_auth_timeout(auth_timeout, client->opts.ssh); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } static int -nc_server_ssh_add_authkey(const char *pubkey_path, const char *username, struct nc_server_ssh_opts *opts) +_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, + const char *username) { - if (!pubkey_path) { - ERRARG("pubkey_path"); - return -1; - } else if (!username) { - ERRARG("username"); - return -1; - } + /* LOCK */ + pthread_mutex_lock(&server_opts.authkey_lock); - ++opts->authkey_count; - opts->authkeys = nc_realloc(opts->authkeys, opts->authkey_count * sizeof *opts->authkeys); - if (!opts->authkeys) { + ++server_opts.authkey_count; + server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys); + if (!server_opts.authkeys) { ERRMEM; return -1; } - opts->authkeys[opts->authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0); - opts->authkeys[opts->authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0); + server_opts.authkeys[server_opts.authkey_count - 1].path = lydict_insert(server_opts.ctx, pubkey_path, 0); + server_opts.authkeys[server_opts.authkey_count - 1].base64 = lydict_insert(server_opts.ctx, pubkey_base64, 0); + server_opts.authkeys[server_opts.authkey_count - 1].type = type; + server_opts.authkeys[server_opts.authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0); + + /* UNLOCK */ + pthread_mutex_unlock(&server_opts.authkey_lock); return 0; } API int -nc_server_ssh_endpt_add_authkey(const char *endpt_name, const char *pubkey_path, const char *username) +nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username) { - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { + if (!pubkey_path) { + ERRARG("pubkey_path"); + return -1; + } else if (!username) { + ERRARG("username"); return -1; } - ret = nc_server_ssh_add_authkey(pubkey_path, username, endpt->ssh_opts); - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - return ret; + return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username); } API int -nc_server_ssh_ch_add_authkey(const char *pubkey_path, const char *username) +nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username) { - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_add_authkey(pubkey_path, username, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + if (!pubkey_base64) { + ERRARG("pubkey_base64"); + return -1; + } else if (!type) { + ERRARG("type"); + return -1; + } else if (!username) { + ERRARG("username"); + return -1; + } - return ret; + return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username); } -static int -nc_server_ssh_del_authkey(const char *pubkey_path, const char *username, struct nc_server_ssh_opts *opts) +API int +nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, + const char *username) { uint32_t i; int ret = -1; - if (!pubkey_path && !username) { - for (i = 0; i < opts->authkey_count; ++i) { - lydict_remove(server_opts.ctx, opts->authkeys[i].path); - lydict_remove(server_opts.ctx, opts->authkeys[i].username); + /* LOCK */ + pthread_mutex_lock(&server_opts.authkey_lock); + + if (!pubkey_path && !pubkey_base64 && !type && !username) { + for (i = 0; i < server_opts.authkey_count; ++i) { + lydict_remove(server_opts.ctx, server_opts.authkeys[i].path); + lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64); + lydict_remove(server_opts.ctx, server_opts.authkeys[i].username); ret = 0; } - free(opts->authkeys); - opts->authkeys = NULL; - opts->authkey_count = 0; + free(server_opts.authkeys); + server_opts.authkeys = NULL; + server_opts.authkey_count = 0; } else { - for (i = 0; i < opts->authkey_count; ++i) { - if ((!pubkey_path || !strcmp(opts->authkeys[i].path, pubkey_path)) - && (!username || !strcmp(opts->authkeys[i].username, username))) { - lydict_remove(server_opts.ctx, opts->authkeys[i].path); - lydict_remove(server_opts.ctx, opts->authkeys[i].username); - - --opts->authkey_count; - if (i < opts->authkey_count) { - memcpy(&opts->authkeys[i], &opts->authkeys[opts->authkey_count], sizeof *opts->authkeys); - } else if (!opts->authkey_count) { - free(opts->authkeys); - opts->authkeys = NULL; + for (i = 0; i < server_opts.authkey_count; ++i) { + if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path)) + && (!pubkey_base64 || strcmp(server_opts.authkeys[i].base64, pubkey_base64)) + && (!type || (server_opts.authkeys[i].type == type)) + && (!username || !strcmp(server_opts.authkeys[i].username, username))) { + lydict_remove(server_opts.ctx, server_opts.authkeys[i].path); + lydict_remove(server_opts.ctx, server_opts.authkeys[i].base64); + lydict_remove(server_opts.ctx, server_opts.authkeys[i].username); + + --server_opts.authkey_count; + if (i < server_opts.authkey_count) { + memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count], + sizeof *server_opts.authkeys); + } else if (!server_opts.authkey_count) { + free(server_opts.authkeys); + server_opts.authkeys = NULL; } ret = 0; @@ -436,37 +688,8 @@ nc_server_ssh_del_authkey(const char *pubkey_path, const char *username, struct } } - return ret; -} - -API int -nc_server_ssh_endpt_del_authkey(const char *endpt_name, const char *pubkey_path, const char *username) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_ssh_del_authkey(pubkey_path, username, endpt->ssh_opts); /* UNLOCK */ - nc_server_endpt_unlock(endpt); - - return ret; -} - -API int -nc_server_ssh_ch_del_authkey(const char *pubkey_path, const char *username) -{ - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - ret = nc_server_ssh_del_authkey(pubkey_path, username, &ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); + pthread_mutex_unlock(&server_opts.authkey_lock); return ret; } @@ -474,22 +697,11 @@ nc_server_ssh_ch_del_authkey(const char *pubkey_path, const char *username) void nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts) { - nc_server_ssh_del_hostkey(NULL, opts); + nc_server_ssh_del_hostkey(NULL, -1, opts); if (opts->banner) { lydict_remove(server_opts.ctx, opts->banner); opts->banner = NULL; } - nc_server_ssh_del_authkey(NULL, NULL, opts); -} - -API void -nc_server_ssh_ch_clear_opts(void) -{ - /* OPTS LOCK */ - pthread_mutex_lock(&ssh_ch_opts_lock); - nc_server_ssh_clear_opts(&ssh_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&ssh_ch_opts_lock); } static char * @@ -572,8 +784,8 @@ nc_sshcb_auth_password(struct nc_session *session, ssh_message msg) } free(pass_hash); - ++session->ssh_auth_attempts; - VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts); + ++session->opts.server.ssh_auth_attempts; + VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); } @@ -602,8 +814,8 @@ nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg) session->flags |= NC_SESSION_SSH_AUTHENTICATED; ssh_message_auth_reply_success(msg, 0); } else { - ++session->ssh_auth_attempts; - VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts); + ++session->opts.server.ssh_auth_attempts; + VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); } free(pass_hash); @@ -611,20 +823,37 @@ nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg) } static const char * -auth_pubkey_compare_key(struct nc_server_ssh_opts *opts, ssh_key key) +auth_pubkey_compare_key(ssh_key key) { uint32_t i; ssh_key pub_key; const char *username = NULL; - int ret; + int ret = 0; + + /* LOCK */ + pthread_mutex_lock(&server_opts.authkey_lock); + + for (i = 0; i < server_opts.authkey_count; ++i) { + switch (server_opts.authkeys[i].type) { + case NC_SSH_KEY_UNKNOWN: + ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key); + break; + case NC_SSH_KEY_DSA: + ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key); + break; + case NC_SSH_KEY_RSA: + ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key); + break; + case NC_SSH_KEY_ECDSA: + ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key); + break; + } - for (i = 0; i < opts->authkey_count; ++i) { - ret = ssh_pki_import_pubkey_file(opts->authkeys[i].path, &pub_key); if (ret == SSH_EOF) { - WRN("Failed to import the public key \"%s\" (File access problem).", opts->authkeys[i].path); + WRN("Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username); continue; } else if (ret == SSH_ERROR) { - WRN("Failed to import the public key \"%s\" (SSH error).", opts->authkeys[i].path); + WRN("Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username); continue; } @@ -636,10 +865,13 @@ auth_pubkey_compare_key(struct nc_server_ssh_opts *opts, ssh_key key) ssh_key_free(pub_key); } - if (i < opts->authkey_count) { - username = opts->authkeys[i].username; + if (i < server_opts.authkey_count) { + username = server_opts.authkeys[i].username; } + /* UNLOCK */ + pthread_mutex_unlock(&server_opts.authkey_lock); + return username; } @@ -649,7 +881,7 @@ nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg) const char *username; int signature_state; - if ((username = auth_pubkey_compare_key(session->data, ssh_message_auth_pubkey(msg))) == NULL) { + if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) { VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username); goto fail; } else if (strcmp(session->username, username)) { @@ -670,8 +902,8 @@ nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg) return; fail: - ++session->ssh_auth_attempts; - VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->ssh_auth_attempts); + ++session->opts.server.ssh_auth_attempts; + VRB("Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); } @@ -730,7 +962,7 @@ nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, cons session->flags |= NC_SESSION_SSH_SUBSYS_NETCONF; } else { /* additional channel subsystem request, new session is ready as far as SSH is concerned */ - new_session = calloc(1, sizeof *new_session); + new_session = nc_new_session(1); if (!new_session) { ERRMEM; return -1; @@ -748,6 +980,8 @@ nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, cons new_session->side = NC_SERVER; new_session->ti_type = NC_TI_LIBSSH; new_session->ti_lock = session->ti_lock; + new_session->ti_cond = session->ti_cond; + new_session->ti_inuse = session->ti_inuse; new_session->ti.libssh.channel = channel; new_session->ti.libssh.session = session->ti.libssh.session; new_session->username = lydict_insert(server_opts.ctx, session->username, 0); @@ -957,7 +1191,8 @@ nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) static int nc_open_netconf_channel(struct nc_session *session, int timeout) { - int elapsed_usec = 0, ret; + int ret; + struct timespec ts_timeout, ts_cur; /* message callback is executed twice to give chance for the channel to be * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */ @@ -967,7 +1202,7 @@ nc_open_netconf_channel(struct nc_session *session, int timeout) return -1; } - ret = nc_timedlock(session->ti_lock, timeout, __func__); + ret = nc_session_lock(session, timeout, __func__); if (ret != 1) { return ret; } @@ -976,24 +1211,23 @@ nc_open_netconf_channel(struct nc_session *session, int timeout) if (ret != SSH_OK) { ERR("Failed to receive SSH messages on a session (%s).", ssh_get_error(session->ti.libssh.session)); - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); return -1; } if (!session->ti.libssh.channel) { /* we did not receive channel-open, timeout */ - pthread_mutex_unlock(session->ti_lock); + nc_session_unlock(session, timeout, __func__); return 0; } ret = ssh_execute_message_callbacks(session->ti.libssh.session); + nc_session_unlock(session, timeout, __func__); if (ret != SSH_OK) { ERR("Failed to receive SSH messages on a session (%s).", ssh_get_error(session->ti.libssh.session)); - pthread_mutex_unlock(session->ti_lock); return -1; } - pthread_mutex_unlock(session->ti_lock); if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { /* we did not receive subsystem-request, timeout */ @@ -1003,48 +1237,91 @@ nc_open_netconf_channel(struct nc_session *session, int timeout) return 1; } + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while (1) { if (!nc_session_is_connected(session)) { ERR("Communication socket unexpectedly closed (libssh)."); return -1; } - ret = nc_timedlock(session->ti_lock, timeout, __func__); + ret = nc_session_lock(session, timeout, __func__); if (ret != 1) { return ret; } ret = ssh_execute_message_callbacks(session->ti.libssh.session); + nc_session_unlock(session, timeout, __func__); if (ret != SSH_OK) { ERR("Failed to receive SSH messages on a session (%s).", ssh_get_error(session->ti.libssh.session)); - pthread_mutex_unlock(session->ti_lock); return -1; } - pthread_mutex_unlock(session->ti_lock); - if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { return 1; } - if ((timeout != -1) && (elapsed_usec / 1000 >= timeout)) { - /* timeout */ - ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting."); - break; - } - usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + /* timeout */ + ERR("Failed to start \"netconf\" SSH subsystem for too long, disconnecting."); + break; + } + } } return 0; } -API NC_MSG_TYPE -nc_connect_callhome_ssh(const char *host, uint16_t port, struct nc_session **session) +static int +nc_ssh_bind_add_hostkeys(ssh_bind sbind, const char **hostkeys, uint8_t hostkey_count) { - return nc_connect_callhome(host, port, NC_TI_LIBSSH, session); + uint8_t i; + char *privkey_path, *privkey_data; + int privkey_data_rsa, ret; + + if (!server_opts.hostkey_clb) { + ERR("Callback for retrieving SSH host keys not set."); + return -1; + } + + for (i = 0; i < hostkey_count; ++i) { + privkey_path = privkey_data = NULL; + if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_data_rsa)) { + ERR("Host key callback failed."); + return -1; + } + + if (privkey_data) { + privkey_path = base64der_key_to_tmp_file(privkey_data, privkey_data_rsa); + if (!privkey_path) { + ERR("Temporarily storing a host key into a file failed (%s).", strerror(errno)); + free(privkey_data); + return -1; + } + } + + ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path); + + /* cleanup */ + if (privkey_data && unlink(privkey_path)) { + WRN("Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno)); + } + free(privkey_path); + free(privkey_data); + + if (ret != SSH_OK) { + ERR("Failed to set hostkey \"%s\" (%s).", hostkeys[i], ssh_get_error(sbind)); + return -1; + } + } + + return 0; } int @@ -1052,8 +1329,8 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) { ssh_bind sbind; struct nc_server_ssh_opts *opts; - int libssh_auth_methods = 0, elapsed_usec = 0, ret; - uint8_t i; + int libssh_auth_methods = 0, ret; + struct timespec ts_timeout, ts_cur; opts = session->data; @@ -1083,13 +1360,11 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) close(sock); return -1; } - for (i = 0; i < opts->hostkey_count; ++i) { - if (ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, opts->hostkeys[i]) != SSH_OK) { - ERR("Failed to set hostkey \"%s\" (%s).", opts->hostkeys[i], ssh_get_error(sbind)); - close(sock); - ssh_bind_free(sbind); - return -1; - } + + if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) { + close(sock); + ssh_bind_free(sbind); + return -1; } if (opts->banner) { ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_BANNER, opts->banner); @@ -1109,12 +1384,18 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) ssh_set_blocking(session->ti.libssh.session, 0); + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while ((ret = ssh_handle_key_exchange(session->ti.libssh.session)) == SSH_AGAIN) { /* this tends to take longer */ usleep(NC_TIMEOUT_STEP * 20); - elapsed_usec += NC_TIMEOUT_STEP * 20; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - break; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + break; + } } } if (ret == SSH_AGAIN) { @@ -1126,10 +1407,13 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) } /* authenticate */ - elapsed_usec = 0; - do { + if (opts->auth_timeout) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, opts->auth_timeout * 1000); + } + while (1) { if (!nc_session_is_connected(session)) { - ERR("Communication socket unexpectedly closed (libssh)."); + ERR("Communication SSH socket unexpectedly closed."); return -1; } @@ -1139,27 +1423,37 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) return -1; } - if (session->ssh_auth_attempts >= opts->auth_attempts) { - ERR("Too many failed authentication attempts of user \"%s\".", session->username); - return -1; - } - if (session->flags & NC_SESSION_SSH_AUTHENTICATED) { break; } + if (session->opts.server.ssh_auth_attempts >= opts->auth_attempts) { + ERR("Too many failed authentication attempts of user \"%s\".", session->username); + return -1; + } + usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - } while (!opts->auth_timeout || (elapsed_usec / 1000000 < opts->auth_timeout)); + if (opts->auth_timeout) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + /* timeout */ + break; + } + } + } if (!(session->flags & NC_SESSION_SSH_AUTHENTICATED)) { /* timeout */ - ERR("Client failed to authenticate for too long, disconnecting."); + if (session->username) { + ERR("User \"%s\" failed to authenticate for too long, disconnecting.", session->username); + } else { + ERR("User failed to authenticate for too long, disconnecting."); + } return 0; } /* open channel */ - ret = nc_open_netconf_channel(session, opts->auth_timeout ? (opts->auth_timeout * 1000 - elapsed_usec / 1000) : -1); + ret = nc_open_netconf_channel(session, timeout); if (ret < 1) { return ret; } @@ -1214,7 +1508,7 @@ nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session return msgtype; } - new_session->session_start = new_session->last_rpc = time(NULL); + new_session->opts.server.session_start = new_session->opts.server.last_rpc = time(NULL); new_session->status = NC_STATUS_RUNNING; *session = new_session; @@ -1282,7 +1576,7 @@ nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session) return msgtype; } - new_session->session_start = new_session->last_rpc = time(NULL); + new_session->opts.server.session_start = new_session->opts.server.last_rpc = time(NULL); new_session->status = NC_STATUS_RUNNING; *session = new_session; diff --git a/src/session_server_tls.c b/src/session_server_tls.c index e703a7c8..cc6e4133 100644 --- a/src/session_server_tls.c +++ b/src/session_server_tls.c @@ -36,7 +36,7 @@ static pthread_key_t verify_key; static pthread_once_t verify_once = PTHREAD_ONCE_INIT; static char * -asn1time_to_str(ASN1_TIME *t) +asn1time_to_str(const ASN1_TIME *t) { char *cp; BIO *bio; @@ -224,7 +224,11 @@ nc_tls_ctn_get_username_from_cert(X509 *client_cert, NC_TLS_CTN_MAPTYPE map_type /* rfc822Name (email) */ if ((map_type == NC_TLS_CTN_SAN_ANY || map_type == NC_TLS_CTN_SAN_RFC822_NAME) && san_name->type == GEN_EMAIL) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 *username = strdup((char *)ASN1_STRING_data(san_name->d.rfc822Name)); +#else + *username = strdup((char *)ASN1_STRING_get0_data(san_name->d.rfc822Name)); +#endif if (!*username) { ERRMEM; return 1; @@ -235,7 +239,11 @@ nc_tls_ctn_get_username_from_cert(X509 *client_cert, NC_TLS_CTN_MAPTYPE map_type /* dNSName */ if ((map_type == NC_TLS_CTN_SAN_ANY || map_type == NC_TLS_CTN_SAN_DNS_NAME) && san_name->type == GEN_DNS) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L // < 1.1.0 *username = strdup((char *)ASN1_STRING_data(san_name->d.dNSName)); +#else + *username = strdup((char *)ASN1_STRING_get0_data(san_name->d.dNSName)); +#endif if (!*username) { ERRMEM; return 1; @@ -317,6 +325,12 @@ nc_tls_cert_to_name(struct nc_ctn *ctn_first, X509 *cert, NC_TLS_CTN_MAPTYPE *ma } for (ctn = ctn_first; ctn; ctn = ctn->next) { + /* first make sure the entry is valid */ + if (!ctn->fingerprint || !ctn->map_type || ((ctn->map_type == NC_TLS_CTN_SPECIFIED) && !ctn->name)) { + VRB("Cert verify CTN: entry with id %u not valid, skipping.", ctn->id); + continue; + } + /* MD5 */ if (!strncmp(ctn->fingerprint, "01", 2)) { if (!digest_md5) { @@ -464,6 +478,220 @@ nc_tls_cert_to_name(struct nc_ctn *ctn_first, X509 *cert, NC_TLS_CTN_MAPTYPE *ma return ret; } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 + +static int +nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + X509_STORE_CTX *store_ctx; + X509_OBJECT *obj; + X509_NAME *subject; + X509_NAME *issuer; + X509 *cert; + X509_CRL *crl; + X509_REVOKED *revoked; + STACK_OF(X509) *cert_stack; + EVP_PKEY *pubkey; + struct nc_session* session; + struct nc_server_tls_opts *opts; + const ASN1_INTEGER *serial; + int i, n, rc, depth; + char *cp; + const char *username = NULL; + NC_TLS_CTN_MAPTYPE map_type = 0; + const ASN1_TIME *last_update = NULL, *next_update = NULL; + + /* get the thread session */ + session = pthread_getspecific(verify_key); + if (!session) { + ERRINT; + return 0; + } + + opts = session->data; + + /* get the last certificate, that is the peer (client) certificate */ + if (!session->opts.server.client_cert) { + cert_stack = X509_STORE_CTX_get1_chain(x509_ctx); + session->opts.server.client_cert = sk_X509_value(cert_stack, sk_X509_num(cert_stack) - 1); + X509_up_ref(session->opts.server.client_cert); + sk_X509_pop_free(cert_stack, X509_free); + } + + /* standard certificate verification failed, so a trusted client cert must match to continue */ + if (!preverify_ok) { + subject = X509_get_subject_name(session->opts.server.client_cert); + cert_stack = X509_STORE_CTX_get1_certs(x509_ctx, subject); + if (cert_stack) { + for (i = 0; i < sk_X509_num(cert_stack); ++i) { + if (cert_pubkey_match(session->opts.server.client_cert, sk_X509_value(cert_stack, i))) { + /* we are just overriding the failed standard certificate verification (preverify_ok == 0), + * this callback will be called again with the same current certificate and preverify_ok == 1 */ + VRB("Cert verify: fail (%s), but the client certificate is trusted, continuing.", + X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + sk_X509_pop_free(cert_stack, X509_free); + return 1; + } + } + sk_X509_pop_free(cert_stack, X509_free); + } + + ERR("Cert verify: fail (%s).", X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx))); + return 0; + } + + /* print cert verify info */ + depth = X509_STORE_CTX_get_error_depth(x509_ctx); + VRB("Cert verify: depth %d.", depth); + + cert = X509_STORE_CTX_get_current_cert(x509_ctx); + subject = X509_get_subject_name(cert); + issuer = X509_get_issuer_name(cert); + + cp = X509_NAME_oneline(subject, NULL, 0); + VRB("Cert verify: subject: %s.", cp); + OPENSSL_free(cp); + cp = X509_NAME_oneline(issuer, NULL, 0); + VRB("Cert verify: issuer: %s.", cp); + OPENSSL_free(cp); + + /* check for revocation if set */ + if (opts->crl_store) { + /* 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_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) { + cp = X509_NAME_oneline(subject, NULL, 0); + VRB("Cert verify CRL: issuer: %s.", cp); + OPENSSL_free(cp); + + last_update = X509_CRL_get0_lastUpdate(crl); + next_update = X509_CRL_get0_nextUpdate(crl); + cp = asn1time_to_str(last_update); + VRB("Cert verify CRL: last update: %s.", cp); + free(cp); + cp = asn1time_to_str(next_update); + VRB("Cert verify CRL: next update: %s.", cp); + free(cp); + + /* verify the signature on this CRL */ + pubkey = X509_get_pubkey(cert); + if (X509_CRL_verify(crl, pubkey) <= 0) { + ERR("Cert verify CRL: invalid signature."); + 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; + } + if (pubkey) { + EVP_PKEY_free(pubkey); + } + + /* check date of CRL to make sure it's not expired */ + if (!next_update) { + ERR("Cert verify CRL: invalid nextUpdate field."); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD); + X509_OBJECT_free(obj); + return 0; + } + if (X509_cmp_current_time(next_update) < 0) { + ERR("Cert verify CRL: expired - revoking all certificates."); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED); + X509_OBJECT_free(obj); + return 0; + } + 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_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); + serial = X509_REVOKED_get0_serialNumber(revoked); + if (ASN1_INTEGER_cmp(serial, X509_get_serialNumber(cert)) == 0) { + cp = X509_NAME_oneline(issuer, NULL, 0); + ERR("Cert verify CRL: certificate with serial %ld (0x%lX) revoked per CRL from issuer %s.", serial, serial, cp); + OPENSSL_free(cp); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED); + X509_OBJECT_free(obj); + return 0; + } + } + X509_OBJECT_free(obj); + } + } + + /* cert-to-name already successful */ + if (session->username) { + return 1; + } + + /* cert-to-name */ + rc = nc_tls_cert_to_name(opts->ctn, cert, &map_type, &username); + + if (rc) { + if (rc == -1) { + /* fatal error */ + depth = 0; + } + /* rc == 1 is a normal CTN fail (no match found) */ + goto fail; + } + + /* cert-to-name match, now to extract the specific field from the peer cert */ + if (map_type == NC_TLS_CTN_SPECIFIED) { + session->username = lydict_insert(server_opts.ctx, username, 0); + } else { + rc = nc_tls_ctn_get_username_from_cert(session->opts.server.client_cert, map_type, &cp); + if (rc) { + if (rc == -1) { + depth = 0; + } + goto fail; + } + session->username = lydict_insert_zc(server_opts.ctx, cp); + } + + VRB("Cert verify CTN: new client username recognized as \"%s\".", session->username); + + if (server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) { + VRB("Cert verify: user verify callback revoked authorization."); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION); + return 0; + } + + return 1; + +fail: + if (depth > 0) { + VRB("Cert verify CTN: cert fail, cert-to-name will continue on the next cert in chain."); + return 1; + } + + VRB("Cert-to-name unsuccessful, dropping the new client."); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION); + return 0; +} + +#else + static int nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) { @@ -495,26 +723,22 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) opts = session->data; /* get the last certificate, that is the peer (client) certificate */ - if (!session->tls_cert) { + if (!session->opts.server.client_cert) { cert_stack = X509_STORE_CTX_get1_chain(x509_ctx); - /* TODO all that is needed, but function X509_up_ref not present in older OpenSSL versions - session->cert = sk_X509_value(cert_stack, sk_X509_num(cert_stack) - 1); - X509_up_ref(session->cert); - sk_X509_pop_free(cert_stack, X509_free); */ while ((cert = sk_X509_pop(cert_stack))) { - X509_free(session->tls_cert); - session->tls_cert = cert; + X509_free(session->opts.server.client_cert); + session->opts.server.client_cert = cert; } sk_X509_pop_free(cert_stack, X509_free); } /* standard certificate verification failed, so a trusted client cert must match to continue */ if (!preverify_ok) { - subject = X509_get_subject_name(session->tls_cert); + subject = X509_get_subject_name(session->opts.server.client_cert); cert_stack = X509_STORE_get1_certs(x509_ctx, subject); if (cert_stack) { for (i = 0; i < sk_X509_num(cert_stack); ++i) { - if (cert_pubkey_match(session->tls_cert, sk_X509_value(cert_stack, i))) { + if (cert_pubkey_match(session->opts.server.client_cert, sk_X509_value(cert_stack, i))) { /* we are just overriding the failed standard certificate verification (preverify_ok == 0), * this callback will be called again with the same current certificate and preverify_ok == 1 */ VRB("Cert verify: fail (%s), but the client certificate is trusted, continuing.", @@ -647,7 +871,7 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) if (map_type == NC_TLS_CTN_SPECIFIED) { session->username = lydict_insert(server_opts.ctx, username, 0); } else { - rc = nc_tls_ctn_get_username_from_cert(session->tls_cert, map_type, &cp); + rc = nc_tls_ctn_get_username_from_cert(session->opts.server.client_cert, map_type, &cp); if (rc) { if (rc == -1) { depth = 0; @@ -658,6 +882,13 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) } VRB("Cert verify CTN: new client username recognized as \"%s\".", session->username); + + if (server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) { + VRB("Cert verify: user verify callback revoked authorization."); + X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_APPLICATION_VERIFICATION); + return 0; + } + return 1; fail: @@ -671,424 +902,248 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) return 0; } -API int -nc_server_tls_endpt_set_address(const char *endpt_name, const char *address) -{ - return nc_server_endpt_set_address_port(endpt_name, address, 0, NC_TI_OPENSSL); -} - -API int -nc_server_tls_endpt_set_port(const char *endpt_name, uint16_t port) -{ - return nc_server_endpt_set_address_port(endpt_name, NULL, port, NC_TI_OPENSSL); -} +#endif static int -nc_server_tls_set_cert(const char *cert, struct nc_server_tls_opts *opts) +nc_server_tls_set_server_cert(const char *name, struct nc_server_tls_opts *opts) { - if (!cert) { - ERRARG("cert"); - return -1; + if (!name) { + if (opts->server_cert) { + lydict_remove(server_opts.ctx, opts->server_cert); + } + opts->server_cert = NULL; + return 0; } if (opts->server_cert) { - X509_free(opts->server_cert); - } - - opts->server_cert = base64der_to_cert(cert); - if (!opts->server_cert) { - ERR("Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); - return -1; + lydict_remove(server_opts.ctx, opts->server_cert); } + opts->server_cert = lydict_insert(server_opts.ctx, name, 0); return 0; } API int -nc_server_tls_endpt_set_cert(const char *endpt_name, const char *cert) +nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name) { int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_set_cert(cert, endpt->tls_opts); + ret = nc_server_tls_set_server_cert(name, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_set_cert(const char *cert) +nc_server_tls_ch_client_set_server_cert(const char *client_name, const char *name) { int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } + + ret = nc_server_tls_set_server_cert(name, client->opts.tls); - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_cert(cert, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } +API 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, int *privkey_data_rsa), + void *user_data, void (*free_user_data)(void *user_data)) +{ + if (!cert_clb) { + ERRARG("cert_clb"); + return; + } + + server_opts.server_cert_clb = cert_clb; + server_opts.server_cert_data = user_data; + server_opts.server_cert_data_free = free_user_data; +} + static int -nc_server_tls_set_cert_path(const char *cert_path, struct nc_server_tls_opts *opts) +nc_server_tls_add_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts) { - if (!cert_path) { - ERRARG("cert_path"); + if (!name) { + ERRARG("name"); return -1; } - if (opts->server_cert) { - X509_free(opts->server_cert); - } - errno = 0; - opts->server_cert = pem_to_cert(cert_path); - if (!opts->server_cert) { - ERR("Loading the server certificate failed (%s).", (errno ? strerror(errno) : ERR_reason_error_string(ERR_get_error()))); + ++opts->trusted_cert_list_count; + opts->trusted_cert_lists = nc_realloc(opts->trusted_cert_lists, opts->trusted_cert_list_count * sizeof *opts->trusted_cert_lists); + if (!opts->trusted_cert_lists) { + ERRMEM; return -1; } + opts->trusted_cert_lists[opts->trusted_cert_list_count - 1] = lydict_insert(server_opts.ctx, name, 0); return 0; } API int -nc_server_tls_endpt_set_cert_path(const char *endpt_name, const char *cert_path) +nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name) { int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_set_cert_path(cert_path, endpt->tls_opts); + ret = nc_server_tls_add_trusted_cert_list(name, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_set_cert_path(const char *cert_path) +nc_server_tls_ch_client_add_trusted_cert_list(const char *client_name, const char *name) { int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_cert_path(cert_path, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + ret = nc_server_tls_add_trusted_cert_list(name, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } -static int -nc_server_tls_set_key(const char *privkey, int is_rsa, struct nc_server_tls_opts *opts) +API 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)) { - if (!privkey) { - ERRARG("privkey"); - return -1; + if (!cert_list_clb) { + ERRARG("cert_list_clb"); + return; } - if (opts->server_key) { - EVP_PKEY_free(opts->server_key); - } + server_opts.trusted_cert_list_clb = cert_list_clb; + server_opts.trusted_cert_list_data = user_data; + server_opts.trusted_cert_list_data_free = free_user_data; +} - opts->server_key = base64der_to_privatekey(privkey, is_rsa); - if (!opts->server_key) { - ERR("Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); - return -1; +static int +nc_server_tls_del_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts) +{ + uint16_t i; + + if (!name) { + for (i = 0; i < opts->trusted_cert_list_count; ++i) { + lydict_remove(server_opts.ctx, opts->trusted_cert_lists[i]); + } + free(opts->trusted_cert_lists); + opts->trusted_cert_lists = NULL; + opts->trusted_cert_list_count = 0; + return 0; + } else { + for (i = 0; i < opts->trusted_cert_list_count; ++i) { + if (!strcmp(opts->trusted_cert_lists[i], name)) { + lydict_remove(server_opts.ctx, opts->trusted_cert_lists[i]); + + --opts->trusted_cert_list_count; + if (i < opts->trusted_cert_list_count - 1) { + memmove(opts->trusted_cert_lists + i, opts->trusted_cert_lists + i + 1, + (opts->trusted_cert_list_count - i) * sizeof *opts->trusted_cert_lists); + } + return 0; + } + } } - return 0; + ERR("Certificate list \"%s\" not found.", name); + return -1; } API int -nc_server_tls_endpt_set_key(const char *endpt_name, const char *privkey, int is_rsa) +nc_server_tls_endpt_del_trusted_cert_list(const char *endpt_name, const char *name) { int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_set_key(privkey, is_rsa, endpt->tls_opts); + ret = nc_server_tls_del_trusted_cert_list(name, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_set_key(const char *privkey, int is_rsa) +nc_server_tls_ch_client_del_trusted_cert_list(const char *client_name, const char *name) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_key(privkey, is_rsa, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); - - return ret; -} - -static int -nc_server_tls_set_key_path(const char *privkey_path, struct nc_server_tls_opts *opts) -{ - FILE *file; - - if (!privkey_path) { - ERRARG("privkey_path"); - return -1; - } - - file = fopen(privkey_path, "r"); - if (!file) { - ERR("Cannot open the file \"%s\" for reading (%s).", privkey_path, strerror(errno)); - return -1; - } - - if (opts->server_key) { - EVP_PKEY_free(opts->server_key); - } - opts->server_key = PEM_read_PrivateKey(file, NULL, NULL, NULL); - fclose(file); - - if (!opts->server_key) { - ERR("Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); - return -1; - } - - return 0; -} - -API int -nc_server_tls_endpt_set_key_path(const char *endpt_name, const char *privkey_path) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_set_key_path(privkey_path, endpt->tls_opts); - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - - return ret; -} - -API int -nc_server_tls_ch_set_key_path(const char *privkey_path) -{ - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_key_path(privkey_path, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); - - return ret; -} - -static int -nc_server_tls_add_trusted_cert(const char *name, const char *cert, struct nc_server_tls_opts *opts) -{ - X509 *crt; - - if (!cert) { - ERRARG("cert"); - return -1; - } - - crt = base64der_to_cert(cert); - if (!crt) { - ERR("Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); - return -1; - } - - ++opts->trusted_cert_count; - opts->trusted_certs = nc_realloc(opts->trusted_certs, opts->trusted_cert_count * sizeof *opts->trusted_certs); - if (!opts->trusted_certs) { - ERRMEM; - X509_free(crt); - return -1; - } - opts->trusted_certs[opts->trusted_cert_count - 1].name = lydict_insert(server_opts.ctx, name, 0); - opts->trusted_certs[opts->trusted_cert_count - 1].cert = crt; - - return 0; -} - -API int -nc_server_tls_endpt_add_trusted_cert(const char *endpt_name, const char *cert_name, const char *cert) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_add_trusted_cert(cert_name, cert, endpt->tls_opts); - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - - return ret; -} - -API int -nc_server_tls_ch_add_trusted_cert(const char *cert_name, const char *cert) -{ - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_add_trusted_cert(cert_name, cert, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); - - return ret; -} - -static int -nc_server_tls_add_trusted_cert_path(const char *name, const char *cert_path, struct nc_server_tls_opts *opts) -{ - X509 *cert; - - if (!cert_path) { - ERRARG("cert"); - return -1; - } - - errno = 0; - cert = pem_to_cert(cert_path); - if (!cert) { - ERR("Loading the server certificate failed (%s).", (errno ? strerror(errno) : ERR_reason_error_string(ERR_get_error()))); - return -1; - } - - ++opts->trusted_cert_count; - opts->trusted_certs = nc_realloc(opts->trusted_certs, opts->trusted_cert_count * sizeof *opts->trusted_certs); - if (!opts->trusted_certs) { - ERRMEM; - X509_free(cert); + if (!client_name) { + ERRARG("client_name"); return -1; } - opts->trusted_certs[opts->trusted_cert_count - 1].name = lydict_insert(server_opts.ctx, name, 0); - opts->trusted_certs[opts->trusted_cert_count - 1].cert = cert; - - return 0; -} - -API int -nc_server_tls_endpt_add_trusted_cert_path(const char *endpt_name, const char *cert_name, const char *cert_path) -{ - int ret; - struct nc_endpt *endpt; /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { return -1; } - ret = nc_server_tls_add_trusted_cert_path(cert_name, cert_path, endpt->tls_opts); - /* UNLOCK */ - nc_server_endpt_unlock(endpt); - - return ret; -} - -API int -nc_server_tls_ch_add_trusted_cert_path(const char *cert_name, const char *cert_path) -{ - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_add_trusted_cert_path(cert_name, cert_path, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); - - return ret; -} - -static int -nc_server_tls_del_trusted_cert(const char *name, struct nc_server_tls_opts *opts) -{ - uint16_t i; - - if (!name) { - for (i = 0; i < opts->trusted_cert_count; ++i) { - lydict_remove(server_opts.ctx, opts->trusted_certs[i].name); - X509_free(opts->trusted_certs[i].cert); - } - free(opts->trusted_certs); - opts->trusted_certs = NULL; - opts->trusted_cert_count = 0; - return 0; - } else { - for (i = 0; i < opts->trusted_cert_count; ++i) { - if (!strcmp(opts->trusted_certs[i].name, name)) { - lydict_remove(server_opts.ctx, opts->trusted_certs[i].name); - X509_free(opts->trusted_certs[i].cert); - - --opts->trusted_cert_count; - if (i < opts->trusted_cert_count - 1) { - memmove(opts->trusted_certs + i, opts->trusted_certs + i + 1, - (opts->trusted_cert_count - i) * sizeof *opts->trusted_certs); - } - return 0; - } - } - } - ERR("Certificate \"%s\" not found.", name); - return -1; -} + ret = nc_server_tls_del_trusted_cert_list(name, client->opts.tls); -API int -nc_server_tls_endpt_del_trusted_cert(const char *endpt_name, const char *cert_name) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); - if (!endpt) { - return -1; - } - ret = nc_server_tls_del_trusted_cert(cert_name, endpt->tls_opts); /* UNLOCK */ - nc_server_endpt_unlock(endpt); - - return ret; -} - -API int -nc_server_tls_ch_del_trusted_cert(const char *cert_name) -{ - int ret; - - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_del_trusted_cert(cert_name, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + nc_server_ch_client_unlock(client); return ret; } @@ -1124,28 +1179,44 @@ nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_ int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, endpt->tls_opts); + ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_set_trusted_ca_paths(const char *ca_file, const char *ca_dir) +nc_server_tls_ch_client_set_trusted_ca_paths(const char *client_name, const char *ca_file, const char *ca_dir) { int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } + + ret = nc_server_tls_set_trusted_ca_paths(ca_file, ca_dir, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -1202,28 +1273,44 @@ nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_file, int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, endpt->tls_opts); + ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_set_crl_paths(const char *crl_file, const char *crl_dir) +nc_server_tls_ch_client_set_crl_paths(const char *client_name, const char *crl_file, const char *crl_dir) { int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } + + ret = nc_server_tls_set_crl_paths(crl_file, crl_dir, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } @@ -1244,106 +1331,155 @@ nc_server_tls_endpt_clear_crls(const char *endpt_name) { struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return; } - nc_server_tls_clear_crls(endpt->tls_opts); + nc_server_tls_clear_crls(endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); } API void -nc_server_tls_ch_clear_crls(void) +nc_server_tls_ch_client_clear_crls(const char *client_name) { - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - nc_server_tls_clear_crls(&tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); -} + struct nc_ch_client *client; -static int -nc_server_tls_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, struct nc_server_tls_opts *opts) -{ - struct nc_ctn *ctn, *new; - - if (!fingerprint) { - ERRARG("fingerprint"); - return -1; - } else if (!map_type) { - ERRARG("map_type"); - return -1; - } else if (((map_type == NC_TLS_CTN_SPECIFIED) && !name) - || ((map_type != NC_TLS_CTN_SPECIFIED) && name)) { - ERRARG("map_type and name"); - return -1; + if (!client_name) { + ERRARG("client_name"); + return; } - new = malloc(sizeof *new); - if (!new) { - ERRMEM; - return -1; + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return; } - new->fingerprint = lydict_insert(server_opts.ctx, fingerprint, 0); - new->name = lydict_insert(server_opts.ctx, name, 0); - new->id = id; - new->map_type = map_type; - new->next = NULL; + nc_server_tls_clear_crls(client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); +} + +static int +nc_server_tls_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, + struct nc_server_tls_opts *opts) +{ + struct nc_ctn *ctn, *new; if (!opts->ctn) { /* the first item */ - opts->ctn = new; + opts->ctn = new = calloc(1, sizeof *new); + if (!new) { + ERRMEM; + return -1; + } } else if (opts->ctn->id > id) { /* insert at the beginning */ + new = calloc(1, sizeof *new); + if (!new) { + ERRMEM; + return -1; + } new->next = opts->ctn; opts->ctn = new; } else { - for (ctn = opts->ctn; ctn->next && ctn->next->id <= id; ctn = ctn->next); - /* insert after ctn */ - new->next = ctn->next; - ctn->next = new; + for (ctn = opts->ctn; ctn->next && ctn->next->id < id; ctn = ctn->next); + if (ctn->id == id) { + /* it exists already */ + new = ctn; + } else { + /* insert after ctn */ + new = calloc(1, sizeof *new); + if (!new) { + ERRMEM; + return -1; + } + new->next = ctn->next; + ctn->next = new; + } + } + + new->id = id; + if (fingerprint) { + if (new->fingerprint) { + lydict_remove(server_opts.ctx, new->fingerprint); + } + new->fingerprint = lydict_insert(server_opts.ctx, fingerprint, 0); + } + if (map_type) { + new->map_type = map_type; + } + if (name) { + if (new->name) { + lydict_remove(server_opts.ctx, new->name); + } + new->name = lydict_insert(server_opts.ctx, name, 0); } return 0; } API 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) +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) { int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, endpt->tls_opts); + ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name) +nc_server_tls_ch_client_add_ctn(const char *client_name, uint32_t id, const char *fingerprint, + NC_TLS_CTN_MAPTYPE map_type, const char *name) { int ret; + struct nc_ch_client *client; - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } + + ret = nc_server_tls_add_ctn(id, fingerprint, map_type, name, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } static int -nc_server_tls_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, struct nc_server_tls_opts *opts) +nc_server_tls_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, + struct nc_server_tls_opts *opts) { struct nc_ctn *ctn, *next, *prev; int ret = -1; @@ -1394,69 +1530,312 @@ nc_server_tls_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE ma } API 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) +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) +{ + int ret; + struct nc_endpt *endpt; + + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + + /* LOCK */ + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); + if (!endpt) { + return -1; + } + ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, endpt->opts.tls); + /* UNLOCK */ + pthread_rwlock_unlock(&server_opts.endpt_lock); + + return ret; +} + +API int +nc_server_tls_ch_client_del_ctn(const char *client_name, int64_t id, const char *fingerprint, + NC_TLS_CTN_MAPTYPE map_type, const char *name) +{ + int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } + + ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); + + return ret; +} + +static int +nc_server_tls_get_ctn(uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, char **name, + struct nc_server_tls_opts *opts) +{ + struct nc_ctn *ctn; + int ret = -1; + + for (ctn = opts->ctn; ctn; ctn = ctn->next) { + if (id && *id && (*id != ctn->id)) { + continue; + } + if (fingerprint && *fingerprint && (!ctn->fingerprint || strcmp(*fingerprint, ctn->fingerprint))) { + continue; + } + if (map_type && *map_type && (!ctn->map_type || (*map_type != ctn->map_type))) { + continue; + } + if (name && *name && (!ctn->name || strcmp(*name, ctn->name))) { + continue; + } + + /* first match, good enough */ + if (id && !(*id)) { + *id = ctn->id; + } + if (fingerprint && !(*fingerprint) && ctn->fingerprint) { + *fingerprint = strdup(ctn->fingerprint); + } + if (map_type && !(*map_type) && ctn->map_type) { + *map_type = ctn->map_type; + } + if (name && !(*name) && ctn->name && ctn->name) { + *name = strdup(ctn->name); + } + + ret = 0; + break; + } + + return ret; +} + +API int +nc_server_tls_endpt_get_ctn(const char *endpt_name, uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, + char **name) { int ret; struct nc_endpt *endpt; + if (!endpt_name) { + ERRARG("endpt_name"); + return -1; + } + /* LOCK */ - endpt = nc_server_endpt_lock(endpt_name, NULL); + endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_OPENSSL, NULL); if (!endpt) { return -1; } - ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, endpt->tls_opts); + ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, endpt->opts.tls); /* UNLOCK */ - nc_server_endpt_unlock(endpt); + pthread_rwlock_unlock(&server_opts.endpt_lock); return ret; } API int -nc_server_tls_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name) +nc_server_tls_ch_client_get_ctn(const char *client_name, uint32_t *id, char **fingerprint, NC_TLS_CTN_MAPTYPE *map_type, + char **name) { int ret; + struct nc_ch_client *client; + + if (!client_name) { + ERRARG("client_name"); + return -1; + } + + /* LOCK */ + client = nc_server_ch_client_lock(client_name, NC_TI_OPENSSL, NULL); + if (!client) { + return -1; + } - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - ret = nc_server_tls_del_ctn(id, fingerprint, map_type, name, &tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + ret = nc_server_tls_get_ctn(id, fingerprint, map_type, name, client->opts.tls); + + /* UNLOCK */ + nc_server_ch_client_unlock(client); return ret; } +API const X509 * +nc_session_get_client_cert(const struct nc_session *session) +{ + if (!session || (session->side != NC_SERVER)) { + ERRARG("session"); + return NULL; + } + + return session->opts.server.client_cert; +} + +API void +nc_server_tls_set_verify_clb(int (*verify_clb)(const struct nc_session *session)) +{ + server_opts.user_verify_clb = verify_clb; +} + void nc_server_tls_clear_opts(struct nc_server_tls_opts *opts) { - EVP_PKEY_free(opts->server_key); - X509_free(opts->server_cert); - nc_server_tls_del_trusted_cert(NULL, opts); + lydict_remove(server_opts.ctx, opts->server_cert); + nc_server_tls_del_trusted_cert_list(NULL, opts); lydict_remove(server_opts.ctx, opts->trusted_ca_file); lydict_remove(server_opts.ctx, opts->trusted_ca_dir); nc_server_tls_clear_crls(opts); nc_server_tls_del_ctn(-1, NULL, 0, NULL, opts); } -API void -nc_server_tls_ch_clear_opts(void) +static void +nc_tls_make_verify_key(void) +{ + pthread_key_create(&verify_key, NULL); +} + +static int +nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name) { - /* OPTS LOCK */ - pthread_mutex_lock(&tls_ch_opts_lock); - nc_server_tls_clear_opts(&tls_ch_opts); - /* OPTS UNLOCK */ - pthread_mutex_unlock(&tls_ch_opts_lock); + char *cert_path = NULL, *cert_data = NULL, *privkey_path = NULL, *privkey_data = NULL; + int privkey_data_rsa = 1, ret = 0; + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + + if (!cert_name) { + ERR("Server certificate not set."); + return -1; + } else if (!server_opts.server_cert_clb) { + ERR("Callback for retrieving the server certificate is not set."); + return -1; + } + + if (server_opts.server_cert_clb(cert_name, server_opts.server_cert_data, &cert_path, &cert_data, &privkey_path, + &privkey_data, &privkey_data_rsa)) { + ERR("Server certificate callback failed."); + return -1; + } + + /* load the certificate */ + if (cert_path) { + if (SSL_CTX_use_certificate_file(tls_ctx, cert_path, SSL_FILETYPE_PEM) != 1) { + ERR("Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } else { + cert = base64der_to_cert(cert_data); + if (!cert || (SSL_CTX_use_certificate(tls_ctx, cert) != 1)) { + ERR("Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } + + /* load the private key */ + if (privkey_path) { + if (SSL_CTX_use_PrivateKey_file(tls_ctx, privkey_path, SSL_FILETYPE_PEM) != 1) { + ERR("Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } else { + pkey = base64der_to_privatekey(privkey_data, privkey_data_rsa); + if (!pkey || (SSL_CTX_use_PrivateKey(tls_ctx, pkey) != 1)) { + ERR("Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } + +cleanup: + X509_free(cert); + EVP_PKEY_free(pkey); + free(cert_path); + free(cert_data); + free(privkey_path); + free(privkey_data); + return ret; } static void -nc_tls_make_verify_key(void) +tls_store_add_trusted_cert(X509_STORE *cert_store, const char *cert_path, const char *cert_data) { - pthread_key_create(&verify_key, NULL); + X509 *cert; + + if (cert_path) { + cert = pem_to_cert(cert_path); + } else { + cert = base64der_to_cert(cert_data); + } + + if (!cert) { + if (cert_path) { + ERR("Loading a trusted certificate (path \"%s\") failed (%s).", cert_path, + ERR_reason_error_string(ERR_get_error())); + } else { + ERR("Loading a trusted certificate (data \"%s\") failed (%s).", cert_data, + ERR_reason_error_string(ERR_get_error())); + } + return; + } + + /* add the trusted certificate */ + if (X509_STORE_add_cert(cert_store, cert) != 1) { + ERR("Adding a trusted certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); + X509_free(cert); + return; + } + X509_free(cert); } -API NC_MSG_TYPE -nc_connect_callhome_tls(const char *host, uint16_t port, struct nc_session **session) +static int +nc_tls_store_set_trusted_certs(X509_STORE *cert_store, const char **trusted_cert_lists, uint16_t trusted_cert_list_count) { - return nc_connect_callhome(host, port, NC_TI_OPENSSL, session); + uint16_t i; + int j; + char **cert_paths, **cert_data; + int cert_path_count, cert_data_count; + + if (!server_opts.trusted_cert_list_clb) { + ERR("Callback for retrieving trusted certificate lists is not set."); + return -1; + } + + for (i = 0; i < trusted_cert_list_count; ++i) { + cert_paths = cert_data = NULL; + cert_path_count = cert_data_count = 0; + if (server_opts.trusted_cert_list_clb(trusted_cert_lists[i], server_opts.trusted_cert_list_data, + &cert_paths, &cert_path_count, &cert_data, &cert_data_count)) { + ERR("Trusted certificate list callback for \"%s\" failed.", trusted_cert_lists[i]); + return -1; + } + + for (j = 0; j < cert_path_count; ++j) { + tls_store_add_trusted_cert(cert_store, cert_paths[j], NULL); + free(cert_paths[j]); + } + free(cert_paths); + + for (j = 0; j < cert_data_count; ++j) { + tls_store_add_trusted_cert(cert_store, NULL, cert_data[j]); + free(cert_data[j]); + } + free(cert_data); + } + + return 0; } int @@ -1466,24 +1845,23 @@ nc_accept_tls_session(struct nc_session *session, int sock, int timeout) SSL_CTX *tls_ctx; X509_LOOKUP *lookup; struct nc_server_tls_opts *opts; - int ret, elapsed_usec = 0; - uint16_t i; + int ret; + struct timespec ts_timeout, ts_cur; opts = session->data; /* SSL_CTX */ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L // >= 1.1.0 + tls_ctx = SSL_CTX_new(TLS_server_method()); +#else tls_ctx = SSL_CTX_new(TLSv1_2_server_method()); +#endif if (!tls_ctx) { ERR("Failed to create TLS context."); goto error; } SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nc_tlsclb_verify); - if (SSL_CTX_use_PrivateKey(tls_ctx, opts->server_key) != 1) { - ERR("Loading the server private key failed (%s).", ERR_reason_error_string(ERR_get_error())); - goto error; - } - if (SSL_CTX_use_certificate(tls_ctx, opts->server_cert) != 1) { - ERR("Loading the server certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); + if (nc_tls_ctx_set_server_cert_key(tls_ctx, opts->server_cert)) { goto error; } @@ -1491,11 +1869,8 @@ nc_accept_tls_session(struct nc_session *session, int sock, int timeout) cert_store = X509_STORE_new(); SSL_CTX_set_cert_store(tls_ctx, cert_store); - for (i = 0; i < opts->trusted_cert_count; ++i) { - if (X509_STORE_add_cert(cert_store, opts->trusted_certs[i].cert) != 1) { - ERR("Adding a trusted certificate failed (%s).", ERR_reason_error_string(ERR_get_error())); - goto error; - } + if (nc_tls_store_set_trusted_certs(cert_store, opts->trusted_cert_lists, opts->trusted_cert_list_count)) { + goto error; } if (opts->trusted_ca_file) { @@ -1527,7 +1902,7 @@ nc_accept_tls_session(struct nc_session *session, int sock, int timeout) session->ti_type = NC_TI_OPENSSL; session->ti.tls = SSL_new(tls_ctx); - /* context can be freed already */ + /* context can be freed already, trusted certs must be freed manually */ SSL_CTX_free(tls_ctx); tls_ctx = NULL; @@ -1544,12 +1919,18 @@ nc_accept_tls_session(struct nc_session *session, int sock, int timeout) pthread_once(&verify_once, nc_tls_make_verify_key); pthread_setspecific(verify_key, session); + if (timeout > -1) { + nc_gettimespec(&ts_timeout); + nc_addtimespec(&ts_timeout, timeout); + } while (((ret = SSL_accept(session->ti.tls)) == -1) && (SSL_get_error(session->ti.tls, ret) == SSL_ERROR_WANT_READ)) { usleep(NC_TIMEOUT_STEP); - elapsed_usec += NC_TIMEOUT_STEP; - if ((timeout > -1) && (elapsed_usec / 1000 >= timeout)) { - ERR("SSL_accept timeout."); - return 0; + if (timeout > -1) { + nc_gettimespec(&ts_cur); + if (nc_difftimespec(&ts_cur, &ts_timeout) < 1) { + ERR("SSL_accept timeout."); + return 0; + } } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e7c4a65a..00d5b3fd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,7 +17,7 @@ if (ENABLE_VALGRIND_TESTS) find_program(valgrind_FOUND valgrind) if (valgrind_FOUND) foreach (test_name IN LISTS tests) - add_test(${test_name}_valgrind valgrind --leak-check=full ${CMAKE_BINARY_DIR}/tests/${test_name}) + add_test(${test_name}_valgrind valgrind --leak-check=full --error-exitcode=1 ${CMAKE_BINARY_DIR}/tests/${test_name}) endforeach() else (valgrind_FOUND) Message("-- valgrind executable not found! Disabling memory leaks tests") diff --git a/tests/config.h.in b/tests/config.h.in index 864de672..2136c1b0 100644 --- a/tests/config.h.in +++ b/tests/config.h.in @@ -12,6 +12,12 @@ * https://opensource.org/licenses/BSD-3-Clause */ +#ifdef __GNUC__ +# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#else +# define UNUSED(x) UNUSED_ ## x +#endif + #define TESTS_DIR "@CMAKE_SOURCE_DIR@/tests/" @SSH_MACRO@ diff --git a/tests/test_fd_comm.c b/tests/test_fd_comm.c index 0367cc28..e4eee709 100644 --- a/tests/test_fd_comm.c +++ b/tests/test_fd_comm.c @@ -80,6 +80,10 @@ setup_sessions(void **state) server_session->ti_type = NC_TI_FD; server_session->ti_lock = malloc(sizeof *server_session->ti_lock); pthread_mutex_init(server_session->ti_lock, NULL); + server_session->ti_cond = malloc(sizeof *server_session->ti_cond); + pthread_cond_init(server_session->ti_cond, NULL); + server_session->ti_inuse = malloc(sizeof *server_session->ti_inuse); + *server_session->ti_inuse = 0; server_session->ti.fd.in = sock[0]; server_session->ti.fd.out = sock[0]; server_session->ctx = ctx; @@ -93,11 +97,15 @@ setup_sessions(void **state) client_session->ti_type = NC_TI_FD; client_session->ti_lock = malloc(sizeof *client_session->ti_lock); pthread_mutex_init(client_session->ti_lock, NULL); + client_session->ti_cond = malloc(sizeof *client_session->ti_cond); + pthread_cond_init(client_session->ti_cond, NULL); + client_session->ti_inuse = malloc(sizeof *client_session->ti_inuse); + *client_session->ti_inuse = 0; client_session->ti.fd.in = sock[1]; client_session->ti.fd.out = sock[1]; client_session->ctx = ctx; client_session->flags = NC_SESSION_SHAREDCTX; - client_session->msgid = 50; + client_session->opts.client.msgid = 50; return 0; } @@ -109,12 +117,18 @@ teardown_sessions(void **state) close(server_session->ti.fd.in); pthread_mutex_destroy(server_session->ti_lock); + pthread_cond_destroy(server_session->ti_cond); free(server_session->ti_lock); + free(server_session->ti_cond); + free((int *)server_session->ti_inuse); free(server_session); close(client_session->ti.fd.in); pthread_mutex_destroy(client_session->ti_lock); + pthread_cond_destroy(client_session->ti_cond); free(client_session->ti_lock); + free(client_session->ti_cond); + free((int *)client_session->ti_inuse); free(client_session); return 0; diff --git a/tests/test_io.c b/tests/test_io.c index 203e3cce..eb725312 100644 --- a/tests/test_io.c +++ b/tests/test_io.c @@ -27,9 +27,9 @@ #include #include +#include #include #include -#include #include "config.h" struct wr { @@ -47,8 +47,6 @@ setup_write(void **state) w = malloc(sizeof *w); w->session = calloc(1, sizeof *w->session); w->session->ctx = ly_ctx_new(TESTS_DIR"../schemas"); - w->session->ti_lock = malloc(sizeof *w->session->ti_lock); - pthread_mutex_init(w->session->ti_lock, NULL); /* ietf-netconf */ fd = open(TESTS_DIR"../schemas/ietf-netconf.yin", O_RDONLY); @@ -62,8 +60,14 @@ setup_write(void **state) w->session->status = NC_STATUS_RUNNING; w->session->version = NC_VERSION_10; - w->session->msgid = 999; + w->session->opts.client.msgid = 999; w->session->ti_type = NC_TI_FD; + w->session->ti_lock = malloc(sizeof *w->session->ti_lock); + pthread_mutex_init(w->session->ti_lock, NULL); + w->session->ti_cond = malloc(sizeof *w->session->ti_cond); + pthread_cond_init(w->session->ti_cond, NULL); + w->session->ti_inuse = malloc(sizeof *w->session->ti_inuse); + *w->session->ti_inuse = 0; w->session->ti.fd.in = STDIN_FILENO; w->session->ti.fd.out = STDOUT_FILENO; diff --git a/tests/test_server_thread.c b/tests/test_server_thread.c index 0be2cfdb..b6f09cfb 100644 --- a/tests/test_server_thread.c +++ b/tests/test_server_thread.c @@ -3,7 +3,7 @@ * \author Michal Vasko * \brief libnetconf2 tests - thread-safety of all server functions * - * Copyright (c) 2015 CESNET, z.s.p.o. + * Copyright (c) 2017 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. @@ -78,61 +78,47 @@ server_thread(void *arg) return NULL; } -static void * -add_endpt_thread(void *arg) -{ - (void)arg; - int ret; - - pthread_barrier_wait(&barrier); - - ret = nc_server_add_endpt("tertiary"); - nc_assert(!ret); +#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ - return NULL; -} +#ifdef NC_ENABLED_SSH -static void * -del_endpt_thread(void *arg) +static int +clb_hostkeys(const char *name, void *UNUSED(user_data), char **privkey_path, char **UNUSED(privkey_data), + int *UNUSED(privkey_data_rsa)) { - (void)arg; - int ret; - - pthread_barrier_wait(&barrier); - - ret = nc_server_del_endpt("secondary"); - nc_assert(!ret); + if (!strcmp(name, "key_rsa")) { + *privkey_path = strdup(TESTS_DIR"/data/key_rsa"); + return 0; + } else if (!strcmp(name, "key_dsa")) { + *privkey_path = strdup(TESTS_DIR"/data/key_dsa"); + return 0; + } - return NULL; + return 1; } -#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ - -#ifdef NC_ENABLED_SSH - static void * -ssh_endpt_set_address_thread(void *arg) +add_endpt_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - - ret = nc_server_ssh_endpt_set_address("quaternary", "0.0.0.0"); + ret = nc_server_add_endpt("tertiary", NC_TI_LIBSSH); nc_assert(!ret); return NULL; } static void * -ssh_endpt_set_port_thread(void *arg) +del_endpt_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_set_port("quaternary", 6003); + ret = nc_server_del_endpt("secondary", 0); nc_assert(!ret); return NULL; @@ -146,7 +132,7 @@ ssh_endpt_set_hostkey_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_add_hostkey("main", TESTS_DIR"/data/key_dsa"); + ret = nc_server_ssh_endpt_add_hostkey("main_ssh", "key_dsa", -1); nc_assert(!ret); return NULL; @@ -160,7 +146,7 @@ ssh_endpt_set_banner_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_set_banner("main", "Howdy, partner!"); + ret = nc_server_ssh_endpt_set_banner("main_ssh", "Howdy, partner!"); nc_assert(!ret); return NULL; @@ -174,7 +160,7 @@ ssh_endpt_set_auth_methods_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_set_auth_methods("main", NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE); + ret = nc_server_ssh_endpt_set_auth_methods("main_ssh", NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE); nc_assert(!ret); return NULL; @@ -188,7 +174,7 @@ ssh_endpt_set_auth_attempts_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_set_auth_attempts("main", 2); + ret = nc_server_ssh_endpt_set_auth_attempts("main_ssh", 2); nc_assert(!ret); return NULL; @@ -202,7 +188,7 @@ ssh_endpt_set_auth_timeout_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_set_auth_timeout("main", 5); + ret = nc_server_ssh_endpt_set_auth_timeout("main_ssh", 5); nc_assert(!ret); return NULL; @@ -216,7 +202,7 @@ ssh_endpt_add_authkey_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_add_authkey("main", TESTS_DIR"/data/key_rsa.pub", "test3"); + ret = nc_server_ssh_add_authkey_path(TESTS_DIR"/data/key_rsa.pub", "test3"); nc_assert(!ret); return NULL; @@ -230,7 +216,7 @@ ssh_endpt_del_authkey_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_ssh_endpt_del_authkey("main", TESTS_DIR"/data/key_ecdsa.pub", "test2"); + ret = nc_server_ssh_del_authkey(TESTS_DIR"/data/key_ecdsa.pub", NULL, 0, "test2"); nc_assert(!ret); return NULL; @@ -284,128 +270,168 @@ ssh_client_thread(void *arg) #ifdef NC_ENABLED_TLS -static void * -tls_endpt_set_address_thread(void *arg) +static int +clb_server_cert(const char *name, void *UNUSED(user_data), char **cert_path, char **cert_data, char **privkey_path, + char **privkey_data, int *privkey_data_rsa) { - (void)arg; - int ret; + if (!strcmp(name, "server_cert1")) { + *cert_data = strdup("MIIEKjCCAxICCQDqSTPpuoUZkzANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJB\n" + "VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\n" + "cyBQdHkgTHRkMREwDwYDVQQDDAhzZXJ2ZXJjYTAeFw0xNjAyMDgxMTE0MzdaFw0y\n" + "NjAyMDUxMTE0MzdaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRl\n" + "MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMMBnNl\n" + "cnZlcjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOqI7Y3w5r8kD9WZ\n" + "CMAaa/e3ig7nm76aIJUR0Xb1bk6X/4FNVQKwEJsBodOYupZvE5FZdZ6DJSMSyQ3F\n" + "rJWnlZ+isr7F9B4bELV8Kj6sJGuVAr+mpcH/4rwL3DaXF9Y9Lf7iBgiOHUoip80A\n" + "sn9BU4q80JI6w2VHd5ng4TUE67gmpRleIHzViKt3taBrsAJ9bS5bvaE6xOB8zKYG\n" + "zRFOsDZrEqqcBsVIWC6EmjO29HS5qj/mXM0ktFGnNDxTZHoRkNgmCE/NH+fNKOFx\n" + "raCwlFBpKemAky+GdgngRGiQAVowyAx/nSmCFAalKc+E4ddoFwD/oft6iOvvXqaX\n" + "h6368wEQ7Hy48FDcUCbHtUEgK4wMrX9BSrRh6zkXO1tE4ghb0dM2qFDS0ypO3p04\n" + "kUPa31mTgLuOH1LzwmlwxOs113mlYKCgqOFR5YaN+nq1HI5RATPo5NvCMpG2RrQW\n" + "+ooCr2GtbT0oHmJv8yaBVY0HJ69eLnIv37dfjWvoTiBKBBIisXAD5Nm9rwSjZUSF\n" + "u1iyd7u2YrkBCUzZuvt3BOPpX8GgQgagU6BPnac76FF6DMhRUXlBXdTuWsbuH14L\n" + "dNIzGjkMZhNL/Tpkf6S/z1iH5VReGc+clTjWGg1XO5fr3mNKBGa7hDydIZRIMbgs\n" + "y63DIY7n5dqhNkO30CGmr/9TagVZAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAEVr\n" + "4skCpwuMuR+3WCmH6S17sYzWMYogJCGQdbZtFqmf4W3EDlNClk4HszAeUdmROMj6\n" + "MdqNDUnDM/GPxHB4Aje1DZOH1h68CCAl9W32LFRDC0KaUOquuYIG4rnZADJl6P4T\n" + "WVlaXfuE2bQjE7iYPhWGNWJtkb7JNIHmB8EAIa4tt3+XJs+vZiSpVDpiP2ucgrCn\n" + "BltsK0iOMPDLVlXdk1hpU5HvlMXdBHQebfTiCFDQSX7ViKc4wSJUHDt4CyoCzchY\n" + "mbQIcTc7uNDE5chQWV8Z3Vxkp4yuqZM3HdLskoo4IgFDOoj8eCAi+58+YRuKpaEQ\n" + "fWt+A9rvlaOApWryMW4="); + *privkey_data = strdup("MIIJKAIBAAKCAgEA6ojtjfDmvyQP1ZkIwBpr97eKDuebvpoglRHRdvVuTpf/gU1V\n" + "ArAQmwGh05i6lm8TkVl1noMlIxLJDcWslaeVn6KyvsX0HhsQtXwqPqwka5UCv6al\n" + "wf/ivAvcNpcX1j0t/uIGCI4dSiKnzQCyf0FTirzQkjrDZUd3meDhNQTruCalGV4g\n" + "fNWIq3e1oGuwAn1tLlu9oTrE4HzMpgbNEU6wNmsSqpwGxUhYLoSaM7b0dLmqP+Zc\n" + "zSS0Uac0PFNkehGQ2CYIT80f580o4XGtoLCUUGkp6YCTL4Z2CeBEaJABWjDIDH+d\n" + "KYIUBqUpz4Th12gXAP+h+3qI6+9eppeHrfrzARDsfLjwUNxQJse1QSArjAytf0FK\n" + "tGHrORc7W0TiCFvR0zaoUNLTKk7enTiRQ9rfWZOAu44fUvPCaXDE6zXXeaVgoKCo\n" + "4VHlho36erUcjlEBM+jk28IykbZGtBb6igKvYa1tPSgeYm/zJoFVjQcnr14uci/f\n" + "t1+Na+hOIEoEEiKxcAPk2b2vBKNlRIW7WLJ3u7ZiuQEJTNm6+3cE4+lfwaBCBqBT\n" + "oE+dpzvoUXoMyFFReUFd1O5axu4fXgt00jMaOQxmE0v9OmR/pL/PWIflVF4Zz5yV\n" + "ONYaDVc7l+veY0oEZruEPJ0hlEgxuCzLrcMhjufl2qE2Q7fQIaav/1NqBVkCAwEA\n" + "AQKCAgAeRZw75Oszoqj0jfMmMILdD3Cfad+dY3FvLESYESeyt0XAX8XoOed6ymQj\n" + "1qPGxQGGkkBvPEgv1b3jrC8Rhfb3Ct39Z7mRpTar5iHhwwBUboBTUmQ0vR173iAH\n" + "X8sw2Oa17mCO/CDlr8Fu4Xcom7r3vlVBepo72VSjpPYMjN0MANjwhEi3NCyWzTXB\n" + "RgUK3TuZbzfzto0w2Irlpx0S7dAqxfk70jXBgwv2vSDWKfg1lL1X0BkMVX98xpMk\n" + "cjMW2muSqp4KBtTma4GqT6z0f7Y1Bs3lGLZmvPlBXxQVVvkFtiQsENCtSd/h17Gk\n" + "2mb4EbReaaBzwCYqJdRWtlpJ54kzy8U00co+Yn//ZS7sbbIDkqHPnXkpdIr+0rED\n" + "MlOw2Y3vRZCxqZFqfWCW0uzhwKqk2VoYqtDL+ORKG/aG/KTBQ4Y71Uh+7aabPwj5\n" + "R+NaVMjbqmrVeH70eKjoNVgcNYY1C9rGVF1d+LQEm7UsqS0DPp4wN9QKLAqIfuar\n" + "AhQBhZy1R7Sj1r5macD9DsGxsurM4mHZV0LNmYLZiFHjTUb6iRSPD5RBFW80vcNt\n" + "xZ0cxmkLtxrj/DVyExV11Cl0SbZLLa9mScYvxdl/qZutXt3PQyab0NiYxGzCD2Rn\n" + "LkCyxkh1vuHHjhvIWYfbd2VgZB/qGr+o9T07FGfMCu23//fugQKCAQEA9UH38glH\n" + "/rAjZ431sv6ryUEFY8I2FyLTijtvoj9CNGcQn8vJQAHvUPfMdyqDoum6wgcTmG+U\n" + "XA6mZzpGQCiY8JW5CoItgXRoYgNzpvVVe2aLf51QGtNLLEFpNDMpCtI+I+COpAmG\n" + "vWAukku0pZfRjm9eb1ydvTpHlFC9+VhVUsLzw3VtSC5PVW6r65mZcYcB6SFVPap+\n" + "31ENP/9jOMFoymh57lSMZJMxTEA5b0l2miFb9Rp906Zqiud5zv2jIqF6gL70giW3\n" + "ovVxR7LGKKTKIa9pxawHwB6Ithygs7YoJkjF2dm8pZTMZKsQN92K70XGj07SmYRL\n" + "ZpkVD7i+cqbbKQKCAQEA9M6580Rcw6W0twfcy0/iB4U5ZS52EcCjW8vHlL+MpUo7\n" + "YvXadSgV1ZaM28zW/ZGk3wE0zy1YT5s30SQkm0NiWN3t/J0l19ccAOxlPWfjhF7v\n" + "IQZr7XMo5HeaK0Ak5+68J6bx6KgcXmlJOup7INaE8DyGXB6vd4K6957IXyqs3/bf\n" + "JAUmz49hnveCfLFdTVVT/Uq4IoPKfQSbSZc0BvPBsnBCF164l4jllGBaWS302dhg\n" + "W4cgxzG0SZGgNwow4AhB+ygiiS8yvOa7UcHfUObVrzWeeq9mYSQ1PkvUTjkWR2/Y\n" + "8xy7WP0TRBdJOVSs90H51lerEDGNQWvQvI97S9ZOsQKCAQB59u9lpuXtqwxAQCFy\n" + "fSFSuQoEHR2nDcOjF4GhbtHum15yCPaw5QVs/33nuPWze4ZLXReKk9p0mTh5V0p+\n" + "N3IvGlXl+uzEVu5d55eI7LIw5sLymHmwjWjxvimiMtrzLbCHSPHGc5JU9NLUH9/b\n" + "BY/JxGpy+NzcsHHOOQTwTdRIjviIOAo7fgQn2RyX0k+zXE8/7zqjqvji9zyemdNu\n" + "8we4uJICSntyvJwkbj/hrufTKEnBrwXpzfVn1EsH+6w32ZPBGLUhT75txJ8r56SR\n" + "q7l1XPU9vxovmT+lSMFF/Y0j1MbHWnds5H1shoFPNtYTvWBL/gfPHjIc+H23zsiu\n" + "3XlZAoIBAC2xB/Pnpoi9vOUMiqFH36AXtYa1DURy+AqCFlYlClMvb7YgvQ1w1eJv\n" + "nwrHSLk7HdKhnwGsLPduuRRH8q0n/osnoOutSQroE0n41UyIv2ZNccRwNmSzQcai\n" + "rBu2dSz02hlsh2otNl5IuGpOqXyPjXBpW4qGD6n2tH7THALnLC0BHtTSQVQsJsRM\n" + "3gX39LoiWvLDp2qJvplm6rTpi8Rgap6rZSqHe1yNKIxxD2vlr/WY9SMgLXYASO4S\n" + "SBz9wfGOmQIPk6KXNJkdV4kC7nNjIi75iwLLCgjHgUiHTrDq5sWekpeNnUoWsinb\n" + "Tsdsjnv3zHG9GyiClyLGxMbs4M5eyYECggEBAKuC8ZMpdIrjk6tERYB6g0LnQ7mW\n" + "8XYbDFAmLYMLs9yfG2jcjVbsW9Kugsr+3poUUv/q+hNO3jfY4HazhZDa0MalgNPo\n" + "Swr/VNRnkck40x2ovFb989J7yl++zTrnIrax9XRH1V0cNu+Kj7OMwZ2RRfbNv5JB\n" + "dOZPvkfqyIKFmbQgYbtD66rHuzNOfJpzqr/WVLO57/zzW8245NKG2B6B0oXkei/K\n" + "qDY0DAbHR3i3EOj1NPtVI1FC/xX8R9BREaid458bqoHJKuInrGcBjaUI9Cvymv8T\n" + "bstUgD6NPbJR4Sm6vrLeUqzjWZP3t1+Z6DjXmnpR2vvhMU/FWb//21p/88o="); + *privkey_data_rsa = 1; + return 0; + } else if (!strcmp(name, "main_cert")) { + *cert_path = strdup(TESTS_DIR"/data/server.crt"); + *privkey_path = strdup(TESTS_DIR"/data/server.key"); + return 0; + } - pthread_barrier_wait(&barrier); + return 1; +} - ret = nc_server_tls_endpt_set_address("quaternary", "0.0.0.0"); - nc_assert(!ret); +static int +clb_trusted_cert_lists(const char *name, void *UNUSED(user_data), char ***cert_paths, int *cert_path_count, + char ***cert_data, int *cert_data_count) +{ + if (!strcmp(name, "trusted_cert_list1")) { + *cert_data = malloc(sizeof **cert_data); + (*cert_data)[0] = strdup("MIIDgzCCAmugAwIBAgIJAL+y0WMRGax0MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV\n" + "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" + "aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMMCGNsaWVudGNhMB4XDTE2MDExMTEyMTAx\n" + "OVoXDTE4MTAzMTEyMTAxOVowWDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt\n" + "U3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UE\n" + "AwwIY2xpZW50Y2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw7Eyq\n" + "5T5tX6tAv5DHHfWNuaD/a3gVIBlGRWMAXkFWWJEa3o6leIjKxoDnL6tcBWNVJ+Gw\n" + "32MHerpHY6o5czsEHQ2XsOgodyFqe5cvx0kjQbjYQqnIMrslcdvSYuNe/ItqFP/w\n" + "uxb6kQbCYnCQKd/qhdhfoXjIHcnXpZzMCPKQ/uqls7LANJymtQkAuzydlf3+UqoG\n" + "4oo04GXK1Dc0A12cgCXxf+kWx7x34ctx2VEvDsJzw6LiZm8czOWjMFcuqqm/+kla\n" + "N3+6O7Z1kZlft/KNSrOYtc45xKNoSVrdVwFLkxipVDfOql6/DmWfE8iVmlX3QflO\n" + "u3+fzZZQpR5jYzUNAgMBAAGjUDBOMB0GA1UdDgQWBBTjBbQJ6p/mjnjBWXLgXXXW\n" + "a3ieoTAfBgNVHSMEGDAWgBTjBbQJ6p/mjnjBWXLgXXXWa3ieoTAMBgNVHRMEBTAD\n" + "AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAZr9b0YTaDV5XZr/QQPP1pvHkN3Ezbm9F4\n" + "MiYe4e0QnM9JtjNLDKq1dDnqVDQ/BYdupWWh0398tObFACssWkm4aubPG7LVh5Ck\n" + "O8I8i/GHiXYLmYT22hslWe5dFvidUICkTXoj1h5X2vwfBrNTI1+gnVXXw842xCvU\n" + "sgq28vGMSXLSYKBNaP/llXNmqW35oLs6CwVuiCL7Go0IDIOmiXN2bssb87hZSw3B\n" + "6iwU78wYshJUGZjLaK9PuMvFYJLFWSAePA2Yb+aEv80wMbX1oANSryU7Uf5BJk8V\n" + "kO3mlRDh2b1/5Gb5xA2vU2z3ReHdPNy6qSx0Mk4XJvQw9FsVHZ13"); + *cert_data_count = 1; + return 0; + } else if (!strcmp(name, "client_cert_list")) { + *cert_paths = malloc(sizeof **cert_paths); + (*cert_paths)[0] = strdup(TESTS_DIR"/data/client.crt"); + *cert_path_count = 1; + return 0; + } - return NULL; + return 1; } static void * -tls_endpt_set_port_thread(void *arg) +endpt_set_address_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_set_port("quaternary", 6505); + ret = nc_server_endpt_set_address("quaternary", "0.0.0.0"); nc_assert(!ret); return NULL; } static void * -tls_endpt_set_cert_thread(void *arg) +endpt_set_port_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_set_cert("quaternary", "MIIEKjCCAxICCQDqSTPpuoUZkzANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJB\n" - "VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\n" - "cyBQdHkgTHRkMREwDwYDVQQDDAhzZXJ2ZXJjYTAeFw0xNjAyMDgxMTE0MzdaFw0y\n" - "NjAyMDUxMTE0MzdaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRl\n" - "MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMMBnNl\n" - "cnZlcjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOqI7Y3w5r8kD9WZ\n" - "CMAaa/e3ig7nm76aIJUR0Xb1bk6X/4FNVQKwEJsBodOYupZvE5FZdZ6DJSMSyQ3F\n" - "rJWnlZ+isr7F9B4bELV8Kj6sJGuVAr+mpcH/4rwL3DaXF9Y9Lf7iBgiOHUoip80A\n" - "sn9BU4q80JI6w2VHd5ng4TUE67gmpRleIHzViKt3taBrsAJ9bS5bvaE6xOB8zKYG\n" - "zRFOsDZrEqqcBsVIWC6EmjO29HS5qj/mXM0ktFGnNDxTZHoRkNgmCE/NH+fNKOFx\n" - "raCwlFBpKemAky+GdgngRGiQAVowyAx/nSmCFAalKc+E4ddoFwD/oft6iOvvXqaX\n" - "h6368wEQ7Hy48FDcUCbHtUEgK4wMrX9BSrRh6zkXO1tE4ghb0dM2qFDS0ypO3p04\n" - "kUPa31mTgLuOH1LzwmlwxOs113mlYKCgqOFR5YaN+nq1HI5RATPo5NvCMpG2RrQW\n" - "+ooCr2GtbT0oHmJv8yaBVY0HJ69eLnIv37dfjWvoTiBKBBIisXAD5Nm9rwSjZUSF\n" - "u1iyd7u2YrkBCUzZuvt3BOPpX8GgQgagU6BPnac76FF6DMhRUXlBXdTuWsbuH14L\n" - "dNIzGjkMZhNL/Tpkf6S/z1iH5VReGc+clTjWGg1XO5fr3mNKBGa7hDydIZRIMbgs\n" - "y63DIY7n5dqhNkO30CGmr/9TagVZAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAEVr\n" - "4skCpwuMuR+3WCmH6S17sYzWMYogJCGQdbZtFqmf4W3EDlNClk4HszAeUdmROMj6\n" - "MdqNDUnDM/GPxHB4Aje1DZOH1h68CCAl9W32LFRDC0KaUOquuYIG4rnZADJl6P4T\n" - "WVlaXfuE2bQjE7iYPhWGNWJtkb7JNIHmB8EAIa4tt3+XJs+vZiSpVDpiP2ucgrCn\n" - "BltsK0iOMPDLVlXdk1hpU5HvlMXdBHQebfTiCFDQSX7ViKc4wSJUHDt4CyoCzchY\n" - "mbQIcTc7uNDE5chQWV8Z3Vxkp4yuqZM3HdLskoo4IgFDOoj8eCAi+58+YRuKpaEQ\n" - "fWt+A9rvlaOApWryMW4="); + ret = nc_server_endpt_set_port("quaternary", 6003); nc_assert(!ret); - nc_thread_destroy(); return NULL; } static void * -tls_endpt_set_key_thread(void *arg) +tls_endpt_set_server_cert_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_set_key("quaternary", "MIIJKAIBAAKCAgEA6ojtjfDmvyQP1ZkIwBpr97eKDuebvpoglRHRdvVuTpf/gU1V\n" - "ArAQmwGh05i6lm8TkVl1noMlIxLJDcWslaeVn6KyvsX0HhsQtXwqPqwka5UCv6al\n" - "wf/ivAvcNpcX1j0t/uIGCI4dSiKnzQCyf0FTirzQkjrDZUd3meDhNQTruCalGV4g\n" - "fNWIq3e1oGuwAn1tLlu9oTrE4HzMpgbNEU6wNmsSqpwGxUhYLoSaM7b0dLmqP+Zc\n" - "zSS0Uac0PFNkehGQ2CYIT80f580o4XGtoLCUUGkp6YCTL4Z2CeBEaJABWjDIDH+d\n" - "KYIUBqUpz4Th12gXAP+h+3qI6+9eppeHrfrzARDsfLjwUNxQJse1QSArjAytf0FK\n" - "tGHrORc7W0TiCFvR0zaoUNLTKk7enTiRQ9rfWZOAu44fUvPCaXDE6zXXeaVgoKCo\n" - "4VHlho36erUcjlEBM+jk28IykbZGtBb6igKvYa1tPSgeYm/zJoFVjQcnr14uci/f\n" - "t1+Na+hOIEoEEiKxcAPk2b2vBKNlRIW7WLJ3u7ZiuQEJTNm6+3cE4+lfwaBCBqBT\n" - "oE+dpzvoUXoMyFFReUFd1O5axu4fXgt00jMaOQxmE0v9OmR/pL/PWIflVF4Zz5yV\n" - "ONYaDVc7l+veY0oEZruEPJ0hlEgxuCzLrcMhjufl2qE2Q7fQIaav/1NqBVkCAwEA\n" - "AQKCAgAeRZw75Oszoqj0jfMmMILdD3Cfad+dY3FvLESYESeyt0XAX8XoOed6ymQj\n" - "1qPGxQGGkkBvPEgv1b3jrC8Rhfb3Ct39Z7mRpTar5iHhwwBUboBTUmQ0vR173iAH\n" - "X8sw2Oa17mCO/CDlr8Fu4Xcom7r3vlVBepo72VSjpPYMjN0MANjwhEi3NCyWzTXB\n" - "RgUK3TuZbzfzto0w2Irlpx0S7dAqxfk70jXBgwv2vSDWKfg1lL1X0BkMVX98xpMk\n" - "cjMW2muSqp4KBtTma4GqT6z0f7Y1Bs3lGLZmvPlBXxQVVvkFtiQsENCtSd/h17Gk\n" - "2mb4EbReaaBzwCYqJdRWtlpJ54kzy8U00co+Yn//ZS7sbbIDkqHPnXkpdIr+0rED\n" - "MlOw2Y3vRZCxqZFqfWCW0uzhwKqk2VoYqtDL+ORKG/aG/KTBQ4Y71Uh+7aabPwj5\n" - "R+NaVMjbqmrVeH70eKjoNVgcNYY1C9rGVF1d+LQEm7UsqS0DPp4wN9QKLAqIfuar\n" - "AhQBhZy1R7Sj1r5macD9DsGxsurM4mHZV0LNmYLZiFHjTUb6iRSPD5RBFW80vcNt\n" - "xZ0cxmkLtxrj/DVyExV11Cl0SbZLLa9mScYvxdl/qZutXt3PQyab0NiYxGzCD2Rn\n" - "LkCyxkh1vuHHjhvIWYfbd2VgZB/qGr+o9T07FGfMCu23//fugQKCAQEA9UH38glH\n" - "/rAjZ431sv6ryUEFY8I2FyLTijtvoj9CNGcQn8vJQAHvUPfMdyqDoum6wgcTmG+U\n" - "XA6mZzpGQCiY8JW5CoItgXRoYgNzpvVVe2aLf51QGtNLLEFpNDMpCtI+I+COpAmG\n" - "vWAukku0pZfRjm9eb1ydvTpHlFC9+VhVUsLzw3VtSC5PVW6r65mZcYcB6SFVPap+\n" - "31ENP/9jOMFoymh57lSMZJMxTEA5b0l2miFb9Rp906Zqiud5zv2jIqF6gL70giW3\n" - "ovVxR7LGKKTKIa9pxawHwB6Ithygs7YoJkjF2dm8pZTMZKsQN92K70XGj07SmYRL\n" - "ZpkVD7i+cqbbKQKCAQEA9M6580Rcw6W0twfcy0/iB4U5ZS52EcCjW8vHlL+MpUo7\n" - "YvXadSgV1ZaM28zW/ZGk3wE0zy1YT5s30SQkm0NiWN3t/J0l19ccAOxlPWfjhF7v\n" - "IQZr7XMo5HeaK0Ak5+68J6bx6KgcXmlJOup7INaE8DyGXB6vd4K6957IXyqs3/bf\n" - "JAUmz49hnveCfLFdTVVT/Uq4IoPKfQSbSZc0BvPBsnBCF164l4jllGBaWS302dhg\n" - "W4cgxzG0SZGgNwow4AhB+ygiiS8yvOa7UcHfUObVrzWeeq9mYSQ1PkvUTjkWR2/Y\n" - "8xy7WP0TRBdJOVSs90H51lerEDGNQWvQvI97S9ZOsQKCAQB59u9lpuXtqwxAQCFy\n" - "fSFSuQoEHR2nDcOjF4GhbtHum15yCPaw5QVs/33nuPWze4ZLXReKk9p0mTh5V0p+\n" - "N3IvGlXl+uzEVu5d55eI7LIw5sLymHmwjWjxvimiMtrzLbCHSPHGc5JU9NLUH9/b\n" - "BY/JxGpy+NzcsHHOOQTwTdRIjviIOAo7fgQn2RyX0k+zXE8/7zqjqvji9zyemdNu\n" - "8we4uJICSntyvJwkbj/hrufTKEnBrwXpzfVn1EsH+6w32ZPBGLUhT75txJ8r56SR\n" - "q7l1XPU9vxovmT+lSMFF/Y0j1MbHWnds5H1shoFPNtYTvWBL/gfPHjIc+H23zsiu\n" - "3XlZAoIBAC2xB/Pnpoi9vOUMiqFH36AXtYa1DURy+AqCFlYlClMvb7YgvQ1w1eJv\n" - "nwrHSLk7HdKhnwGsLPduuRRH8q0n/osnoOutSQroE0n41UyIv2ZNccRwNmSzQcai\n" - "rBu2dSz02hlsh2otNl5IuGpOqXyPjXBpW4qGD6n2tH7THALnLC0BHtTSQVQsJsRM\n" - "3gX39LoiWvLDp2qJvplm6rTpi8Rgap6rZSqHe1yNKIxxD2vlr/WY9SMgLXYASO4S\n" - "SBz9wfGOmQIPk6KXNJkdV4kC7nNjIi75iwLLCgjHgUiHTrDq5sWekpeNnUoWsinb\n" - "Tsdsjnv3zHG9GyiClyLGxMbs4M5eyYECggEBAKuC8ZMpdIrjk6tERYB6g0LnQ7mW\n" - "8XYbDFAmLYMLs9yfG2jcjVbsW9Kugsr+3poUUv/q+hNO3jfY4HazhZDa0MalgNPo\n" - "Swr/VNRnkck40x2ovFb989J7yl++zTrnIrax9XRH1V0cNu+Kj7OMwZ2RRfbNv5JB\n" - "dOZPvkfqyIKFmbQgYbtD66rHuzNOfJpzqr/WVLO57/zzW8245NKG2B6B0oXkei/K\n" - "qDY0DAbHR3i3EOj1NPtVI1FC/xX8R9BREaid458bqoHJKuInrGcBjaUI9Cvymv8T\n" - "bstUgD6NPbJR4Sm6vrLeUqzjWZP3t1+Z6DjXmnpR2vvhMU/FWb//21p/88o=", 1); + ret = nc_server_tls_endpt_set_server_cert("quaternary", "server_cert1"); nc_assert(!ret); nc_thread_destroy(); @@ -413,32 +439,14 @@ tls_endpt_set_key_thread(void *arg) } static void * -tls_endpt_add_trusted_cert_thread(void *arg) +tls_endpt_add_trusted_cert_list_thread(void *arg) { (void)arg; int ret; pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_add_trusted_cert("quaternary", "cert1", "MIIDgzCCAmugAwIBAgIJAL+y0WMRGax0MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV\n" - "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" - "aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMMCGNsaWVudGNhMB4XDTE2MDExMTEyMTAx\n" - "OVoXDTE4MTAzMTEyMTAxOVowWDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUt\n" - "U3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UE\n" - "AwwIY2xpZW50Y2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw7Eyq\n" - "5T5tX6tAv5DHHfWNuaD/a3gVIBlGRWMAXkFWWJEa3o6leIjKxoDnL6tcBWNVJ+Gw\n" - "32MHerpHY6o5czsEHQ2XsOgodyFqe5cvx0kjQbjYQqnIMrslcdvSYuNe/ItqFP/w\n" - "uxb6kQbCYnCQKd/qhdhfoXjIHcnXpZzMCPKQ/uqls7LANJymtQkAuzydlf3+UqoG\n" - "4oo04GXK1Dc0A12cgCXxf+kWx7x34ctx2VEvDsJzw6LiZm8czOWjMFcuqqm/+kla\n" - "N3+6O7Z1kZlft/KNSrOYtc45xKNoSVrdVwFLkxipVDfOql6/DmWfE8iVmlX3QflO\n" - "u3+fzZZQpR5jYzUNAgMBAAGjUDBOMB0GA1UdDgQWBBTjBbQJ6p/mjnjBWXLgXXXW\n" - "a3ieoTAfBgNVHSMEGDAWgBTjBbQJ6p/mjnjBWXLgXXXWa3ieoTAMBgNVHRMEBTAD\n" - "AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAZr9b0YTaDV5XZr/QQPP1pvHkN3Ezbm9F4\n" - "MiYe4e0QnM9JtjNLDKq1dDnqVDQ/BYdupWWh0398tObFACssWkm4aubPG7LVh5Ck\n" - "O8I8i/GHiXYLmYT22hslWe5dFvidUICkTXoj1h5X2vwfBrNTI1+gnVXXw842xCvU\n" - "sgq28vGMSXLSYKBNaP/llXNmqW35oLs6CwVuiCL7Go0IDIOmiXN2bssb87hZSw3B\n" - "6iwU78wYshJUGZjLaK9PuMvFYJLFWSAePA2Yb+aEv80wMbX1oANSryU7Uf5BJk8V\n" - "kO3mlRDh2b1/5Gb5xA2vU2z3ReHdPNy6qSx0Mk4XJvQw9FsVHZ13"); + ret = nc_server_tls_endpt_add_trusted_cert_list("quaternary", "trusted_cert_list1"); nc_assert(!ret); nc_thread_destroy(); @@ -461,13 +469,13 @@ tls_endpt_set_trusted_ca_paths_thread(void *arg) } static void * -tls_endpt_clear_certs_thread(void *arg) +tls_endpt_del_trusted_cert_list_thread(void *arg) { (void)arg; pthread_barrier_wait(&barrier); - nc_server_tls_endpt_del_trusted_cert("quaternary", "cert1"); + nc_server_tls_endpt_del_trusted_cert_list("quaternary", "trusted_cert_list1"); return NULL; } @@ -507,7 +515,7 @@ tls_endpt_add_ctn_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_add_ctn("main", 0, "02:F0:F1:F2:F3:F4:F5:F6:F7:F8:F9:10:11:12:EE:FF:A0:A1:A2:A3", + ret = nc_server_tls_endpt_add_ctn("main_tls", 2, "02:F0:F1:F2:F3:F4:F5:F6:F7:F8:F9:10:11:12:EE:FF:A0:A1:A2:A3", NC_TLS_CTN_SAN_IP_ADDRESS, NULL); nc_assert(!ret); @@ -522,7 +530,7 @@ tls_endpt_del_ctn_thread(void *arg) pthread_barrier_wait(&barrier); - ret = nc_server_tls_endpt_del_ctn("main", -1, NULL, NC_TLS_CTN_SAN_ANY, NULL); + ret = nc_server_tls_endpt_del_ctn("main_tls", -1, NULL, NC_TLS_CTN_SAN_ANY, NULL); nc_assert(!ret); return NULL; @@ -560,12 +568,10 @@ tls_client_thread(void *arg) static void *(*thread_funcs[])(void *) = { #if defined(NC_ENABLED_SSH) || defined(NC_ENABLED_TLS) server_thread, - add_endpt_thread, - del_endpt_thread, #endif #ifdef NC_ENABLED_SSH - ssh_endpt_set_address_thread, - ssh_endpt_set_port_thread, + add_endpt_thread, + del_endpt_thread, ssh_endpt_set_hostkey_thread, ssh_endpt_set_banner_thread, ssh_endpt_set_auth_methods_thread, @@ -575,13 +581,12 @@ static void *(*thread_funcs[])(void *) = { ssh_endpt_del_authkey_thread, #endif #ifdef NC_ENABLED_TLS - tls_endpt_set_address_thread, - tls_endpt_set_port_thread, - tls_endpt_set_cert_thread, - tls_endpt_set_key_thread, - tls_endpt_add_trusted_cert_thread, + endpt_set_address_thread, + endpt_set_port_thread, + tls_endpt_set_server_cert_thread, + tls_endpt_add_trusted_cert_list_thread, tls_endpt_set_trusted_ca_paths_thread, - tls_endpt_clear_certs_thread, + tls_endpt_del_trusted_cert_list_thread, tls_endpt_set_crl_paths_thread, tls_endpt_clear_crls_thread, tls_endpt_add_ctn_thread, @@ -669,18 +674,20 @@ main(void) pthread_barrier_init(&barrier, NULL, thread_count); - ret = nc_server_add_endpt("main"); - nc_assert(!ret); - #ifdef NC_ENABLED_SSH + /* set callback */ + nc_server_ssh_set_hostkey_clb(clb_hostkeys, NULL, NULL); + /* do first, so that client can connect on SSH */ - ret = nc_server_ssh_endpt_set_address("main", "0.0.0.0"); + ret = nc_server_add_endpt("main_ssh", NC_TI_LIBSSH); + nc_assert(!ret); + ret = nc_server_endpt_set_address("main_ssh", "0.0.0.0"); nc_assert(!ret); - ret = nc_server_ssh_endpt_set_port("main", 6001); + ret = nc_server_endpt_set_port("main_ssh", 6001); nc_assert(!ret); - ret = nc_server_ssh_endpt_add_authkey("main", TESTS_DIR"/data/key_dsa.pub", "test"); + ret = nc_server_ssh_add_authkey_path(TESTS_DIR"/data/key_dsa.pub", "test"); nc_assert(!ret); - ret = nc_server_ssh_endpt_add_hostkey("main", TESTS_DIR"/data/key_rsa"); + ret = nc_server_ssh_endpt_add_hostkey("main_ssh", "key_rsa", -1); nc_assert(!ret); /* client ready */ @@ -689,23 +696,30 @@ main(void) ++clients; /* for ssh_endpt_del_authkey */ - ret = nc_server_ssh_endpt_add_authkey("main", TESTS_DIR"/data/key_ecdsa.pub", "test2"); + ret = nc_server_ssh_add_authkey_path(TESTS_DIR"/data/key_ecdsa.pub", "test2"); + nc_assert(!ret); + + ret = nc_server_add_endpt("secondary", NC_TI_LIBSSH); nc_assert(!ret); #endif #ifdef NC_ENABLED_TLS + /* set callbacks */ + nc_server_tls_set_server_cert_clb(clb_server_cert, NULL, NULL); + nc_server_tls_set_trusted_cert_list_clb(clb_trusted_cert_lists, NULL, NULL); + /* do first, so that client can connect on TLS */ - ret = nc_server_tls_endpt_set_address("main", "0.0.0.0"); + ret = nc_server_add_endpt("main_tls", NC_TI_OPENSSL); nc_assert(!ret); - ret = nc_server_tls_endpt_set_port("main", 6501); + ret = nc_server_endpt_set_address("main_tls", "0.0.0.0"); nc_assert(!ret); - ret = nc_server_tls_endpt_set_cert_path("main", TESTS_DIR"/data/server.crt"); + ret = nc_server_endpt_set_port("main_tls", 6501); nc_assert(!ret); - ret = nc_server_tls_endpt_set_key_path("main", TESTS_DIR"/data/server.key"); + ret = nc_server_tls_endpt_set_server_cert("main_tls", "main_cert"); nc_assert(!ret); - ret = nc_server_tls_endpt_add_trusted_cert_path("main", "client", TESTS_DIR"/data/client.crt"); + ret = nc_server_tls_endpt_add_trusted_cert_list("main_tls", "client_cert_list"); nc_assert(!ret); - ret = nc_server_tls_endpt_add_ctn("main", 0, "02:D3:03:0E:77:21:E2:14:1F:E5:75:48:98:6B:FD:8A:63:BB:DE:40:34", NC_TLS_CTN_SPECIFIED, "test"); + ret = nc_server_tls_endpt_add_ctn("main_tls", 0, "02:D3:03:0E:77:21:E2:14:1F:E5:75:48:98:6B:FD:8A:63:BB:DE:40:34", NC_TLS_CTN_SPECIFIED, "test"); nc_assert(!ret); /* client ready */ @@ -714,17 +728,12 @@ main(void) ++clients; /* for tls_endpt_del_ctn */ - ret = nc_server_tls_endpt_add_ctn("main", 0, "02:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:A0:A1:A2:A3", NC_TLS_CTN_SAN_ANY, NULL); + ret = nc_server_tls_endpt_add_ctn("main_tls", 1, "02:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:A0:A1:A2:A3", NC_TLS_CTN_SAN_ANY, NULL); nc_assert(!ret); -#endif - /* for del_endpt */ - ret = nc_server_add_endpt("secondary"); - nc_assert(!ret); - - /* for endpt_set_address, endpt_set_port */ - ret = nc_server_add_endpt("quaternary"); + ret = nc_server_add_endpt("quaternary", NC_TI_OPENSSL); nc_assert(!ret); +#endif /* threads'n'stuff */ ret = 0; diff --git a/tests/test_server_thread.supp b/tests/test_server_thread.supp index 6f52e9c8..8eb257d5 100644 --- a/tests/test_server_thread.supp +++ b/tests/test_server_thread.supp @@ -8,3 +8,8 @@ Helgrind:Race fun:DSA_get_default_method } +{ + + Helgrind:Race + fun:nc_server_endpt_set_address_port +}