1
0
Fork 0

Merging upstream version 3.5.5 (Closes: #1098233).

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

View file

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

403
src/io.c
View file

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

View file

@ -1,669 +0,0 @@
/**
* @file libnetconf.h
* @author Radek Krejci <rkrejci@cesnet.cz>
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief libnetconf2 main internal header.
*
* Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#ifndef NC_LIBNETCONF_H_
#define NC_LIBNETCONF_H_
#include "config.h"
#include "log_p.h"
#include "messages_p.h"
#include "netconf.h"
#include "session_p.h"
/* Tests whether string is empty or non-empty. */
#define strisempty(str) ((str)[0] == '\0')
#define strnonempty(str) ((str)[0] != '\0')
/**
* @mainpage About
*
* libnetconf2 is a NETCONF library in C handling NETCONF authentication and all NETCONF
* RPC communication both server and client-side. Note that NETCONF datastore implementation
* is not a part of this library. The library supports both NETCONF 1.0
* ([RFC 4741](https://tools.ietf.org/html/rfc4741)) as well as NETCONF 1.1
* ([RFC 6241](https://tools.ietf.org/html/rfc6241)).
*
* @section about-features Main Features
*
* - Creating SSH ([RFC 4742](https://tools.ietf.org/html/rfc4742), [RFC 6242](https://tools.ietf.org/html/rfc6242)),
* using [libssh](https://www.libssh.org/), or TLS ([RFC 7589](https://tools.ietf.org/html/rfc7589)),
* using [OpenSSL](https://www.openssl.org/), authenticated NETCONF sessions.
* - Creating NETCONF sessions with a pre-established transport protocol
* (using this mechanism the communication can be tunneled through sshd(8), for instance).
* - Creating NETCONF Call Home sessions ([RFC 8071](https://tools.ietf.org/html/rfc8071)).
* - Creating, sending, receiving, and replying to RPCs ([RFC 4741](https://tools.ietf.org/html/rfc4741),
* [RFC 6241](https://tools.ietf.org/html/rfc6241)).
* - Creating, sending and receiving NETCONF Event Notifications ([RFC 5277](https://tools.ietf.org/html/rfc5277)),
*
* @section about-license License
*
* Copyright (c) 2015-2021 CESNET, z.s.p.o.
*
* (The BSD 3-Clause License)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of the Company nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*/
/**
* @page howto How To ...
*
* - @subpage howtoinit
* - @subpage howtoclient
* - @subpage howtoserver
* - @subpage howtoclientcomm
* - @subpage howtoservercomm
* - @subpage howtotimeouts
*/
/**
* @page howtoinit Init and Thread-safety Information
*
* Before working with the library, it must be initialized using ::nc_client_init()
* or ::nc_server_init(). Based on how the library was compiled, also _libssh_ and/or
* _libssh_/_libcrypto_ are initialized (for multi-threaded use) too. To prevent
* any reachable memory at the end of your application, there are complementary
* destroy functions (::nc_server_destroy() and ::nc_client_destroy() available. If your
* application is multi-threaded, call the destroy functions in the main thread,
* after all the other threads have ended. In every other thread you should call
* ::nc_thread_destroy() just before it exits.
*
* If _libnetconf2_ is used in accordance with this information, there should
* not be memory leaks of any kind at program exit. For thread-safety details
* of _libssh_, _libssl_, and _libcrypto_, please refer to the corresponding project
* documentation. _libnetconf2_ thread-safety information is below.
*
* Client
* ------
*
* Optionally, a client can specify two alternative ways to get schemas needed when connecting
* with a server. The primary way is to read local files in searchpath (and its subdirectories)
* specified via ::nc_client_set_schema_searchpath(). Alternatively, _libnetconf2_ can use callback
* provided via ::nc_client_set_schema_callback(). If these ways do not succeed and the server
* implements NETCONF \<get-schema\> operation, the schema is retrieved from the server and stored
* localy into the searchpath (if specified) for a future use. If none of these methods succeed to
* load particular schema, the data from this schema are ignored during the communication with the
* server.
*
* Besides the mentioned setters, there are many other @ref howtoclientssh "SSH", @ref howtoclienttls "TLS"
* and @ref howtoclientch "Call Home" getter/setter functions to manipulate with various settings. All these
* settings are internally placed in a thread-specific context so they are independent and
* initialized to the default values within each new thread. However, the context can be shared among
* the threads using ::nc_client_get_thread_context() and ::nc_client_set_thread_context() functions. In such
* a case, be careful and avoid concurrent execution of the mentioned setters/getters and functions
* creating connection (no matter if it is a standard NETCONF connection or Call Home).
*
* In the client, it is thread-safe to work with distinguish NETCONF sessions since the client
* settings are thread-specific as described above.
*
* Server
* ------
*
* Server is __FULLY__ thread-safe meaning you can set all the (thread-shared in contrast to
* client) options simultaneously while listening for or accepting new sessions or
* 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
* --------------
*
* Available in __nc_client.h__.
*
* - ::nc_client_init()
* - ::nc_client_destroy()
*
* - ::nc_client_set_schema_searchpath()
* - ::nc_client_get_schema_searchpath()
* - ::nc_client_set_schema_callback()
* - ::nc_client_get_schema_callback()
*
* - ::nc_client_set_thread_context()
* - ::nc_client_get_thread_context()
*
* Available in __nc_server.h__.
*
* - ::nc_server_init()
* - ::nc_server_destroy()
*
* Available in both __nc_client.h__ and __nc_server.h__.
*
* - ::nc_thread_destroy()
*/
/**
* @page howtoclient Client sessions
*
* To connect to a NETCONF server, a NETCONF session must be established,
* which requires a working transport session. It is possible to create
* NETCONF sessions with SSH (using _libssh_) or TLS (using _libssl/libcrypto_)
* as the underlying transport protocol. It is also possible to establish
* the transport protocol outside _libnetconf2_ and then provide these file
* descriptors (FD) for full NETCONF session creation.
*
* There are a lot of options for both an SSH and a TLS client. All of them
* have setters and getters so that there is no need to duplicate them in
* a client.
*
* @anchor howtoclientssh
* SSH
* ===
*
* Connecting to a server using SSH does not strictly require to set any
* options, there are sensible default values for all the basic ones.
* Except all the SSH options, optionally some authetication callbacks can be set,
* which are particulary useful in automated clients (passwords cannot be
* asked a user) or simply if any additional information is retrieved some
* other way than from standard terminal input.
*
* Having the default options or changing any unsuitable ones, there are 2 functions
* to use for a new server connection. ::nc_connect_ssh() is the standard function
* that creates sessions using the set options. If there are some options, which
* cannot be changed with the provided API, there is ::nc_connect_libssh() available.
* It requires a _libssh_ session, in which all the SSH options can be modified
* and even the connection established. This allows for full customization and
* should fit any specific situation.
*
* New NETCONF sessions can also be created on existing authenticated SSH sessions.
* There is a new SSH channel needed, on which the NETCONF session is then created.
* Use ::nc_connect_ssh_channel() for this purpose.
*
* Functions List
* --------------
*
* Available in __nc_client.h__.
*
* - ::nc_client_ssh_set_auth_hostkey_check_clb()
* - ::nc_client_ssh_get_auth_hostkey_check_clb()
* - ::nc_client_ssh_set_auth_password_clb()
* - ::nc_client_ssh_get_auth_password_clb()
* - ::nc_client_ssh_set_auth_interactive_clb()
* - ::nc_client_ssh_get_auth_interactive_clb()
* - ::nc_client_ssh_set_auth_privkey_passphrase_clb()
* - ::nc_client_ssh_get_auth_privkey_passphrase_clb()
* - ::nc_client_ssh_add_keypair()
* - ::nc_client_ssh_del_keypair()
* - ::nc_client_ssh_get_keypair_count()
* - ::nc_client_ssh_get_keypair()
* - ::nc_client_ssh_set_auth_pref()
* - ::nc_client_ssh_get_auth_pref()
* - ::nc_client_ssh_set_username()
* - ::nc_client_ssh_get_username()
*
* - ::nc_connect_ssh()
* - ::nc_connect_libssh()
* - ::nc_connect_ssh_channel()
*
* @anchor howtoclienttls
* TLS
* ===
*
* To connect to a server using TLS, there must be some client identification
* options set. Client must specify its certificate with a private key using
* ::nc_client_tls_set_cert_key_paths(). Also, the Certificate Authority of
* a server certificate must be considered trusted. Paths to all the trusted
* CA certificates can be set by ::nc_client_tls_set_trusted_ca_paths().
*
* Then there are again 2 functions for connecting, ::nc_connect_tls() being
* the standard way of connecting. ::nc_connect_libssl() again enables
* to customize the TLS session in every way _libssl_ allows.
*
* Functions List
* --------------
*
* Available in __nc_client.h__.
*
* - ::nc_client_tls_set_cert_key_paths()
* - ::nc_client_tls_get_cert_key_paths()
* - ::nc_client_tls_set_trusted_ca_paths()
* - ::nc_client_tls_get_trusted_ca_paths()
* - ::nc_client_tls_set_crl_paths()
* - ::nc_client_tls_get_crl_paths()
*
* - ::nc_connect_tls()
* - ::nc_connect_libssl()
*
*
* FD and UNIX socket
* ==================
*
* If you authenticated the connection using some tunneling software, you
* can pass its file descriptors to _libnetconf2_ using ::nc_connect_inout(),
* which will continue to establish a full NETCONF session. To connect locally
* on a UNIX socket avoiding all cryptography use ::nc_connect_unix().
*
* Funtions List
* -------------
*
* Available in __nc_client.h__.
*
* - ::nc_connect_inout()
* - ::nc_connect_unix()
*
*
* @anchor howtoclientch
* Call Home
* =========
*
* Call Home needs the same options set as standard SSH or TLS and the functions
* reflect it exactly. However, to accept a connection, the client must first
* specify addresses and ports, which to listen on by ::nc_client_ssh_ch_add_bind_listen()
* and ::nc_client_tls_ch_add_bind_listen(). Then connections can be
* accepted using ::nc_accept_callhome().
*
* Functions List
* --------------
*
* Available in __nc_client.h__.
*
* - ::nc_client_ssh_ch_set_auth_hostkey_check_clb()
* - ::nc_client_ssh_ch_set_auth_password_clb()
* - ::nc_client_ssh_ch_set_auth_interactive_clb()
* - ::nc_client_ssh_ch_set_auth_privkey_passphrase_clb()
* - ::nc_client_ssh_ch_add_bind_listen()
* - ::nc_client_ssh_ch_del_bind()
* - ::nc_client_ssh_ch_add_keypair()
* - ::nc_client_ssh_ch_del_keypair()
* - ::nc_client_ssh_ch_get_keypair_count()
* - ::nc_client_ssh_ch_get_keypair()
* - ::nc_client_ssh_ch_set_auth_pref()
* - ::nc_client_ssh_ch_get_auth_pref()
* - ::nc_client_ssh_ch_set_username()
* - ::nc_client_ssh_ch_get_username()
*
* - ::nc_client_tls_ch_add_bind_listen()
* - ::nc_client_tls_ch_del_bind()
* - ::nc_client_tls_ch_set_cert_key_paths()
* - ::nc_client_tls_ch_get_cert_key_paths()
* - ::nc_client_tls_ch_set_trusted_ca_paths()
* - ::nc_client_tls_ch_get_trusted_ca_paths()
* - ::nc_client_tls_ch_set_crl_paths()
* - ::nc_client_tls_ch_get_crl_paths()
*
* - ::nc_accept_callhome()
*
*
* Cleanup
* =======
*
* These options and the schema searchpath are stored in dynamically
* allocated memory. They are freed as a part of [destroying the client](@ref howtoinit).
*/
/**
* @page howtoserver Server sessions
*
* Init
* ====
*
* Server takes an argument for its [initialization function](@ref howtoinit).
* In it, you set the server context, which determines what modules it
* supports and what capabilities to advertise. Few capabilities that
* cannot be learnt from the context are set with separate functions
* ::nc_server_set_capab_withdefaults() and generally ::nc_server_set_capability().
* Timeout for receiving the _hello_ message on a new session can be set
* by ::nc_server_set_hello_timeout() and the timeout for disconnecting
* an inactive session by ::nc_server_set_idle_timeout().
*
* Context does not only determine server modules, but its overall
* functionality as well. For every RPC the server should support,
* an nc_rpc_clb callback should be set on that node in the context using ::nc_set_rpc_callback().
* Server then calls these as appropriate [during poll](@ref howtoservercomm).
*
* Just like in the [client](@ref howtoclient), you can let _libnetconf2_
* establish SSH or TLS transport or do it yourself and only provide the file
* descriptors of the connection.
*
* Server options can be only set, there are no getters.
*
* To be able to accept any connections, endpoints must first be added
* with ::nc_server_add_endpt() and configured with ::nc_server_endpt_set_address()
* and ::nc_server_endpt_set_port(). For unix sockets, ::nc_server_endpt_set_perms()
* is available to set the unix socket file permissions, and ::nc_server_endpt_set_port()
* is invalid.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_set_capab_withdefaults()
* - ::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()
* - ::nc_server_endpt_set_perms()
*
*
* SSH
* ===
*
* 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_add_hostkey()
* - ::nc_server_ssh_endpt_del_hostkey()
* - ::nc_server_ssh_endpt_mov_hostkey()
* - ::nc_server_ssh_endpt_mod_hostkey()
* - ::nc_server_ssh_endpt_set_auth_methods()
* - ::nc_server_ssh_endpt_set_auth_attempts()
* - ::nc_server_ssh_endpt_set_auth_timeout()
*
* - ::nc_server_ssh_set_hostkey_clb()
*
* - ::nc_server_ssh_add_authkey()
* - ::nc_server_ssh_add_authkey_path()
* - ::nc_server_ssh_del_authkey()
*
*
* TLS
* ===
*
* TLS works with endpoints too, but its options differ
* significantly from the SSH ones, especially in the _cert-to-name_
* options that TLS uses to derive usernames from client certificates.
* So, after starting listening on an endpoint you need to set the server
* certificate (::nc_server_tls_endpt_set_server_cert()). Its actual content
* together with the matching private key will be loaded using a callback
* from ::nc_server_tls_set_server_cert_clb(). Additional certificates needed
* for the client to verify the server's certificate chain can be loaded using
* a callback from ::nc_server_tls_set_server_cert_chain_clb().
*
* To accept client certificates, they must first be considered trusted,
* which you have three ways of achieving. You can add each of their Certificate Authority
* certificates to the trusted ones or mark a specific client certificate
* as trusted. Lastly, you can set paths with all the trusted CA certificates
* with ::nc_server_tls_endpt_set_trusted_ca_paths(). Adding specific certificates
* is also performed only as an arbitrary identificator and later retrieved from
* callback set by ::nc_server_tls_set_trusted_cert_list_clb(). But, you can add
* certficates as whole lists, not one-by-one.
*
* 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_list().
* To clear all Certificate Revocation Lists use ::nc_server_tls_endpt_clear_crls().
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_server_tls_endpt_set_server_cert()
* - ::nc_server_tls_endpt_add_trusted_cert_list()
* - ::nc_server_tls_endpt_del_trusted_cert_list()
* - ::nc_server_tls_endpt_set_trusted_ca_paths()
* - ::nc_server_tls_endpt_set_crl_paths()
* - ::nc_server_tls_endpt_clear_crls()
* - ::nc_server_tls_endpt_add_ctn()
* - ::nc_server_tls_endpt_del_ctn()
* - ::nc_server_tls_endpt_get_ctn()
*
* - ::nc_server_tls_set_server_cert_clb()
* - ::nc_server_tls_set_server_cert_chain_clb()
* - ::nc_server_tls_set_trusted_cert_list_clb()
*
* FD
* ==
*
* If you used a tunneling software, which does its own authentication,
* you can accept a NETCONF session on its file descriptors with
* ::nc_accept_inout().
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_accept_inout()
*
*
* Call Home
* =========
*
* _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_server_ch_add_client()
* - ::nc_server_ch_del_client()
* - ::nc_server_ch_is_client()
* - ::nc_server_ch_client_add_endpt()
* - ::nc_server_ch_client_del_endpt()
* - ::nc_server_ch_client_is_endpt()
* - ::nc_server_ch_client_endpt_set_address()
* - ::nc_server_ch_client_endpt_set_port()
* - ::nc_server_ch_client_endpt_enable_keepalives()
* - ::nc_server_ch_client_endpt_set_keepalives()
* - ::nc_server_ch_client_set_conn_type()
* - ::nc_server_ch_client_periodic_set_period()
* - ::nc_server_ch_client_periodic_set_anchor_time()
* - ::nc_server_ch_client_periodic_set_idle_timeout()
* - ::nc_server_ch_client_set_start_with()
* - ::nc_server_ch_client_set_max_attempts()
* - ::nc_connect_ch_client_dispatch()
*
* - ::nc_server_ssh_ch_client_endpt_add_hostkey()
* - ::nc_server_ssh_ch_client_endpt_del_hostkey()
* - ::nc_server_ssh_ch_client_endpt_mov_hostkey()
* - ::nc_server_ssh_ch_client_endpt_set_auth_methods()
* - ::nc_server_ssh_ch_client_endpt_get_auth_methods()
* - ::nc_server_ssh_ch_client_endpt_set_auth_attempts()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
*
* - ::nc_server_tls_ch_client_endpt_set_server_cert()
* - ::nc_server_tls_ch_client_endpt_add_trusted_cert_list()
* - ::nc_server_tls_ch_client_endpt_del_trusted_cert_list()
* - ::nc_server_tls_ch_client_endpt_set_trusted_ca_paths()
* - ::nc_server_tls_ch_client_endpt_set_crl_paths()
* - ::nc_server_tls_ch_client_endpt_clear_crls()
* - ::nc_server_tls_ch_client_endpt_add_ctn()
* - ::nc_server_tls_ch_client_endpt_del_ctn()
* - ::nc_server_tls_ch_client_endpt_get_ctn()
*
*
* 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 CH clients,
* endpoints, and free any used dynamic memory, [destroy](@ref howtoinit) the server.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_accept()
*/
/**
* @page howtoclientcomm Client communication
*
* To send RPCs on a session, you simply create an RPC, send it using ::nc_send_rpc(),
* and then wait for a reply using ::nc_recv_reply(). If you are subscribed, there are 2 ways
* of receiving notifications. Either you wait for them the same way
* as for standard replies with ::nc_recv_notif() or you create a dispatcher
* with ::nc_recv_notif_dispatch() that asynchronously (in a separate thread)
* reads notifications and passes them to your callback.
*
* Functions List
* --------------
*
* Available in __nc_client.h__.
*
* - ::nc_rpc_act_generic()
* - ::nc_rpc_act_generic_xml()
* - ::nc_rpc_getconfig()
* - ::nc_rpc_edit()
* - ::nc_rpc_copy()
* - ::nc_rpc_delete()
* - ::nc_rpc_lock()
* - ::nc_rpc_unlock()
* - ::nc_rpc_get()
* - ::nc_rpc_kill()
* - ::nc_rpc_commit()
* - ::nc_rpc_discard()
* - ::nc_rpc_cancel()
* - ::nc_rpc_validate()
* - ::nc_rpc_getschema()
* - ::nc_rpc_subscribe()
* - ::nc_rpc_getdata()
* - ::nc_rpc_editdata()
* - ::nc_rpc_establishsub()
* - ::nc_rpc_modifysub()
* - ::nc_rpc_deletesub()
* - ::nc_rpc_killsub()
* - ::nc_rpc_establishpush_periodic()
* - ::nc_rpc_establishpush_onchange()
* - ::nc_rpc_modifypush_periodic()
* - ::nc_rpc_modifypush_onchange()
* - ::nc_rpc_resyncsub()
*
* - ::nc_send_rpc()
* - ::nc_recv_reply()
* - ::nc_recv_notif()
* - ::nc_recv_notif_dispatch()
*/
/**
* @page howtoservercomm Server communication
*
* Once at least one session is established, an nc_pollsession structure
* should be created with ::nc_ps_new(), filled with the session using
* ::nc_ps_add_session() and finally polled with ::nc_ps_poll(). Based on
* the return value from the poll, further actions can be taken. More
* sessions can be polled at the same time and any requests received on
* the sessions are [handled internally](@ref howtoserver).
*
* If an SSH NETCONF session asks for a new channel, you can accept
* this request with ::nc_ps_accept_ssh_channel() or ::nc_session_accept_ssh_channel()
* depending on the structure you want to use as the argument.
*
* Functions List
* --------------
*
* Available in __nc_server.h__.
*
* - ::nc_ps_new()
* - ::nc_ps_add_session()
* - ::nc_ps_del_session()
* - ::nc_ps_session_count()
* - ::nc_ps_free()
*
* - ::nc_ps_poll()
* - ::nc_ps_clear()
* - ::nc_ps_accept_ssh_channel()
* - ::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_get_hello_timeout()
* - ::nc_server_set_idle_timeout()
* - ::nc_server_get_idle_timeout()
* - ::nc_server_ch_client_periodic_set_idle_timeout()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
* - ::nc_server_ssh_ch_client_endpt_set_auth_timeout()
*/
/**
* @defgroup misc Miscellaneous
* @brief Miscellaneous macros, types, structure and functions for a generic use by both server and client applications.
*/
/**
* @defgroup client Client
* @brief NETCONF client functionality.
*/
/**
* @defgroup server Server
* @brief NETCONF server functionality.
*/
#endif /* NC_LIBNETCONF_H_ */

113
src/log.c
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

4707
src/server_config.c Normal file

File diff suppressed because it is too large Load diff

1442
src/server_config.h Normal file

File diff suppressed because it is too large Load diff

445
src/server_config_ks.c Normal file
View file

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

169
src/server_config_p.h Normal file
View file

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

600
src/server_config_ts.c Normal file
View file

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

1366
src/server_config_util.c Normal file

File diff suppressed because it is too large Load diff

169
src/server_config_util.h Normal file
View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

2014
src/session_mbedtls.c Normal file

File diff suppressed because it is too large Load diff

1494
src/session_openssl.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

742
src/session_wrapper.h Normal file
View file

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