| /** |
| * @file server_config.c |
| * @author Roman Janota <janota@cesnet.cz> |
| * @brief libnetconf2 server configuration functions |
| * |
| * @copyright |
| * Copyright (c) 2022-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 <ctype.h> |
| #include <pthread.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <libyang/libyang.h> |
| #include <libyang/tree_data.h> |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| #include <openssl/err.h> |
| #include <openssl/evp.h> // EVP_PKEY_free |
| #include <openssl/x509.h> // d2i_PUBKEY |
| #include <openssl/x509_vfy.h> // X509_STORE_free |
| #endif |
| |
| #include "compat.h" |
| #include "config.h" |
| #include "log_p.h" |
| #include "server_config.h" |
| #include "server_config_p.h" |
| #include "session_p.h" |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| /* All libssh supported host-key, key-exchange, encryption and mac algorithms as of version 0.10.90 */ |
| |
| static const char *supported_hostkey_algs[] = { |
| "openssh-ssh-ed25519-cert-v01", "openssh-ecdsa-sha2-nistp521-cert-v01", |
| "openssh-ecdsa-sha2-nistp384-cert-v01", "openssh-ecdsa-sha2-nistp256-cert-v01", |
| "openssh-rsa-sha2-512-cert-v01", "openssh-rsa-sha2-256-cert-v01", |
| "openssh-ssh-rsa-cert-v01", "openssh-ssh-dss-cert-v01", |
| "ssh-ed25519", "ecdsa-sha2-nistp521", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp256", |
| "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa", "ssh-dss", NULL |
| }; |
| |
| static const char *supported_kex_algs[] = { |
| "diffie-hellman-group-exchange-sha1", "curve25519-sha256", "libssh-curve25519-sha256", |
| "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group18-sha512", |
| "diffie-hellman-group16-sha512", "diffie-hellman-group-exchange-sha256", "diffie-hellman-group14-sha256", NULL |
| }; |
| |
| static const char *supported_encryption_algs[] = { |
| "openssh-chacha20-poly1305", "openssh-aes256-gcm", "openssh-aes128-gcm", |
| "aes256-ctr", "aes192-ctr", "aes128-ctr", "aes256-cbc", "aes192-cbc", "aes128-cbc", |
| "blowfish-cbc", "triple-des-cbc", "none", NULL |
| }; |
| |
| static const char *supported_mac_algs[] = { |
| "openssh-hmac-sha2-256-etm", "openssh-hmac-sha2-512-etm", "openssh-hmac-sha1-etm", |
| "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", NULL |
| }; |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| extern struct nc_server_opts server_opts; |
| |
| /* returns a parent node of 'node' that matches the name 'name' */ |
| static const struct lyd_node * |
| nc_server_config_get_parent(const struct lyd_node *node, const char *name) |
| { |
| NC_CHECK_ARG_RET(NULL, node, name, NULL); |
| |
| while (node) { |
| if (!strcmp(LYD_NAME(node), name)) { |
| return node; |
| } |
| node = lyd_parent(node); |
| } |
| |
| return NULL; |
| } |
| |
| /* returns a parent list node of 'node' that matches the name 'name' */ |
| static const struct lyd_node * |
| nc_server_config_get_parent_list(const struct lyd_node *node, const char *name) |
| { |
| NC_CHECK_ARG_RET(NULL, node, name, NULL); |
| |
| while (node) { |
| /* check if the node is a list and its name matches the param */ |
| if ((node->schema->nodetype == LYS_LIST) && (!strcmp(LYD_NAME(node), name))) { |
| return node; |
| } |
| node = lyd_parent(node); |
| } |
| |
| return NULL; |
| } |
| |
| /* returns the key of a list node with the name 'name' */ |
| static const char * |
| nc_server_config_get_parent_list_key_value(const struct lyd_node *node, const char *name, const char *key_name) |
| { |
| const char *original_name; |
| |
| NC_CHECK_ARG_RET(NULL, node, name, key_name, NULL); |
| original_name = LYD_NAME(node); |
| |
| /* get the supposed parent list */ |
| node = nc_server_config_get_parent_list(node, name); |
| if (!node) { |
| ERR(NULL, "Node \"%s\" not contained in \"%s\" subtree.", original_name, name); |
| return NULL; |
| } |
| |
| /* child should be the key */ |
| node = lyd_child(node); |
| if (!node) { |
| ERR(NULL, "Node \"%s\" has no child nodes.", name); |
| return NULL; |
| } |
| if (strcmp(LYD_NAME(node), key_name)) { |
| ERR(NULL, "Node \"%s\" child names mismatch (found:\"%s\", expected:\"%s\").", original_name, LYD_NAME(node), key_name); |
| return NULL; |
| } |
| |
| return lyd_get_value(node); |
| } |
| |
| /* returns true if a node is a part of the listen subtree */ |
| static int |
| is_listen(const struct lyd_node *node) |
| { |
| node = nc_server_config_get_parent(node, "listen"); |
| return node != NULL; |
| } |
| |
| /* returns true if a node is a part of the Call Home subtree */ |
| static int |
| is_ch(const struct lyd_node *node) |
| { |
| node = nc_server_config_get_parent(node, "call-home"); |
| return node != NULL; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| /* returns true if a node is a part of the ssh subtree */ |
| static int |
| is_ssh(const struct lyd_node *node) |
| { |
| node = nc_server_config_get_parent(node, "ssh"); |
| return node != NULL; |
| } |
| |
| /* returns true if a node is a part of the tls subtree */ |
| static int |
| is_tls(const struct lyd_node *node) |
| { |
| node = nc_server_config_get_parent(node, "tls"); |
| return node != NULL; |
| } |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| /* gets the endpoint struct (and optionally bind) based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_endpt(const struct lyd_node *node, struct nc_endpt **endpt, struct nc_bind **bind) |
| { |
| uint16_t i; |
| const char *name; |
| |
| NC_CHECK_ARG_RET(NULL, node, endpt, 1); |
| |
| name = nc_server_config_get_parent_list_key_value(node, "endpoint", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < server_opts.endpt_count; i++) { |
| if (!strcmp(server_opts.endpts[i].name, name)) { |
| *endpt = &server_opts.endpts[i]; |
| if (bind) { |
| *bind = &server_opts.binds[i]; |
| } |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Endpoint \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| /* gets the ch_client struct based on node's location in the YANG data tree |
| * THE ch_client_lock HAS TO BE LOCKED PRIOR TO CALLING THIS |
| */ |
| static int |
| nc_server_config_get_ch_client(const struct lyd_node *node, struct nc_ch_client **ch_client) |
| { |
| uint16_t i; |
| const char *name; |
| |
| NC_CHECK_ARG_RET(NULL, node, ch_client, 1); |
| |
| name = nc_server_config_get_parent_list_key_value(node, "netconf-client", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < server_opts.ch_client_count; i++) { |
| if (!strcmp(server_opts.ch_clients[i].name, name)) { |
| *ch_client = &server_opts.ch_clients[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Call-home client \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| /* gets the ch_endpt struct based on node's location in the YANG data tree, |
| * ch_client has to be locked |
| */ |
| static int |
| nc_server_config_get_ch_endpt(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_ch_endpt **ch_endpt) |
| { |
| uint16_t i; |
| const char *name; |
| |
| NC_CHECK_ARG_RET(NULL, node, ch_client, ch_endpt, 1); |
| |
| name = nc_server_config_get_parent_list_key_value(node, "endpoint", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < ch_client->ch_endpt_count; i++) { |
| if (!strcmp(ch_client->ch_endpts[i].name, name)) { |
| *ch_endpt = &ch_client->ch_endpts[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Call-home client's \"%s\" endpoint \"%s\" was not found.", ch_client->name, name); |
| return 1; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| /* gets the ssh_opts struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_ssh_opts(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_server_ssh_opts **opts) |
| { |
| struct nc_endpt *endpt; |
| struct nc_ch_endpt *ch_endpt; |
| |
| NC_CHECK_ARG_RET(NULL, node, opts, 1); |
| |
| if (is_listen(node)) { |
| if (nc_server_config_get_endpt(node, &endpt, NULL)) { |
| return 1; |
| } |
| *opts = endpt->opts.ssh; |
| } else { |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| return 1; |
| } |
| *opts = ch_endpt->opts.ssh; |
| } |
| |
| return 0; |
| } |
| |
| /* gets the hostkey struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_hostkey(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_hostkey **hostkey) |
| { |
| uint16_t i; |
| const char *name; |
| struct nc_server_ssh_opts *opts; |
| |
| NC_CHECK_ARG_RET(NULL, node, hostkey, 1); |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| return 1; |
| } |
| |
| name = nc_server_config_get_parent_list_key_value(node, "host-key", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < opts->hostkey_count; i++) { |
| if (!strcmp(opts->hostkeys[i].name, name)) { |
| *hostkey = &opts->hostkeys[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Host-key \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| /* gets the client_auth struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_auth_client(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_auth_client **auth_client) |
| { |
| uint16_t i; |
| const char *name; |
| struct nc_server_ssh_opts *opts; |
| |
| NC_CHECK_ARG_RET(NULL, node, auth_client, 1); |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| return 1; |
| } |
| |
| name = nc_server_config_get_parent_list_key_value(node, "user", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < opts->client_count; i++) { |
| if (!strcmp(opts->auth_clients[i].username, name)) { |
| *auth_client = &opts->auth_clients[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Authorized key \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| /* gets the pubkey struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_pubkey(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_public_key **pubkey) |
| { |
| uint16_t i; |
| const char *name; |
| struct nc_auth_client *auth_client; |
| |
| NC_CHECK_ARG_RET(NULL, node, pubkey, 1); |
| |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| return 1; |
| } |
| |
| name = nc_server_config_get_parent_list_key_value(node, "public-key", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| for (i = 0; i < auth_client->pubkey_count; i++) { |
| if (!strcmp(auth_client->pubkeys[i].name, name)) { |
| *pubkey = &auth_client->pubkeys[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "Public key \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| /* gets the tls_opts struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_tls_opts(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_server_tls_opts **opts) |
| { |
| struct nc_endpt *endpt; |
| struct nc_ch_endpt *ch_endpt; |
| |
| NC_CHECK_ARG_RET(NULL, node, opts, 1); |
| |
| if (is_listen(node)) { |
| if (nc_server_config_get_endpt(node, &endpt, NULL)) { |
| return 1; |
| } |
| *opts = endpt->opts.tls; |
| } else { |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| return 1; |
| } |
| *opts = ch_endpt->opts.tls; |
| } |
| |
| return 0; |
| } |
| |
| /* gets the cert struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_cert(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_certificate **cert) |
| { |
| uint16_t i; |
| const char *name; |
| struct nc_cert_grouping *certs; |
| struct nc_server_tls_opts *opts; |
| int is_cert_end_entity; |
| const struct lyd_node *tmp; |
| |
| NC_CHECK_ARG_RET(NULL, node, cert, 1); |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| return 1; |
| } |
| |
| name = nc_server_config_get_parent_list_key_value(node, "certificate", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| /* it's in certificate subtree, now check if it's end entity or certificate authority */ |
| tmp = nc_server_config_get_parent(node, "ee-certs"); |
| if (tmp) { |
| is_cert_end_entity = 1; |
| } else { |
| tmp = nc_server_config_get_parent(node, "ca-certs"); |
| if (!tmp) { |
| ERR(NULL, "Node \"%s\" is not contained in ee-certs nor ca-certs subtree.", name); |
| return 1; |
| } |
| is_cert_end_entity = 0; |
| } |
| |
| /* get the right cert stack, either ee or ca */ |
| if (is_cert_end_entity) { |
| certs = &opts->ee_certs; |
| } else { |
| certs = &opts->ca_certs; |
| } |
| |
| for (i = 0; i < certs->cert_count; i++) { |
| if (!strcmp(certs->certs[i].name, name)) { |
| *cert = &certs->certs[i]; |
| return 0; |
| } |
| } |
| |
| ERR(NULL, "%s certificate \"%s\" was not found.", is_cert_end_entity ? "End-entity" : "Certificate authority", name); |
| return 1; |
| } |
| |
| /* gets the ctn struct based on node's location in the YANG data tree */ |
| static int |
| nc_server_config_get_ctn(const struct lyd_node *node, const struct nc_ch_client *ch_client, |
| struct nc_ctn **ctn) |
| { |
| uint32_t id; |
| struct nc_ctn *iter; |
| struct nc_server_tls_opts *opts; |
| const char *name; |
| |
| NC_CHECK_ARG_RET(NULL, node, ctn, 1); |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| return 1; |
| } |
| |
| name = LYD_NAME(node); |
| node = nc_server_config_get_parent_list(node, "cert-to-name"); |
| if (!node) { |
| ERR(NULL, "Node \"%s\" is not contained in a cert-to-name subtree.", name); |
| return 1; |
| } |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "id")); |
| id = ((struct lyd_node_term *)node)->value.uint32; |
| |
| iter = opts->ctn; |
| while (iter) { |
| if (iter->id == id) { |
| *ctn = iter; |
| return 0; |
| } |
| |
| iter = iter->next; |
| } |
| |
| ERR(NULL, "Cert-to-name entry with id \"%d\" was not found.", id); |
| return 1; |
| } |
| |
| NC_PRIVKEY_FORMAT |
| nc_server_config_get_private_key_type(const char *format) |
| { |
| if (!strcmp(format, "rsa-private-key-format")) { |
| return NC_PRIVKEY_FORMAT_RSA; |
| } else if (!strcmp(format, "ec-private-key-format")) { |
| return NC_PRIVKEY_FORMAT_EC; |
| } else if (!strcmp(format, "private-key-info-format")) { |
| return NC_PRIVKEY_FORMAT_X509; |
| } else if (!strcmp(format, "openssh-private-key-format")) { |
| return NC_PRIVKEY_FORMAT_OPENSSH; |
| } else { |
| ERR(NULL, "Private key format (%s) not supported.", format); |
| return NC_PRIVKEY_FORMAT_UNKNOWN; |
| } |
| } |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| /* gets the ch_client struct based on node's location in the YANG data tree and locks it for reading */ |
| static int |
| nc_server_config_get_ch_client_with_lock(const struct lyd_node *node, struct nc_ch_client **ch_client) |
| { |
| uint16_t i; |
| const char *name; |
| |
| NC_CHECK_ARG_RET(NULL, node, ch_client, 1); |
| |
| name = nc_server_config_get_parent_list_key_value(node, "netconf-client", "name"); |
| if (!name) { |
| return 1; |
| } |
| |
| /* LOCK */ |
| pthread_rwlock_rdlock(&server_opts.ch_client_lock); |
| for (i = 0; i < server_opts.ch_client_count; i++) { |
| if (!strcmp(server_opts.ch_clients[i].name, name)) { |
| /* LOCK */ |
| pthread_mutex_lock(&server_opts.ch_clients[i].lock); |
| *ch_client = &server_opts.ch_clients[i]; |
| return 0; |
| } |
| } |
| |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| ERR(NULL, "Call-home client \"%s\" was not found.", name); |
| return 1; |
| } |
| |
| static void |
| nc_ch_client_unlock(struct nc_ch_client *client) |
| { |
| assert(client); |
| |
| pthread_mutex_unlock(&client->lock); |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| } |
| |
| int |
| equal_parent_name(const struct lyd_node *node, uint16_t parent_count, const char *parent_name) |
| { |
| uint16_t i; |
| |
| assert(node && parent_count && parent_name); |
| |
| node = lyd_parent(node); |
| for (i = 1; i < parent_count; i++) { |
| node = lyd_parent(node); |
| } |
| |
| if (!strcmp(LYD_NAME(node), parent_name)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| nc_server_config_realloc(const char *key_value, void **ptr, size_t size, uint16_t *count) |
| { |
| int ret = 0; |
| void *tmp; |
| char **name; |
| |
| tmp = realloc(*ptr, (*count + 1) * size); |
| NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup); |
| *ptr = tmp; |
| |
| /* set the newly allocated memory to 0 */ |
| memset((char *)(*ptr) + (*count * size), 0, size); |
| (*count)++; |
| |
| /* access the first member of the supposed structure */ |
| name = (char **)((*ptr) + ((*count - 1) * size)); |
| |
| /* and set it's value */ |
| *name = strdup(key_value); |
| NC_CHECK_ERRMEM_GOTO(!*name, ret = 1, cleanup); |
| |
| cleanup: |
| return ret; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| static void |
| nc_server_config_del_hostkey(struct nc_server_ssh_opts *opts, struct nc_hostkey *hostkey) |
| { |
| assert(hostkey->store == NC_STORE_LOCAL || hostkey->store == NC_STORE_KEYSTORE); |
| |
| free(hostkey->name); |
| |
| if (hostkey->store == NC_STORE_LOCAL) { |
| free(hostkey->key.pubkey_data); |
| free(hostkey->key.privkey_data); |
| } else { |
| free(hostkey->ks_ref); |
| } |
| |
| opts->hostkey_count--; |
| if (!opts->hostkey_count) { |
| free(opts->hostkeys); |
| opts->hostkeys = NULL; |
| } else if (hostkey != &opts->hostkeys[opts->hostkey_count]) { |
| memcpy(hostkey, &opts->hostkeys[opts->hostkey_count], sizeof *opts->hostkeys); |
| } |
| } |
| |
| static void |
| nc_server_config_del_auth_client_pubkey(struct nc_auth_client *auth_client, struct nc_public_key *pubkey) |
| { |
| free(pubkey->name); |
| free(pubkey->data); |
| |
| auth_client->pubkey_count--; |
| if (!auth_client->pubkey_count) { |
| free(auth_client->pubkeys); |
| auth_client->pubkeys = NULL; |
| } else if (pubkey != &auth_client->pubkeys[auth_client->pubkey_count]) { |
| memcpy(pubkey, &auth_client->pubkeys[auth_client->pubkey_count], sizeof *auth_client->pubkeys); |
| } |
| } |
| |
| static void |
| nc_server_config_del_auth_client(struct nc_server_ssh_opts *opts, struct nc_auth_client *auth_client) |
| { |
| uint16_t i, pubkey_count; |
| |
| free(auth_client->username); |
| |
| if (auth_client->store == NC_STORE_LOCAL) { |
| pubkey_count = auth_client->pubkey_count; |
| for (i = 0; i < pubkey_count; i++) { |
| nc_server_config_del_auth_client_pubkey(auth_client, &auth_client->pubkeys[i]); |
| } |
| } else if (auth_client->store == NC_STORE_TRUSTSTORE) { |
| free(auth_client->ts_ref); |
| } |
| |
| free(auth_client->password); |
| |
| opts->client_count--; |
| if (!opts->client_count) { |
| free(opts->auth_clients); |
| opts->auth_clients = NULL; |
| } else if (auth_client != &opts->auth_clients[opts->client_count]) { |
| memcpy(auth_client, &opts->auth_clients[opts->client_count], sizeof *opts->auth_clients); |
| } |
| } |
| |
| static void |
| nc_server_config_del_ssh_opts(struct nc_bind *bind, struct nc_server_ssh_opts *opts) |
| { |
| uint16_t i, hostkey_count, client_count; |
| |
| if (bind) { |
| free(bind->address); |
| if (bind->sock > -1) { |
| close(bind->sock); |
| } |
| } |
| |
| /* store in variable because it gets decremented in the function call */ |
| hostkey_count = opts->hostkey_count; |
| for (i = 0; i < hostkey_count; i++) { |
| nc_server_config_del_hostkey(opts, &opts->hostkeys[i]); |
| } |
| |
| client_count = opts->client_count; |
| for (i = 0; i < client_count; i++) { |
| nc_server_config_del_auth_client(opts, &opts->auth_clients[i]); |
| } |
| |
| free(opts->hostkey_algs); |
| free(opts->kex_algs); |
| free(opts->encryption_algs); |
| free(opts->mac_algs); |
| |
| free(opts); |
| } |
| |
| /* delete references to endpoint with the name 'referenced_endpt_name' from other endpts */ |
| static void |
| nc_server_config_del_endpt_references(const char *referenced_endpt_name) |
| { |
| uint16_t i, j; |
| |
| /* first go through listen endpoints */ |
| for (i = 0; i < server_opts.endpt_count; i++) { |
| if (server_opts.endpts[i].referenced_endpt_name) { |
| if (!strcmp(server_opts.endpts[i].referenced_endpt_name, referenced_endpt_name)) { |
| free(server_opts.endpts[i].referenced_endpt_name); |
| server_opts.endpts[i].referenced_endpt_name = NULL; |
| |
| if (server_opts.endpts[i].ti == NC_TI_LIBSSH) { |
| server_opts.endpts[i].opts.ssh->referenced_endpt_name = NULL; |
| } else { |
| server_opts.endpts[i].opts.tls->referenced_endpt_name = NULL; |
| } |
| } |
| } |
| } |
| |
| /* LOCK */ |
| pthread_rwlock_rdlock(&server_opts.ch_client_lock); |
| /* next go through ch endpoints */ |
| for (i = 0; i < server_opts.ch_client_count; i++) { |
| /* LOCK */ |
| pthread_mutex_lock(&server_opts.ch_clients[i].lock); |
| for (j = 0; j < server_opts.ch_clients[i].ch_endpt_count; j++) { |
| if (server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name) { |
| if (!strcmp(server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, referenced_endpt_name)) { |
| free(server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name); |
| server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name = NULL; |
| |
| if (server_opts.ch_clients[i].ch_endpts[j].ti == NC_TI_LIBSSH) { |
| server_opts.ch_clients[i].ch_endpts[j].opts.ssh->referenced_endpt_name = NULL; |
| } else { |
| server_opts.ch_clients[i].ch_endpts[j].opts.tls->referenced_endpt_name = NULL; |
| } |
| } |
| } |
| } |
| /* UNLOCK */ |
| pthread_mutex_unlock(&server_opts.ch_clients[i].lock); |
| } |
| |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| } |
| |
| void |
| nc_server_config_del_endpt_ssh(struct nc_endpt *endpt, struct nc_bind *bind) |
| { |
| /* delete any references to this endpoint */ |
| nc_server_config_del_endpt_references(endpt->name); |
| free(endpt->name); |
| |
| free(endpt->referenced_endpt_name); |
| nc_server_config_del_ssh_opts(bind, endpt->opts.ssh); |
| |
| server_opts.endpt_count--; |
| if (!server_opts.endpt_count) { |
| free(server_opts.endpts); |
| free(server_opts.binds); |
| server_opts.endpts = NULL; |
| server_opts.binds = NULL; |
| } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) { |
| memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); |
| memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds); |
| } |
| } |
| |
| static void |
| nc_server_config_del_cert(struct nc_cert_grouping *certs, struct nc_certificate *cert) |
| { |
| free(cert->name); |
| free(cert->data); |
| |
| certs->cert_count--; |
| if (!certs->cert_count) { |
| free(certs->certs); |
| certs->certs = NULL; |
| } else if (cert != &certs->certs[certs->cert_count]) { |
| memcpy(cert, &certs->certs[certs->cert_count], sizeof *certs->certs); |
| } |
| } |
| |
| static void |
| nc_server_config_del_certs(struct nc_cert_grouping *certs_grp) |
| { |
| uint16_t i; |
| |
| if (certs_grp->store == NC_STORE_LOCAL) { |
| for (i = 0; i < certs_grp->cert_count; i++) { |
| free(certs_grp->certs[i].name); |
| free(certs_grp->certs[i].data); |
| } |
| free(certs_grp->certs); |
| certs_grp->certs = NULL; |
| } else if (certs_grp->store == NC_STORE_TRUSTSTORE) { |
| free(certs_grp->ts_ref); |
| } |
| } |
| |
| static void |
| nc_server_config_del_ctn(struct nc_server_tls_opts *opts, struct nc_ctn *ctn) |
| { |
| struct nc_ctn *iter; |
| |
| free(ctn->name); |
| free(ctn->fingerprint); |
| |
| if (opts->ctn == ctn) { |
| /* it's the first in the list */ |
| opts->ctn = ctn->next; |
| free(ctn); |
| return; |
| } |
| |
| for (iter = opts->ctn; iter; iter = iter->next) { |
| if (iter->next == ctn) { |
| /* found the ctn */ |
| break; |
| } |
| } |
| |
| if (!iter) { |
| ERRINT; |
| return; |
| } |
| |
| iter->next = ctn->next; |
| free(ctn); |
| } |
| |
| static void |
| nc_server_config_del_ctns(struct nc_server_tls_opts *opts) |
| { |
| struct nc_ctn *cur, *next; |
| |
| for (cur = opts->ctn; cur; cur = next) { |
| next = cur->next; |
| free(cur->name); |
| free(cur->fingerprint); |
| free(cur); |
| } |
| |
| opts->ctn = NULL; |
| } |
| |
| static void |
| nc_server_config_del_tls_opts(struct nc_bind *bind, struct nc_server_tls_opts *opts) |
| { |
| if (bind) { |
| free(bind->address); |
| if (bind->sock > -1) { |
| close(bind->sock); |
| } |
| } |
| |
| if (opts->store == NC_STORE_LOCAL) { |
| free(opts->pubkey_data); |
| free(opts->privkey_data); |
| free(opts->cert_data); |
| } else if (opts->store == NC_STORE_KEYSTORE) { |
| free(opts->key_ref); |
| free(opts->cert_ref); |
| } |
| |
| nc_server_config_del_certs(&opts->ca_certs); |
| nc_server_config_del_certs(&opts->ee_certs); |
| |
| free(opts->crl_path); |
| free(opts->crl_url); |
| X509_STORE_free(opts->crl_store); |
| |
| nc_server_config_del_ctns(opts); |
| free(opts->ciphers); |
| free(opts); |
| } |
| |
| static void |
| nc_server_config_del_endpt_tls(struct nc_endpt *endpt, struct nc_bind *bind) |
| { |
| /* delete any references to this endpoint */ |
| nc_server_config_del_endpt_references(endpt->name); |
| free(endpt->name); |
| |
| free(endpt->referenced_endpt_name); |
| |
| nc_server_config_del_tls_opts(bind, endpt->opts.tls); |
| |
| server_opts.endpt_count--; |
| if (!server_opts.endpt_count) { |
| free(server_opts.endpts); |
| free(server_opts.binds); |
| server_opts.endpts = NULL; |
| server_opts.binds = NULL; |
| } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) { |
| memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); |
| memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds); |
| } |
| } |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| static void |
| nc_server_config_ch_del_endpt(struct nc_ch_client *ch_client, struct nc_ch_endpt *ch_endpt) |
| { |
| free(ch_endpt->name); |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| free(ch_endpt->address); |
| if (ch_endpt->sock_pending > -1) { |
| close(ch_endpt->sock_pending); |
| ch_endpt->sock_pending = -1; |
| } |
| free(ch_endpt->referenced_endpt_name); |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| switch (ch_endpt->ti) { |
| #ifdef NC_ENABLED_SSH_TLS |
| case NC_TI_LIBSSH: |
| nc_server_config_del_ssh_opts(NULL, ch_endpt->opts.ssh); |
| break; |
| case NC_TI_OPENSSL: |
| nc_server_config_del_tls_opts(NULL, ch_endpt->opts.tls); |
| break; |
| #endif /* NC_ENABLED_SSH_TLS */ |
| default: |
| ERRINT; |
| break; |
| } |
| |
| ch_client->ch_endpt_count--; |
| if (!ch_client->ch_endpt_count) { |
| free(ch_client->ch_endpts); |
| ch_client->ch_endpts = NULL; |
| } |
| } |
| |
| static void |
| nc_server_config_destroy_ch_client(struct nc_ch_client *ch_client) |
| { |
| pthread_t tid; |
| uint16_t i, ch_endpt_count; |
| |
| /* CH COND LOCK */ |
| pthread_mutex_lock(&ch_client->thread_data->cond_lock); |
| if (ch_client->thread_data->thread_running) { |
| ch_client->thread_data->thread_running = 0; |
| pthread_cond_signal(&ch_client->thread_data->cond); |
| /* CH COND UNLOCK */ |
| pthread_mutex_unlock(&ch_client->thread_data->cond_lock); |
| |
| /* get tid */ |
| tid = ch_client->tid; |
| |
| /* wait for the thread to terminate */ |
| pthread_join(tid, NULL); |
| } else { |
| /* CH COND UNLOCK */ |
| pthread_mutex_unlock(&ch_client->thread_data->cond_lock); |
| } |
| |
| /* free its members */ |
| free(ch_client->name); |
| |
| ch_endpt_count = ch_client->ch_endpt_count; |
| for (i = 0; i < ch_endpt_count; i++) { |
| nc_server_config_ch_del_endpt(ch_client, &ch_client->ch_endpts[i]); |
| } |
| } |
| |
| static void |
| nc_server_config_ch_del_client(const struct lyd_node *node) |
| { |
| struct nc_ch_client client, *ch_client; |
| |
| /* WR LOCK */ |
| pthread_rwlock_wrlock(&server_opts.ch_client_lock); |
| |
| if (nc_server_config_get_ch_client(node, &ch_client)) { |
| /* WR UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| ERR(NULL, "Call-home client \"%s\" not found.", lyd_get_value(lyd_child(node))); |
| return; |
| } |
| |
| /* copy the client we want to delete into a local variable */ |
| memcpy(&client, ch_client, sizeof *ch_client); |
| |
| /* delete the client */ |
| server_opts.ch_client_count--; |
| if (!server_opts.ch_client_count) { |
| free(server_opts.ch_clients); |
| server_opts.ch_clients = NULL; |
| } else { |
| memcpy(ch_client, &server_opts.ch_clients[server_opts.ch_client_count], sizeof *server_opts.ch_clients); |
| } |
| |
| /* WR UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| |
| nc_server_config_destroy_ch_client(&client); |
| } |
| |
| /* presence container */ |
| int |
| nc_server_config_listen(const struct lyd_node *node, NC_OPERATION op) |
| { |
| uint16_t i, endpt_count; |
| |
| (void) node; |
| |
| assert(op == NC_OP_CREATE || op == NC_OP_DELETE); |
| |
| if (op == NC_OP_DELETE) { |
| endpt_count = server_opts.endpt_count; |
| for (i = 0; i < endpt_count; i++) { |
| switch (server_opts.endpts[i].ti) { |
| #ifdef NC_ENABLED_SSH_TLS |
| case NC_TI_LIBSSH: |
| nc_server_config_del_endpt_ssh(&server_opts.endpts[i], &server_opts.binds[i]); |
| break; |
| case NC_TI_OPENSSL: |
| nc_server_config_del_endpt_tls(&server_opts.endpts[i], &server_opts.binds[i]); |
| break; |
| #endif /* NC_ENABLED_SSH_TLS */ |
| case NC_TI_UNIX: |
| break; |
| case NC_TI_NONE: |
| case NC_TI_FD: |
| ERRINT; |
| return 1; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| nc_server_config_ch(const struct lyd_node *node, NC_OPERATION op) |
| { |
| uint16_t i, ch_client_count; |
| struct nc_ch_client *ch_clients; |
| |
| (void) node; |
| |
| /* don't do anything if we're not deleting */ |
| if (op != NC_OP_DELETE) { |
| return 0; |
| } |
| |
| /* WR LOCK */ |
| pthread_rwlock_wrlock(&server_opts.ch_client_lock); |
| |
| ch_client_count = server_opts.ch_client_count; |
| ch_clients = server_opts.ch_clients; |
| |
| /* remove them from the server opts */ |
| server_opts.ch_client_count = 0; |
| server_opts.ch_clients = NULL; |
| |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| |
| for (i = 0; i < ch_client_count; i++) { |
| /* now destroy each client */ |
| nc_server_config_destroy_ch_client(&ch_clients[i]); |
| } |
| |
| free(ch_clients); |
| return 0; |
| } |
| |
| /* default leaf */ |
| static int |
| nc_server_config_idle_timeout(const struct lyd_node *node, NC_OPERATION op) |
| { |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "idle-timeout")); |
| |
| if (is_ch(node)) { |
| /* call-home idle timeout */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_client->idle_timeout = ((struct lyd_node_term *)node)->value.uint16; |
| } else if (op == NC_OP_DELETE) { |
| ch_client->idle_timeout = 180; |
| } |
| |
| nc_ch_client_unlock(ch_client); |
| } else { |
| /* listen idle timeout */ |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| server_opts.idle_timeout = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* default value */ |
| server_opts.idle_timeout = 180; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_create_bind(void) |
| { |
| int ret = 0; |
| void *tmp; |
| |
| tmp = realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds); |
| NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup); |
| server_opts.binds = tmp; |
| memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds); |
| |
| server_opts.binds[server_opts.endpt_count].sock = -1; |
| |
| cleanup: |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_endpoint(const struct lyd_node *node) |
| { |
| if (nc_server_config_create_bind()) { |
| return 1; |
| } |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&server_opts.endpts, sizeof *server_opts.endpts, |
| &server_opts.endpt_count); |
| } |
| |
| static int |
| nc_server_config_ch_create_endpoint(const struct lyd_node *node, struct nc_ch_client *ch_client) |
| { |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&ch_client->ch_endpts, sizeof *ch_client->ch_endpts, |
| &ch_client->ch_endpt_count); |
| } |
| |
| /* list */ |
| static int |
| nc_server_config_endpoint(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "endpoint")); |
| |
| if (is_listen(node)) { |
| /* listen */ |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_endpoint(node); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| /* free all children */ |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| switch (endpt->ti) { |
| #ifdef NC_ENABLED_SSH_TLS |
| case NC_TI_LIBSSH: |
| nc_server_config_del_endpt_ssh(endpt, bind); |
| break; |
| case NC_TI_OPENSSL: |
| nc_server_config_del_endpt_tls(endpt, bind); |
| break; |
| #endif /* NC_ENABLED_SSH_TLS */ |
| case NC_TI_UNIX: |
| break; |
| case NC_TI_NONE: |
| case NC_TI_FD: |
| ERRINT; |
| ret = 1; |
| goto cleanup; |
| } |
| } |
| } else if (is_ch(node)) { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_ch_create_endpoint(node, ch_client); |
| if (ret) { |
| goto cleanup; |
| } |
| |
| /* init ch sock */ |
| ch_client->ch_endpts[ch_client->ch_endpt_count - 1].sock_pending = -1; |
| } else if (op == NC_OP_DELETE) { |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| nc_server_config_ch_del_endpt(ch_client, ch_endpt); |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| static int |
| nc_server_config_create_ssh(struct nc_endpt *endpt) |
| { |
| endpt->ti = NC_TI_LIBSSH; |
| endpt->opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); |
| NC_CHECK_ERRMEM_RET(!endpt->opts.ssh, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_ch_create_ssh(struct nc_ch_endpt *ch_endpt) |
| { |
| ch_endpt->ti = NC_TI_LIBSSH; |
| ch_endpt->opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); |
| NC_CHECK_ERRMEM_RET(!ch_endpt->opts.ssh, 1); |
| |
| return 0; |
| } |
| |
| /* NP container */ |
| static int |
| nc_server_config_ssh(const struct lyd_node *node, NC_OPERATION op) |
| { |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| int ret = 0; |
| |
| assert(!strcmp(LYD_NAME(node), "ssh")); |
| |
| if (is_listen(node)) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_ssh(endpt); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| nc_server_config_del_ssh_opts(bind, endpt->opts.ssh); |
| } |
| } else { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_ch_create_ssh(ch_endpt); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| nc_server_config_del_ssh_opts(NULL, ch_endpt->opts.ssh); |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_tls(struct nc_endpt *endpt) |
| { |
| endpt->ti = NC_TI_OPENSSL; |
| endpt->opts.tls = calloc(1, sizeof *endpt->opts.tls); |
| NC_CHECK_ERRMEM_RET(!endpt->opts.tls, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_ch_create_tls(struct nc_ch_endpt *ch_endpt) |
| { |
| ch_endpt->ti = NC_TI_OPENSSL; |
| ch_endpt->opts.tls = calloc(1, sizeof(struct nc_server_tls_opts)); |
| NC_CHECK_ERRMEM_RET(!ch_endpt->opts.tls, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_tls(const struct lyd_node *node, NC_OPERATION op) |
| { |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| int ret = 0; |
| |
| assert(!strcmp(LYD_NAME(node), "tls")); |
| |
| if (is_listen(node)) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_tls(endpt); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| nc_server_config_del_tls_opts(bind, endpt->opts.tls); |
| } |
| } else { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_ch_create_tls(ch_endpt); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| nc_server_config_del_tls_opts(NULL, ch_endpt->opts.tls); |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* mandatory leaf */ |
| static int |
| nc_server_config_local_address(const struct lyd_node *node, NC_OPERATION op) |
| { |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| int ret = 0; |
| |
| (void) op; |
| |
| assert(!strcmp(LYD_NAME(node), "local-address")); |
| |
| if (equal_parent_name(node, 5, "listen")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| free(bind->address); |
| bind->address = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!bind->address, ret = 1, cleanup); |
| |
| ret = nc_server_set_address_port(endpt, bind, lyd_get_value(node), 0); |
| if (ret) { |
| goto cleanup; |
| } |
| } |
| |
| cleanup: |
| return ret; |
| } |
| |
| /* leaf with default value */ |
| static int |
| nc_server_config_local_port(const struct lyd_node *node, NC_OPERATION op) |
| { |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| int ret = 0; |
| |
| assert(!strcmp(LYD_NAME(node), "local-port")); |
| |
| if (equal_parent_name(node, 5, "listen")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| bind->port = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| bind->port = 0; |
| } |
| |
| ret = nc_server_set_address_port(endpt, bind, NULL, bind->port); |
| if (ret) { |
| goto cleanup; |
| } |
| } |
| |
| cleanup: |
| return ret; |
| } |
| |
| /* NP container */ |
| static int |
| nc_server_config_keepalives(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "keepalives")); |
| |
| if (is_listen(node) && equal_parent_name(node, 1, "tcp-server-parameters")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| endpt->ka.enabled = 1; |
| } else { |
| endpt->ka.enabled = 0; |
| } |
| ret = nc_sock_configure_keepalive(bind->sock, &endpt->ka); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (is_ch(node) && equal_parent_name(node, 1, "tcp-client-parameters")) { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ch_endpt->ka.enabled = 1; |
| } else { |
| ch_endpt->ka.enabled = 0; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node) && equal_parent_name(node, 1, "tcp-client-parameters")) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf with default value */ |
| static int |
| nc_server_config_idle_time(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "idle-time")); |
| |
| if (is_listen(node) && equal_parent_name(node, 2, "tcp-server-parameters")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| endpt->ka.idle_time = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| endpt->ka.idle_time = 7200; |
| } |
| ret = nc_sock_configure_keepalive(bind->sock, &endpt->ka); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_endpt->ka.idle_time = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| ch_endpt->ka.idle_time = 7200; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf with default value */ |
| static int |
| nc_server_config_max_probes(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "max-probes")); |
| |
| if (is_listen(node) && equal_parent_name(node, 2, "tcp-server-parameters")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| endpt->ka.max_probes = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| endpt->ka.max_probes = 9; |
| } |
| ret = nc_sock_configure_keepalive(bind->sock, &endpt->ka); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_endpt->ka.max_probes = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| ch_endpt->ka.max_probes = 9; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf with default value */ |
| static int |
| nc_server_config_probe_interval(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt; |
| struct nc_bind *bind; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "probe-interval")); |
| |
| if (is_listen(node) && equal_parent_name(node, 2, "tcp-server-parameters")) { |
| if (nc_server_config_get_endpt(node, &endpt, &bind)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| endpt->ka.probe_interval = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| endpt->ka.probe_interval = 75; |
| } |
| ret = nc_sock_configure_keepalive(bind->sock, &endpt->ka); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_endpt->ka.probe_interval = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| /* delete -> set to default */ |
| ch_endpt->ka.probe_interval = 75; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node) && equal_parent_name(node, 2, "tcp-client-parameters")) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_host_key(const struct lyd_node *node, struct nc_server_ssh_opts *opts) |
| { |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->hostkeys, sizeof *opts->hostkeys, &opts->hostkey_count); |
| } |
| |
| /* list */ |
| static int |
| nc_server_config_host_key(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_hostkey *hostkey; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "host-key")); |
| |
| if (equal_parent_name(node, 1, "server-identity")) { |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_host_key(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| nc_server_config_del_hostkey(opts, hostkey); |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node) && equal_parent_name(node, 1, "server-identity")) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* mandatory leaf */ |
| static int |
| nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *format; |
| NC_PUBKEY_FORMAT pubkey_type; |
| struct nc_public_key *pubkey; |
| struct nc_hostkey *hostkey; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "public-key-format")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| format = ((struct lyd_node_term *)node)->value.ident->name; |
| if (!strcmp(format, "ssh-public-key-format")) { |
| pubkey_type = NC_PUBKEY_FORMAT_SSH; |
| } else if (!strcmp(format, "subject-public-key-info-format")) { |
| pubkey_type = NC_PUBKEY_FORMAT_X509; |
| } else { |
| ERR(NULL, "Public key format (%s) not supported.", format); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (is_ssh(node) && equal_parent_name(node, 4, "server-identity")) { |
| /* SSH hostkey public key fmt */ |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| hostkey->key.pubkey_type = pubkey_type; |
| } |
| } else if (is_ssh(node) && equal_parent_name(node, 6, "client-authentication")) { |
| /* SSH client auth public key fmt */ |
| if (nc_server_config_get_pubkey(node, ch_client, &pubkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| pubkey->type = pubkey_type; |
| } |
| } else if (is_tls(node) && equal_parent_name(node, 3, "server-identity")) { |
| /* TLS server-identity */ |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| opts->pubkey_type = pubkey_type; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_auth_key_public_key_list(const struct lyd_node *node, struct nc_auth_client *auth_client) |
| { |
| assert(!strcmp(LYD_NAME(node), "public-key")); |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&auth_client->pubkeys, sizeof *auth_client->pubkeys, |
| &auth_client->pubkey_count); |
| } |
| |
| static int |
| nc_server_config_replace_auth_key_public_key_leaf(const struct lyd_node *node, struct nc_public_key *pubkey) |
| { |
| free(pubkey->data); |
| pubkey->data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_RET(!pubkey->data, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_replace_host_key_public_key(const struct lyd_node *node, struct nc_hostkey *hostkey) |
| { |
| free(hostkey->key.pubkey_data); |
| hostkey->key.pubkey_data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_RET(!hostkey->key.pubkey_data, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_tls_replace_server_public_key(const struct lyd_node *node, struct nc_server_tls_opts *opts) |
| { |
| free(opts->pubkey_data); |
| opts->pubkey_data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_RET(!opts->pubkey_data, 1); |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_hostkey *hostkey; |
| struct nc_auth_client *auth_client; |
| struct nc_public_key *pubkey; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "public-key")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (is_ssh(node) && equal_parent_name(node, 3, "host-key")) { |
| /* server's public-key, mandatory leaf */ |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* the public key must not be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ |
| if (nc_is_pk_subject_public_key_info(lyd_get_value(node))) { |
| ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH hostkey is forbidden!"); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to local */ |
| hostkey->store = NC_STORE_LOCAL; |
| |
| ret = nc_server_config_replace_host_key_public_key(node, hostkey); |
| if (ret) { |
| goto cleanup; |
| } |
| } |
| } else if (is_ssh(node) && equal_parent_name(node, 5, "client-authentication")) { |
| /* client auth pubkeys, list */ |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| /* set to local */ |
| auth_client->store = NC_STORE_LOCAL; |
| |
| ret = nc_server_config_create_auth_key_public_key_list(node, auth_client); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| if (nc_server_config_get_pubkey(node, ch_client, &pubkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| nc_server_config_del_auth_client_pubkey(auth_client, pubkey); |
| } |
| } else if (is_ssh(node) && equal_parent_name(node, 6, "client-authentication")) { |
| /* client auth pubkey, leaf */ |
| if (nc_server_config_get_pubkey(node, ch_client, &pubkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* the public key must not be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ |
| if (nc_is_pk_subject_public_key_info(lyd_get_value(node))) { |
| ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH user's key is forbidden!"); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ret = nc_server_config_replace_auth_key_public_key_leaf(node, pubkey); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| free(pubkey->data); |
| pubkey->data = NULL; |
| } |
| } else if (is_tls(node) && equal_parent_name(node, 3, "server-identity")) { |
| /* TLS server-identity */ |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* the public key must be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ |
| if (!nc_is_pk_subject_public_key_info(lyd_get_value(node))) { |
| ERR(NULL, "TLS server certificate's Public Key must be in the SubjectPublicKeyInfo format!"); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to local */ |
| opts->store = NC_STORE_LOCAL; |
| |
| ret = nc_server_config_tls_replace_server_public_key(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf */ |
| static int |
| nc_server_config_private_key_format(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *format; |
| NC_PRIVKEY_FORMAT privkey_type; |
| struct nc_hostkey *hostkey; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| (void) op; |
| |
| assert(!strcmp(LYD_NAME(node), "private-key-format")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| format = ((struct lyd_node_term *)node)->value.ident->name; |
| if (!format) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| privkey_type = nc_server_config_get_private_key_type(format); |
| if (privkey_type == NC_PRIVKEY_FORMAT_UNKNOWN) { |
| ERR(NULL, "Unknown private key format."); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (is_ssh(node)) { |
| /* ssh */ |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| hostkey->key.privkey_type = privkey_type; |
| } else if (is_tls(node)) { |
| /* tls */ |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| opts->privkey_type = privkey_type; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_cleartext_private_key(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_hostkey *hostkey; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "cleartext-private-key")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (is_ssh(node)) { |
| /* ssh */ |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(hostkey->key.privkey_data); |
| hostkey->key.privkey_data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!hostkey->key.privkey_data, ret = 1, cleanup); |
| } else { |
| free(hostkey->key.privkey_data); |
| hostkey->key.privkey_data = NULL; |
| } |
| } else if (is_tls(node)) { |
| /* tls */ |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(opts->privkey_data); |
| opts->privkey_data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->privkey_data, ret = 1, cleanup); |
| } else { |
| free(opts->privkey_data); |
| opts->privkey_data = NULL; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf */ |
| static int |
| nc_server_config_keystore_reference(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_hostkey *hostkey; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "central-keystore-reference")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (is_ssh(node) && equal_parent_name(node, 3, "server-identity")) { |
| if (nc_server_config_get_hostkey(node, ch_client, &hostkey)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to keystore */ |
| hostkey->store = NC_STORE_KEYSTORE; |
| |
| free(hostkey->ks_ref); |
| hostkey->ks_ref = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!hostkey->ks_ref, ret = 1, cleanup); |
| } else if (op == NC_OP_DELETE) { |
| free(hostkey->ks_ref); |
| hostkey->ks_ref = NULL; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_auth_client(const struct lyd_node *node, struct nc_server_ssh_opts *opts) |
| { |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->auth_clients, sizeof *opts->auth_clients, &opts->client_count); |
| } |
| |
| /* list */ |
| static int |
| nc_server_config_user(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "user")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_auth_client(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| nc_server_config_del_auth_client(opts, auth_client); |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_auth_attempts(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "auth-attempts")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| opts->auth_attempts = ((struct lyd_node_term *)node)->value.uint16; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_auth_timeout(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "auth-timeout")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| opts->auth_timeout = ((struct lyd_node_term *)node)->value.uint16; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf */ |
| static int |
| nc_server_config_truststore_reference(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_ch_client *ch_client = NULL; |
| struct nc_server_tls_opts *opts; |
| struct nc_cert_grouping *certs_grp; |
| |
| assert(!strcmp(LYD_NAME(node), "central-truststore-reference")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (is_ssh(node) && equal_parent_name(node, 1, "public-keys")) { |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to truststore */ |
| auth_client->store = NC_STORE_TRUSTSTORE; |
| |
| free(auth_client->ts_ref); |
| auth_client->ts_ref = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!auth_client->ts_ref, ret = 1, cleanup); |
| } else if (op == NC_OP_DELETE) { |
| free(auth_client->ts_ref); |
| auth_client->ts_ref = NULL; |
| } |
| } else if (is_tls(node) && (equal_parent_name(node, 1, "ca-certs") || equal_parent_name(node, 1, "ee-certs"))) { |
| /* ee-certs or ca-certs */ |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (equal_parent_name(node, 1, "ca-certs")) { |
| certs_grp = &opts->ca_certs; |
| } else { |
| certs_grp = &opts->ee_certs; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to truststore */ |
| certs_grp->store = NC_STORE_TRUSTSTORE; |
| |
| free(certs_grp->ts_ref); |
| certs_grp->ts_ref = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!certs_grp->ts_ref, ret = 1, cleanup); |
| } else if (op == NC_OP_DELETE) { |
| free(certs_grp->ts_ref); |
| certs_grp->ts_ref = NULL; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_use_system_keys(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "use-system-keys")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| auth_client->store = NC_STORE_SYSTEM; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf */ |
| static int |
| nc_server_config_password(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "password")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(auth_client->password); |
| auth_client->password = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!auth_client->password, ret = 1, cleanup); |
| } else { |
| free(auth_client->password); |
| auth_client->password = NULL; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_use_system_auth(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "use-system-auth")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| auth_client->kb_int_enabled = 1; |
| } else { |
| auth_client->kb_int_enabled = 0; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf */ |
| static int |
| nc_server_config_none(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_auth_client *auth_client; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "none")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_auth_client(node, ch_client, &auth_client)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| auth_client->none_enabled = 1; |
| } else { |
| auth_client->none_enabled = 0; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_delete_substring(const char *haystack, const char *needle, const char delim) |
| { |
| size_t needle_len = strlen(needle); |
| char *substr; |
| int substr_found = 0, ret = 0; |
| |
| while ((substr = strstr(haystack, needle))) { |
| /* iterate over all the substrings */ |
| if (((substr == haystack) && (*(substr + needle_len) == delim)) || |
| ((substr != haystack) && (*(substr - 1) == delim) && (*(substr + needle_len) == delim))) { |
| /* either the first element of the string or somewhere in the middle */ |
| memmove(substr, substr + needle_len + 1, strlen(substr + needle_len + 1)); |
| substr_found = 1; |
| break; |
| } else if ((*(substr - 1) == delim) && (*(substr + needle_len) == '\0')) { |
| /* the last element of the string */ |
| *(substr - 1) = '\0'; |
| substr_found = 1; |
| break; |
| } |
| haystack = substr + 1; |
| } |
| if (!substr_found) { |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_transport_params(const char *algorithm, char **alg_store, NC_OPERATION op) |
| { |
| int ret = 0; |
| char *alg = NULL; |
| |
| if (!strncmp(algorithm, "openssh-", 8)) { |
| /* if the name starts with openssh, convert it to it's original libssh accepted form */ |
| ret = asprintf(&alg, "%s@openssh.com", algorithm + 8); |
| NC_CHECK_ERRMEM_GOTO(ret == -1, ret = 1; alg = NULL, cleanup); |
| } else if (!strncmp(algorithm, "libssh-", 7)) { |
| /* if the name starts with libssh, convert it to it's original libssh accepted form */ |
| ret = asprintf(&alg, "%s@libssh.org", algorithm + 7); |
| NC_CHECK_ERRMEM_GOTO(ret == -1, ret = 1; alg = NULL, cleanup); |
| } else { |
| alg = strdup(algorithm); |
| NC_CHECK_ERRMEM_GOTO(!alg, ret = 1, cleanup); |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| if (!*alg_store) { |
| /* first call */ |
| *alg_store = strdup(alg); |
| NC_CHECK_ERRMEM_GOTO(!*alg_store, ret = 1, cleanup); |
| } else { |
| /* +1 because of ',' between algorithms */ |
| *alg_store = nc_realloc(*alg_store, strlen(*alg_store) + strlen(alg) + 1 + 1); |
| NC_CHECK_ERRMEM_GOTO(!*alg_store, ret = 1, cleanup); |
| strcat(*alg_store, ","); |
| strcat(*alg_store, alg); |
| } |
| } else { |
| /* delete */ |
| ret = nc_server_config_delete_substring(*alg_store, alg, ','); |
| if (ret) { |
| ERR(NULL, "Unable to delete an algorithm (%s), which was not previously added.", alg); |
| goto cleanup; |
| } |
| } |
| |
| cleanup: |
| free(alg); |
| return ret; |
| } |
| |
| /* leaf-list */ |
| static int |
| nc_server_config_host_key_alg(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *alg; |
| uint8_t i; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "host-key-alg")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* get the algorithm name and compare it with algs supported by libssh */ |
| alg = ((struct lyd_node_term *)node)->value.ident->name; |
| i = 0; |
| while (supported_hostkey_algs[i]) { |
| if (!strcmp(supported_hostkey_algs[i], alg)) { |
| if (nc_server_config_transport_params(alg, &opts->hostkey_algs, op)) { |
| ret = 1; |
| goto cleanup; |
| } |
| break; |
| } |
| i++; |
| } |
| if (!supported_hostkey_algs[i]) { |
| /* algorithm not supported */ |
| ERR(NULL, "Public key algorithm (%s) not supported by libssh.", alg); |
| ret = 1; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf-list */ |
| static int |
| nc_server_config_kex_alg(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *alg; |
| uint8_t i; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "key-exchange-alg")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* get the algorithm name and compare it with algs supported by libssh */ |
| alg = ((struct lyd_node_term *)node)->value.ident->name; |
| i = 0; |
| while (supported_kex_algs[i]) { |
| if (!strcmp(supported_kex_algs[i], alg)) { |
| if (nc_server_config_transport_params(alg, &opts->kex_algs, op)) { |
| ret = 1; |
| goto cleanup; |
| } |
| break; |
| } |
| i++; |
| } |
| if (!supported_kex_algs[i]) { |
| /* algorithm not supported */ |
| ERR(NULL, "Key exchange algorithm (%s) not supported by libssh.", alg); |
| ret = 1; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf-list */ |
| static int |
| nc_server_config_encryption_alg(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *alg; |
| uint8_t i; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "encryption-alg")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* get the algorithm name and compare it with algs supported by libssh */ |
| alg = ((struct lyd_node_term *)node)->value.ident->name; |
| i = 0; |
| while (supported_encryption_algs[i]) { |
| if (!strcmp(supported_encryption_algs[i], alg)) { |
| if (nc_server_config_transport_params(alg, &opts->encryption_algs, op)) { |
| ret = 1; |
| goto cleanup; |
| } |
| break; |
| } |
| i++; |
| } |
| if (!supported_encryption_algs[i]) { |
| /* algorithm not supported */ |
| ERR(NULL, "Encryption algorithm (%s) not supported by libssh.", alg); |
| ret = 1; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| /* leaf-list */ |
| static int |
| nc_server_config_mac_alg(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| const char *alg; |
| uint8_t i; |
| struct nc_server_ssh_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "mac-alg")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ssh_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* get the algorithm name and compare it with algs supported by libssh */ |
| alg = ((struct lyd_node_term *)node)->value.ident->name; |
| i = 0; |
| while (supported_mac_algs[i]) { |
| if (!strcmp(supported_mac_algs[i], alg)) { |
| if (nc_server_config_transport_params(alg, &opts->mac_algs, op)) { |
| ret = 1; |
| goto cleanup; |
| } |
| break; |
| } |
| i++; |
| } |
| if (!supported_mac_algs[i]) { |
| /* algorithm not supported */ |
| ERR(NULL, "MAC algorithm (%s) not supported by libssh.", alg); |
| ret = 1; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_check_endpt_reference_cycle(struct nc_endpt *original, struct nc_endpt *next) |
| { |
| if (!next->referenced_endpt_name) { |
| /* no further reference -> no cycle */ |
| return 0; |
| } |
| |
| if (!strcmp(original->name, next->referenced_endpt_name)) { |
| /* found cycle */ |
| return 1; |
| } else { |
| if (nc_server_get_referenced_endpt(next->referenced_endpt_name, &next)) { |
| /* referenced endpoint does not exist */ |
| return 1; |
| } |
| |
| /* continue further */ |
| return nc_server_config_check_endpt_reference_cycle(original, next); |
| } |
| } |
| |
| /** |
| * @brief Set all endpoint references. |
| * |
| * @return 0 on success, 1 on error. |
| */ |
| static int |
| nc_server_config_check_endpt_references(void) |
| { |
| uint16_t i, j; |
| struct nc_endpt *referenced_endpt = NULL; |
| |
| /* first do listen endpoints */ |
| for (i = 0; i < server_opts.endpt_count; i++) { |
| /* go through all the endpoints */ |
| if (server_opts.endpts[i].referenced_endpt_name) { |
| /* get referenced endpt */ |
| if (nc_server_get_referenced_endpt(server_opts.endpts[i].referenced_endpt_name, &referenced_endpt)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by endpoint \"%s\" does not exist.", |
| server_opts.endpts[i].referenced_endpt_name, server_opts.endpts[i].name); |
| return 1; |
| } |
| |
| /* check if the endpoint references itself */ |
| if (&server_opts.endpts[i] == referenced_endpt) { |
| ERR(NULL, "Endpoint \"%s\" references itself.", server_opts.endpts[i].name); |
| return 1; |
| } |
| |
| /* check transport */ |
| if ((server_opts.endpts[i].ti != referenced_endpt->ti)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by endpoint \"%s\" has different transport type.", |
| server_opts.endpts[i].referenced_endpt_name, server_opts.endpts[i].name); |
| return 1; |
| } else if ((referenced_endpt->ti != NC_TI_LIBSSH) && (referenced_endpt->ti != NC_TI_OPENSSL)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by endpoint \"%s\" has unsupported transport type.", |
| server_opts.endpts[i].referenced_endpt_name, server_opts.endpts[i].name); |
| return 1; |
| } |
| |
| /* check cyclic reference */ |
| if (nc_server_config_check_endpt_reference_cycle(&server_opts.endpts[i], referenced_endpt)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by endpoint \"%s\" creates a cycle.", |
| server_opts.endpts[i].referenced_endpt_name, server_opts.endpts[i].name); |
| return 1; |
| } |
| |
| /* all went well, assign the name to the opts, so we can access it for auth */ |
| if (server_opts.endpts[i].ti == NC_TI_LIBSSH) { |
| server_opts.endpts[i].opts.ssh->referenced_endpt_name = referenced_endpt->name; |
| } else { |
| server_opts.endpts[i].opts.tls->referenced_endpt_name = referenced_endpt->name; |
| } |
| } |
| } |
| |
| /* now check all the call home endpoints */ |
| /* LOCK */ |
| pthread_rwlock_rdlock(&server_opts.ch_client_lock); |
| for (i = 0; i < server_opts.ch_client_count; i++) { |
| /* LOCK */ |
| pthread_mutex_lock(&server_opts.ch_clients[i].lock); |
| for (j = 0; j < server_opts.ch_clients[i].ch_endpt_count; j++) { |
| if (server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name) { |
| /* get referenced endpt */ |
| if (nc_server_get_referenced_endpt(server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, &referenced_endpt)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by call home endpoint \"%s\" does not exist.", |
| server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, server_opts.ch_clients[i].ch_endpts[j].name); |
| goto ch_fail; |
| } |
| |
| /* check transport */ |
| if (server_opts.ch_clients[i].ch_endpts[j].ti != referenced_endpt->ti) { |
| ERR(NULL, "Endpoint \"%s\" referenced by call home endpoint \"%s\" has different transport type.", |
| server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, server_opts.ch_clients[i].ch_endpts[j].name); |
| goto ch_fail; |
| } else if ((referenced_endpt->ti != NC_TI_LIBSSH) && (referenced_endpt->ti != NC_TI_OPENSSL)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by call home endpoint \"%s\" has unsupported transport type.", |
| server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, server_opts.ch_clients[i].ch_endpts[j].name); |
| goto ch_fail; |
| } |
| |
| /* check cyclic reference */ |
| if (nc_server_config_check_endpt_reference_cycle(referenced_endpt, referenced_endpt)) { |
| ERR(NULL, "Endpoint \"%s\" referenced by call home endpoint \"%s\" creates a cycle.", |
| server_opts.ch_clients[i].ch_endpts[j].referenced_endpt_name, server_opts.ch_clients[i].ch_endpts[j].name); |
| goto ch_fail; |
| } |
| |
| /* all went well, assign the name to the opts, so we can access it for auth */ |
| if (server_opts.ch_clients[i].ch_endpts[j].ti == NC_TI_LIBSSH) { |
| server_opts.ch_clients[i].ch_endpts[j].opts.ssh->referenced_endpt_name = referenced_endpt->name; |
| } else { |
| server_opts.ch_clients[i].ch_endpts[j].opts.tls->referenced_endpt_name = referenced_endpt->name; |
| } |
| } |
| } |
| /* UNLOCK */ |
| pthread_mutex_unlock(&server_opts.ch_clients[i].lock); |
| } |
| |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| return 0; |
| |
| ch_fail: |
| /* UNLOCK */ |
| pthread_mutex_unlock(&server_opts.ch_clients[i].lock); |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| return 1; |
| } |
| |
| static int |
| nc_server_config_endpoint_reference(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_endpt *endpt = NULL; |
| struct nc_ch_client *ch_client = NULL; |
| struct nc_ch_endpt *ch_endpt = NULL; |
| struct nc_server_ssh_opts *ssh = NULL; |
| struct nc_server_tls_opts *tls = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "endpoint-reference")); |
| |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| /* get endpt */ |
| if (is_listen(node)) { |
| ret = nc_server_config_get_endpt(node, &endpt, NULL); |
| } else { |
| ret = nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt); |
| } |
| if (ret) { |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_DELETE) { |
| if (endpt) { |
| /* listen */ |
| free(endpt->referenced_endpt_name); |
| endpt->referenced_endpt_name = NULL; |
| } else { |
| /* call home */ |
| free(ch_endpt->referenced_endpt_name); |
| ch_endpt->referenced_endpt_name = NULL; |
| } |
| if (is_ssh(node)) { |
| if (nc_server_config_get_ssh_opts(node, ch_client, &ssh)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| ssh->referenced_endpt_name = NULL; |
| } else { |
| if (nc_server_config_get_tls_opts(node, ch_client, &tls)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| tls->referenced_endpt_name = NULL; |
| } |
| |
| goto cleanup; |
| } else { |
| /* just set the name, check it once configuring of all nodes is done */ |
| if (endpt) { |
| free(endpt->referenced_endpt_name); |
| endpt->referenced_endpt_name = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!endpt->referenced_endpt_name, ret = 1, cleanup); |
| } else { |
| free(ch_endpt->referenced_endpt_name); |
| ch_endpt->referenced_endpt_name = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!ch_endpt->referenced_endpt_name, ret = 1, cleanup); |
| } |
| |
| goto cleanup; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_cert_data(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_certificate *cert; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "cert-data")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (equal_parent_name(node, 3, "server-identity")) { |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(opts->cert_data); |
| opts->cert_data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->cert_data, ret = 1, cleanup); |
| } |
| } else if (equal_parent_name(node, 3, "ca-certs") || equal_parent_name(node, 3, "ee-certs")) { |
| if (nc_server_config_get_cert(node, ch_client, &cert)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(cert->data); |
| cert->data = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!cert->data, ret = 1, cleanup); |
| } else { |
| free(cert->data); |
| cert->data = NULL; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_asymmetric_key(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "asymmetric-key")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to keystore */ |
| opts->store = NC_STORE_KEYSTORE; |
| |
| free(opts->key_ref); |
| opts->key_ref = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->key_ref, ret = 1, cleanup); |
| } else { |
| free(opts->key_ref); |
| opts->key_ref = NULL; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_ca_certs_certificate(const struct lyd_node *node, struct nc_server_tls_opts *opts) |
| { |
| assert(!strcmp(LYD_NAME(node), "certificate")); |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->ca_certs.certs, sizeof *opts->ca_certs.certs, |
| &opts->ca_certs.cert_count); |
| } |
| |
| static int |
| nc_server_config_create_ee_certs_certificate(const struct lyd_node *node, struct nc_server_tls_opts *opts) |
| { |
| assert(!strcmp(LYD_NAME(node), "certificate")); |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| return nc_server_config_realloc(lyd_get_value(node), (void **)&opts->ee_certs.certs, sizeof *opts->ee_certs.certs, |
| &opts->ee_certs.cert_count); |
| } |
| |
| static int |
| nc_server_config_certificate(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| struct nc_certificate *cert; |
| |
| assert(!strcmp(LYD_NAME(node), "certificate")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (equal_parent_name(node, 1, "central-keystore-reference")) { |
| /* TLS server-identity */ |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| /* set to keystore */ |
| opts->store = NC_STORE_KEYSTORE; |
| |
| free(opts->cert_ref); |
| opts->cert_ref = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->cert_ref, ret = 1, cleanup); |
| } else { |
| free(opts->cert_ref); |
| opts->cert_ref = NULL; |
| } |
| } else if (equal_parent_name(node, 2, "ca-certs")) { |
| /* TLS client auth certificate authority */ |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ret = nc_server_config_create_ca_certs_certificate(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } else { |
| if (nc_server_config_get_cert(node, ch_client, &cert)) { |
| ret = 1; |
| goto cleanup; |
| } |
| nc_server_config_del_cert(&opts->ca_certs, cert); |
| } |
| } else if (equal_parent_name(node, 2, "ee-certs")) { |
| /* TLS client auth end entity */ |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ret = nc_server_config_create_ee_certs_certificate(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } else { |
| if (nc_server_config_get_cert(node, ch_client, &cert)) { |
| ret = 1; |
| goto cleanup; |
| } |
| nc_server_config_del_cert(&opts->ee_certs, cert); |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_cert_to_name(const struct lyd_node *node, struct nc_server_tls_opts *opts) |
| { |
| int ret = 0; |
| struct lyd_node *n; |
| struct nc_ctn *new, *iter; |
| const char *map_type, *name; |
| uint32_t id; |
| NC_TLS_CTN_MAPTYPE m_type; |
| |
| assert(!strcmp(LYD_NAME(node), "cert-to-name")); |
| |
| /* get all the data */ |
| /* find the list's key */ |
| lyd_find_path(node, "id", 0, &n); |
| assert(n); |
| id = ((struct lyd_node_term *)n)->value.uint32; |
| |
| /* find the ctn's name */ |
| lyd_find_path(node, "name", 0, &n); |
| assert(n); |
| name = lyd_get_value(n); |
| |
| /* find the ctn's map-type */ |
| lyd_find_path(node, "map-type", 0, &n); |
| assert(n); |
| map_type = ((struct lyd_node_term *)n)->value.ident->name; |
| if (!strcmp(map_type, "specified")) { |
| m_type = NC_TLS_CTN_SPECIFIED; |
| } else if (!strcmp(map_type, "san-rfc822-name")) { |
| m_type = NC_TLS_CTN_SAN_RFC822_NAME; |
| } else if (!strcmp(map_type, "san-dns-name")) { |
| m_type = NC_TLS_CTN_SAN_DNS_NAME; |
| } else if (!strcmp(map_type, "san-ip-address")) { |
| m_type = NC_TLS_CTN_SAN_IP_ADDRESS; |
| } else if (!strcmp(map_type, "san-any")) { |
| m_type = NC_TLS_CTN_SAN_ANY; |
| } else if (!strcmp(map_type, "common-name")) { |
| m_type = NC_TLS_CTN_COMMON_NAME; |
| } else { |
| ERR(NULL, "Map-type identity \"%s\" not supported.", map_type); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* create new ctn */ |
| new = calloc(1, sizeof *new); |
| NC_CHECK_ERRMEM_GOTO(!new, ret = 1, cleanup); |
| |
| /* find the right place for insertion */ |
| if (!opts->ctn) { |
| /* inserting the first one */ |
| opts->ctn = new; |
| } else if (opts->ctn->id > id) { |
| /* insert at the beginning */ |
| new->next = opts->ctn; |
| opts->ctn = new; |
| } else { |
| /* have to find the right place */ |
| for (iter = opts->ctn; iter->next && iter->next->id <= id; iter = iter->next) {} |
| if (iter->id == id) { |
| /* collision */ |
| free(new); |
| new = iter; |
| } else { |
| new->next = iter->next; |
| iter->next = new; |
| } |
| } |
| |
| /* insert the right data */ |
| new->id = id; |
| free(new->name); |
| new->name = strdup(name); |
| NC_CHECK_ERRMEM_GOTO(!new->name, ret = 1, cleanup); |
| new->map_type = m_type; |
| |
| cleanup: |
| return ret; |
| } |
| |
| static int |
| nc_server_config_cert_to_name(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ctn *ctn; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "cert-to-name")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ret = nc_server_config_create_cert_to_name(node, opts); |
| if (ret) { |
| goto cleanup; |
| } |
| } else { |
| /* find the given ctn entry */ |
| if (nc_server_config_get_ctn(node, ch_client, &ctn)) { |
| ret = 1; |
| goto cleanup; |
| } |
| nc_server_config_del_ctn(opts, ctn); |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_fingerprint(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ctn *ctn; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "fingerprint")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ctn(node, ch_client, &ctn)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(ctn->fingerprint); |
| ctn->fingerprint = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!ctn->fingerprint, ret = 1, cleanup); |
| } else { |
| free(ctn->fingerprint); |
| ctn->fingerprint = NULL; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_tls_version(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| const char *version = NULL; |
| struct nc_ch_client *ch_client = NULL; |
| NC_TLS_VERSION tls_version; |
| |
| assert(!strcmp(LYD_NAME(node), "tls-version")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| /* str to tls_version */ |
| version = ((struct lyd_node_term *)node)->value.ident->name; |
| if (!strcmp(version, "tls10")) { |
| tls_version = NC_TLS_VERSION_10; |
| } else if (!strcmp(version, "tls11")) { |
| tls_version = NC_TLS_VERSION_11; |
| } else if (!strcmp(version, "tls12")) { |
| tls_version = NC_TLS_VERSION_12; |
| } else if (!strcmp(version, "tls13")) { |
| tls_version = NC_TLS_VERSION_13; |
| } else { |
| ERR(NULL, "TLS version \"%s\" not supported.", version); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if (op == NC_OP_CREATE) { |
| /* add the version if it isn't there already */ |
| opts->tls_versions |= tls_version; |
| } else if ((op == NC_OP_DELETE) && (opts->tls_versions & tls_version)) { |
| /* delete the version if it is there */ |
| opts->tls_versions &= ~tls_version; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_create_cipher_suite(struct nc_server_tls_opts *opts, const char *cipher) |
| { |
| int ret = 0; |
| char *ssl_cipher = NULL; |
| uint16_t i; |
| void *tmp; |
| |
| ssl_cipher = malloc(strlen(cipher) + 1); |
| NC_CHECK_ERRMEM_GOTO(!ssl_cipher, ret = 1, cleanup); |
| |
| for (i = 0; cipher[i]; i++) { |
| if (cipher[i] == '-') { |
| /* OpenSSL requires _ instead of - in cipher names */ |
| ssl_cipher[i] = '_'; |
| } else { |
| /* and requires uppercase unlike the identities */ |
| ssl_cipher[i] = toupper(cipher[i]); |
| } |
| } |
| ssl_cipher[i] = '\0'; |
| |
| if (!opts->ciphers) { |
| /* first entry */ |
| opts->ciphers = strdup(ssl_cipher); |
| NC_CHECK_ERRMEM_GOTO(!opts->ciphers, ret = 1, cleanup); |
| } else { |
| /* + 1 because of : between entries */ |
| tmp = nc_realloc(opts->ciphers, strlen(opts->ciphers) + strlen(ssl_cipher) + 1 + 1); |
| NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup); |
| opts->ciphers = tmp; |
| strcat(opts->ciphers, ":"); |
| strcat(opts->ciphers, ssl_cipher); |
| } |
| |
| cleanup: |
| free(ssl_cipher); |
| return ret; |
| } |
| |
| static int |
| nc_server_config_del_cipher_suite(struct nc_server_tls_opts *opts, const char *cipher) |
| { |
| int ret = 0; |
| |
| ret = nc_server_config_delete_substring(opts->ciphers, cipher, ':'); |
| if (ret) { |
| ERR(NULL, "Unable to delete a cipher (%s), which was not previously added.", cipher); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| nc_server_config_cipher_suite(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| const char *cipher = NULL; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "cipher-suite")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| cipher = ((struct lyd_node_term *)node)->value.ident->name; |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_cipher_suite(opts, cipher); |
| if (ret) { |
| goto cleanup; |
| } |
| } else if (op == NC_OP_DELETE) { |
| ret = nc_server_config_del_cipher_suite(opts, cipher); |
| if (ret) { |
| goto cleanup; |
| } |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_crl_url(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "crl-url")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(opts->crl_url); |
| opts->crl_url = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->crl_url, ret = 1, cleanup); |
| } else if (op == NC_OP_DELETE) { |
| free(opts->crl_url); |
| opts->crl_url = NULL; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_crl_path(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "crl-path")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(opts->crl_path); |
| opts->crl_path = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!opts->crl_path, ret = 1, cleanup); |
| } else if (op == NC_OP_DELETE) { |
| free(opts->crl_path); |
| opts->crl_path = NULL; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| static int |
| nc_server_config_crl_cert_ext(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_server_tls_opts *opts; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "crl-cert-ext")); |
| |
| /* LOCK */ |
| if (is_ch(node) && nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_tls_opts(node, ch_client, &opts)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| opts->crl_cert_ext = 1; |
| } else if (op == NC_OP_DELETE) { |
| opts->crl_cert_ext = 0; |
| } |
| |
| cleanup: |
| if (is_ch(node)) { |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| } |
| return ret; |
| } |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| static int |
| nc_server_config_create_netconf_client(const struct lyd_node *node) |
| { |
| int ret = 0; |
| |
| node = lyd_child(node); |
| assert(!strcmp(LYD_NAME(node), "name")); |
| |
| /* LOCK */ |
| pthread_rwlock_wrlock(&server_opts.ch_client_lock); |
| |
| ret = nc_server_config_realloc(lyd_get_value(node), (void **)&server_opts.ch_clients, sizeof *server_opts.ch_clients, &server_opts.ch_client_count); |
| if (ret) { |
| goto cleanup; |
| } |
| |
| server_opts.ch_clients[server_opts.ch_client_count - 1].id = ATOMIC_INC_RELAXED(server_opts.new_client_id); |
| server_opts.ch_clients[server_opts.ch_client_count - 1].start_with = NC_CH_FIRST_LISTED; |
| server_opts.ch_clients[server_opts.ch_client_count - 1].max_attempts = 3; |
| |
| pthread_mutex_init(&server_opts.ch_clients[server_opts.ch_client_count - 1].lock, NULL); |
| |
| cleanup: |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.ch_client_lock); |
| return ret; |
| } |
| |
| static int |
| nc_server_config_netconf_client(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| |
| assert(!strcmp(LYD_NAME(node), "netconf-client")); |
| |
| if (op == NC_OP_CREATE) { |
| ret = nc_server_config_create_netconf_client(node); |
| if (ret) { |
| goto cleanup; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| if (server_opts.ch_dispatch_data.acquire_ctx_cb && server_opts.ch_dispatch_data.release_ctx_cb && |
| server_opts.ch_dispatch_data.new_session_cb) { |
| /* we have all we need for dispatching a new call home thread */ |
| ret = nc_connect_ch_client_dispatch(lyd_get_value(lyd_child(node)), server_opts.ch_dispatch_data.acquire_ctx_cb, |
| server_opts.ch_dispatch_data.release_ctx_cb, server_opts.ch_dispatch_data.ctx_cb_data, |
| server_opts.ch_dispatch_data.new_session_cb, server_opts.ch_dispatch_data.new_session_cb_data); |
| if (ret) { |
| ERR(NULL, "Dispatching a new Call Home thread failed for Call Home client \"%s\".", lyd_get_value(lyd_child(node))); |
| goto cleanup; |
| } |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| } else if (op == NC_OP_DELETE) { |
| nc_server_config_ch_del_client(node); |
| } |
| |
| cleanup: |
| return ret; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| |
| static int |
| nc_server_config_remote_address(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "remote-address")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| free(ch_endpt->address); |
| ch_endpt->address = strdup(lyd_get_value(node)); |
| NC_CHECK_ERRMEM_GOTO(!ch_endpt->address, ret = 1, cleanup); |
| } else { |
| free(ch_endpt->address); |
| ch_endpt->address = NULL; |
| } |
| |
| cleanup: |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| return ret; |
| } |
| |
| static int |
| nc_server_config_remote_port(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_endpt *ch_endpt; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "remote-port")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (nc_server_config_get_ch_endpt(node, ch_client, &ch_endpt)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_endpt->port = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| ch_endpt->port = 0; |
| } |
| |
| cleanup: |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| return ret; |
| } |
| |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| static int |
| nc_server_config_persistent(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "persistent")); |
| |
| (void) op; |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| ch_client->conn_type = NC_CH_PERSIST; |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_periodic(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "periodic")); |
| |
| (void) op; |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| ch_client->conn_type = NC_CH_PERIOD; |
| /* set default values */ |
| ch_client->period = 60; |
| ch_client->anchor_time = 0; |
| ch_client->idle_timeout = 180; |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_period(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "period")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_client->period = ((struct lyd_node_term *)node)->value.uint16; |
| } else if (op == NC_OP_DELETE) { |
| ch_client->period = 60; |
| } |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_anchor_time(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| struct lyd_value_date_and_time *anchor_time; |
| |
| assert(!strcmp(LYD_NAME(node), "anchor-time")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| /* get the value of time from the node directly */ |
| LYD_VALUE_GET(&((struct lyd_node_term *)node)->value, anchor_time); |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_client->anchor_time = anchor_time->time; |
| } else if (op == NC_OP_DELETE) { |
| ch_client->anchor_time = 0; |
| } |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| return ret; |
| } |
| |
| static int |
| nc_server_config_reconnect_strategy(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "reconnect-strategy")); |
| |
| (void) op; |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| /* set to default values */ |
| ch_client->start_with = NC_CH_FIRST_LISTED; |
| ch_client->max_wait = 5; |
| ch_client->max_attempts = 3; |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_start_with(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| const char *value; |
| |
| assert(!strcmp(LYD_NAME(node), "start-with")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if (op == NC_OP_DELETE) { |
| ch_client->start_with = NC_CH_FIRST_LISTED; |
| goto cleanup; |
| } |
| |
| value = lyd_get_value(node); |
| if (!strcmp(value, "first-listed")) { |
| ch_client->start_with = NC_CH_FIRST_LISTED; |
| } else if (!strcmp(value, "last-connected")) { |
| ch_client->start_with = NC_CH_LAST_CONNECTED; |
| } else if (!strcmp(value, "random-selection")) { |
| ch_client->start_with = NC_CH_RANDOM; |
| } else { |
| ERR(NULL, "Unexpected start-with value \"%s\".", value); |
| ret = 1; |
| goto cleanup; |
| } |
| |
| cleanup: |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| return ret; |
| } |
| |
| static int |
| nc_server_config_max_wait(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "max-wait")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_client->max_wait = ((struct lyd_node_term *)node)->value.uint16; |
| } else { |
| ch_client->max_wait = 5; |
| } |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_max_attempts(const struct lyd_node *node, NC_OPERATION op) |
| { |
| int ret = 0; |
| struct nc_ch_client *ch_client = NULL; |
| |
| assert(!strcmp(LYD_NAME(node), "max-attempts")); |
| |
| /* LOCK */ |
| if (nc_server_config_get_ch_client_with_lock(node, &ch_client)) { |
| /* to avoid unlock on fail */ |
| return 1; |
| } |
| |
| if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { |
| ch_client->max_attempts = ((struct lyd_node_term *)node)->value.uint8; |
| } else { |
| ch_client->max_attempts = 3; |
| } |
| |
| /* UNLOCK */ |
| nc_ch_client_unlock(ch_client); |
| |
| return ret; |
| } |
| |
| static int |
| nc_server_config_parse_netconf_server(const struct lyd_node *node, NC_OPERATION op) |
| { |
| const char *name = LYD_NAME(node); |
| int ret = 0; |
| |
| if (!strcmp(name, "listen")) { |
| ret = nc_server_config_listen(node, op); |
| } else if (!strcmp(name, "call-home")) { |
| ret = nc_server_config_ch(node, op); |
| } else if (!strcmp(name, "endpoint")) { |
| ret = nc_server_config_endpoint(node, op); |
| } |
| #ifdef NC_ENABLED_SSH_TLS |
| else if (!strcmp(name, "ssh")) { |
| ret = nc_server_config_ssh(node, op); |
| } else if (!strcmp(name, "local-address")) { |
| ret = nc_server_config_local_address(node, op); |
| } else if (!strcmp(name, "local-port")) { |
| ret = nc_server_config_local_port(node, op); |
| } else if (!strcmp(name, "keepalives")) { |
| ret = nc_server_config_keepalives(node, op); |
| } else if (!strcmp(name, "idle-time")) { |
| ret = nc_server_config_idle_time(node, op); |
| } else if (!strcmp(name, "max-probes")) { |
| ret = nc_server_config_max_probes(node, op); |
| } else if (!strcmp(name, "probe-interval")) { |
| ret = nc_server_config_probe_interval(node, op); |
| } else if (!strcmp(name, "host-key")) { |
| ret = nc_server_config_host_key(node, op); |
| } else if (!strcmp(name, "public-key-format")) { |
| ret = nc_server_config_public_key_format(node, op); |
| } else if (!strcmp(name, "public-key")) { |
| ret = nc_server_config_public_key(node, op); |
| } else if (!strcmp(name, "private-key-format")) { |
| ret = nc_server_config_private_key_format(node, op); |
| } else if (!strcmp(name, "cleartext-private-key")) { |
| ret = nc_server_config_cleartext_private_key(node, op); |
| } else if (!strcmp(name, "central-keystore-reference")) { |
| ret = nc_server_config_keystore_reference(node, op); |
| } else if (!strcmp(name, "user")) { |
| ret = nc_server_config_user(node, op); |
| } else if (!strcmp(name, "auth-attempts")) { |
| ret = nc_server_config_auth_attempts(node, op); |
| } else if (!strcmp(name, "auth-timeout")) { |
| ret = nc_server_config_auth_timeout(node, op); |
| } else if (!strcmp(name, "central-truststore-reference")) { |
| ret = nc_server_config_truststore_reference(node, op); |
| } else if (!strcmp(name, "use-system-keys")) { |
| ret = nc_server_config_use_system_keys(node, op); |
| } else if (!strcmp(name, "password")) { |
| ret = nc_server_config_password(node, op); |
| } else if (!strcmp(name, "use-system-auth")) { |
| ret = nc_server_config_use_system_auth(node, op); |
| } else if (!strcmp(name, "none")) { |
| ret = nc_server_config_none(node, op); |
| } else if (!strcmp(name, "host-key-alg")) { |
| ret = nc_server_config_host_key_alg(node, op); |
| } else if (!strcmp(name, "key-exchange-alg")) { |
| ret = nc_server_config_kex_alg(node, op); |
| } else if (!strcmp(name, "encryption-alg")) { |
| ret = nc_server_config_encryption_alg(node, op); |
| } else if (!strcmp(name, "mac-alg")) { |
| ret = nc_server_config_mac_alg(node, op); |
| } else if (!strcmp(name, "endpoint-reference")) { |
| ret = nc_server_config_endpoint_reference(node, op); |
| } else if (!strcmp(name, "tls")) { |
| ret = nc_server_config_tls(node, op); |
| } else if (!strcmp(name, "cert-data")) { |
| ret = nc_server_config_cert_data(node, op); |
| } else if (!strcmp(name, "asymmetric-key")) { |
| ret = nc_server_config_asymmetric_key(node, op); |
| } else if (!strcmp(name, "certificate")) { |
| ret = nc_server_config_certificate(node, op); |
| } else if (!strcmp(name, "cert-to-name")) { |
| ret = nc_server_config_cert_to_name(node, op); |
| } else if (!strcmp(name, "fingerprint")) { |
| ret = nc_server_config_fingerprint(node, op); |
| } else if (!strcmp(name, "tls-version")) { |
| ret = nc_server_config_tls_version(node, op); |
| } else if (!strcmp(name, "cipher-suite")) { |
| ret = nc_server_config_cipher_suite(node, op); |
| } else if (!strcmp(name, "crl-url")) { |
| ret = nc_server_config_crl_url(node, op); |
| } else if (!strcmp(name, "crl-path")) { |
| ret = nc_server_config_crl_path(node, op); |
| } else if (!strcmp(name, "crl-cert-ext")) { |
| ret = nc_server_config_crl_cert_ext(node, op); |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| else if (!strcmp(name, "netconf-client")) { |
| ret = nc_server_config_netconf_client(node, op); |
| } |
| #ifdef NC_ENABLED_SSH_TLS |
| else if (!strcmp(name, "remote-address")) { |
| ret = nc_server_config_remote_address(node, op); |
| } else if (!strcmp(name, "remote-port")) { |
| ret = nc_server_config_remote_port(node, op); |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| else if (!strcmp(name, "persistent")) { |
| ret = nc_server_config_persistent(node, op); |
| } else if (!strcmp(name, "periodic")) { |
| ret = nc_server_config_periodic(node, op); |
| } else if (!strcmp(name, "period")) { |
| ret = nc_server_config_period(node, op); |
| } else if (!strcmp(name, "anchor-time")) { |
| ret = nc_server_config_anchor_time(node, op); |
| } else if (!strcmp(name, "idle-timeout")) { |
| ret = nc_server_config_idle_timeout(node, op); |
| } else if (!strcmp(name, "reconnect-strategy")) { |
| ret = nc_server_config_reconnect_strategy(node, op); |
| } else if (!strcmp(name, "start-with")) { |
| ret = nc_server_config_start_with(node, op); |
| } else if (!strcmp(name, "max-wait")) { |
| ret = nc_server_config_max_wait(node, op); |
| } else if (!strcmp(name, "max-attempts")) { |
| ret = nc_server_config_max_attempts(node, op); |
| } |
| |
| if (ret) { |
| ERR(NULL, "Configuring node \"%s\" failed.", LYD_NAME(node)); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| nc_server_config_parse_tree(const struct lyd_node *node, NC_OPERATION parent_op, NC_MODULE module) |
| { |
| struct lyd_node *child; |
| struct lyd_meta *m; |
| NC_OPERATION current_op = NC_OP_UNKNOWN; |
| int ret; |
| |
| assert(node); |
| |
| /* get current op if there is any */ |
| if ((m = lyd_find_meta(node->meta, NULL, "yang:operation"))) { |
| if (!strcmp(lyd_get_meta_value(m), "create")) { |
| current_op = NC_OP_CREATE; |
| } else if (!strcmp(lyd_get_meta_value(m), "delete")) { |
| current_op = NC_OP_DELETE; |
| } else if (!strcmp(lyd_get_meta_value(m), "replace")) { |
| current_op = NC_OP_REPLACE; |
| } else if (!strcmp(lyd_get_meta_value(m), "none")) { |
| current_op = NC_OP_NONE; |
| } |
| } |
| |
| /* node has no op, inherit from the parent */ |
| if (!current_op) { |
| if (!parent_op) { |
| ERR(NULL, "Unknown operation for node \"%s\".", LYD_NAME(node)); |
| return 1; |
| } |
| |
| current_op = parent_op; |
| } |
| |
| switch (current_op) { |
| case NC_OP_NONE: |
| break; |
| case NC_OP_CREATE: |
| case NC_OP_DELETE: |
| case NC_OP_REPLACE: |
| #ifdef NC_ENABLED_SSH_TLS |
| if (module == NC_MODULE_KEYSTORE) { |
| ret = nc_server_config_parse_keystore(node, current_op); |
| } else if (module == NC_MODULE_TRUSTSTORE) { |
| ret = nc_server_config_parse_truststore(node, current_op); |
| } else |
| #endif /* NC_ENABLED_SSH_TLS */ |
| { |
| ret = nc_server_config_parse_netconf_server(node, current_op); |
| } |
| if (ret) { |
| return ret; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (current_op != NC_OP_DELETE) { |
| LY_LIST_FOR(lyd_child(node), child) { |
| if (nc_server_config_parse_tree(child, current_op, module)) { |
| return 1; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| API int |
| nc_server_config_load_modules(struct ly_ctx **ctx) |
| { |
| int i, new_ctx = 0; |
| |
| if (!*ctx) { |
| if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) { |
| ERR(NULL, "Couldn't create new libyang context.\n"); |
| goto error; |
| } |
| new_ctx = 1; |
| } |
| |
| /* all features */ |
| const char *ietf_nectonf_server[] = {"ssh-listen", "tls-listen", "ssh-call-home", "tls-call-home", "central-netconf-server-supported", NULL}; |
| /* all features */ |
| const char *ietf_x509_cert_to_name[] = {NULL}; |
| /* no private-key-encryption, csr-generation, p10-csr-format, certificate-expiration-notification, |
| * encrypted-passwords, hidden-symmetric-keys, encrypted-symmetric-keys, hidden-private-keys, encrypted-private-keys, |
| * one-symmetric-key-format, one-asymmetric-key-format, symmetrically-encrypted-value-format, |
| * asymmetrically-encrypted-value-format, cms-enveloped-data-format, cms-encrypted-data-format, |
| * cleartext-symmetric-keys */ |
| const char *ietf_crypto_types[] = {"cleartext-passwords", "cleartext-private-keys", NULL}; |
| /* all features */ |
| const char *ietf_tcp_common[] = {"keepalives-supported", NULL}; |
| /* all features */ |
| const char *ietf_tcp_server[] = {"tcp-server-keepalives", NULL}; |
| /* no proxy-connect, socks5-gss-api, socks5-username-password, local-binding-supported ? */ |
| const char *ietf_tcp_client[] = {"tcp-client-keepalives", NULL}; |
| /* no ssh-x509-certs, public-key-generation */ |
| const char *ietf_ssh_common[] = {"transport-params", NULL}; |
| /* no ssh-server-keepalives and local-user-auth-hostbased */ |
| const char *ietf_ssh_server[] = {"local-users-supported", "local-user-auth-publickey", "local-user-auth-password", "local-user-auth-none", NULL}; |
| /* all features */ |
| const char *iana_ssh_encryption_algs[] = {NULL}; |
| /* all features */ |
| const char *iana_ssh_key_exchange_algs[] = {NULL}; |
| /* all features */ |
| const char *iana_ssh_mac_algs[] = {NULL}; |
| /* all features */ |
| const char *iana_ssh_public_key_algs[] = {NULL}; |
| /* all features */ |
| const char *iana_crypt_hash[] = {"crypt-hash-md5", "crypt-hash-sha-256", "crypt-hash-sha-512", NULL}; |
| /* no symmetric-keys */ |
| const char *ietf_keystore[] = {"central-keystore-supported", "inline-definitions-supported", "asymmetric-keys", NULL}; |
| /* all features */ |
| const char *ietf_truststore[] = {"central-truststore-supported", "inline-definitions-supported", "certificates", "public-keys", NULL}; |
| /* no public-key-generation */ |
| const char *ietf_tls_common[] = {"tls10", "tls11", "tls12", "tls13", "hello-params", NULL}; |
| /* no tls-server-keepalives, server-ident-raw-public-key, server-ident-tls12-psk, server-ident-tls13-epsk, |
| * client-auth-raw-public-key, client-auth-tls12-psk, client-auth-tls13-epsk */ |
| const char *ietf_tls_server[] = {"server-ident-x509-cert", "client-auth-supported", "client-auth-x509-cert", NULL}; |
| /* all features */ |
| const char *iana_tls_cipher_suite_algs[] = {NULL}; |
| /* all features */ |
| const char *libnetconf2_netconf_server[] = {NULL}; |
| |
| const char *module_names[] = { |
| "ietf-netconf-server", "ietf-x509-cert-to-name", "ietf-crypto-types", "ietf-tcp-common", "ietf-tcp-server", |
| "ietf-tcp-client", "ietf-ssh-common", "ietf-ssh-server", "iana-ssh-encryption-algs", |
| "iana-ssh-key-exchange-algs", "iana-ssh-mac-algs", "iana-ssh-public-key-algs", "iana-crypt-hash", |
| "ietf-keystore", "ietf-truststore", "ietf-tls-common", "ietf-tls-server", "iana-tls-cipher-suite-algs", |
| "libnetconf2-netconf-server", NULL |
| }; |
| |
| const char **module_features[] = { |
| ietf_nectonf_server, ietf_x509_cert_to_name, ietf_crypto_types, ietf_tcp_common, |
| ietf_tcp_server, ietf_tcp_client, ietf_ssh_common, ietf_ssh_server, iana_ssh_encryption_algs, |
| iana_ssh_key_exchange_algs, iana_ssh_mac_algs, iana_ssh_public_key_algs, iana_crypt_hash, |
| ietf_keystore, ietf_truststore, ietf_tls_common, ietf_tls_server, iana_tls_cipher_suite_algs, |
| libnetconf2_netconf_server, NULL |
| }; |
| |
| for (i = 0; module_names[i] != NULL; i++) { |
| if (!ly_ctx_load_module(*ctx, module_names[i], NULL, module_features[i])) { |
| ERR(NULL, "Loading module \"%s\" failed.\n", module_names[i]); |
| goto error; |
| } |
| } |
| |
| return 0; |
| |
| error: |
| if (new_ctx) { |
| ly_ctx_destroy(*ctx); |
| *ctx = NULL; |
| } |
| return 1; |
| } |
| |
| static int |
| nc_server_config_fill_netconf_server(const struct lyd_node *data, NC_OPERATION op) |
| { |
| int ret = 0; |
| uint32_t log_options = 0; |
| struct lyd_node *tree; |
| |
| /* silently search for ietf-netconf-server, it may not be present */ |
| ly_temp_log_options(&log_options); |
| |
| ret = lyd_find_path(data, "/ietf-netconf-server:netconf-server", 0, &tree); |
| if (ret || (tree->flags & LYD_DEFAULT)) { |
| /* not found */ |
| ret = 0; |
| goto cleanup; |
| } |
| |
| if (nc_server_config_parse_tree(tree, op, NC_MODULE_NETCONF_SERVER)) { |
| ret = 1; |
| goto cleanup; |
| } |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| /* check and set all endpoint references */ |
| if (nc_server_config_check_endpt_references()) { |
| ret = 1; |
| goto cleanup; |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| cleanup: |
| /* reset the logging options back to what they were */ |
| ly_temp_log_options(NULL); |
| return ret; |
| } |
| |
| API int |
| nc_server_config_setup_diff(const struct lyd_node *data) |
| { |
| int ret = 0; |
| |
| NC_CHECK_ARG_RET(NULL, data, 1); |
| |
| /* LOCK */ |
| pthread_rwlock_wrlock(&server_opts.config_lock); |
| |
| #ifdef NC_ENABLED_SSH_TLS |
| /* configure keystore */ |
| ret = nc_server_config_fill_keystore(data, NC_OP_UNKNOWN); |
| if (ret) { |
| ERR(NULL, "Filling keystore failed."); |
| goto cleanup; |
| } |
| |
| /* configure truststore */ |
| ret = nc_server_config_fill_truststore(data, NC_OP_UNKNOWN); |
| if (ret) { |
| ERR(NULL, "Filling truststore failed."); |
| goto cleanup; |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| /* configure netconf-server */ |
| ret = nc_server_config_fill_netconf_server(data, NC_OP_UNKNOWN); |
| if (ret) { |
| ERR(NULL, "Filling netconf-server failed."); |
| goto cleanup; |
| } |
| |
| cleanup: |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.config_lock); |
| return ret; |
| } |
| |
| API int |
| nc_server_config_setup_data(const struct lyd_node *data) |
| { |
| int ret = 0; |
| struct lyd_node *tree, *iter, *root; |
| |
| NC_CHECK_ARG_RET(NULL, data, 1); |
| |
| /* LOCK */ |
| pthread_rwlock_wrlock(&server_opts.config_lock); |
| |
| /* find the netconf-server node */ |
| ret = lyd_find_path(data, "/ietf-netconf-server:netconf-server", 0, &root); |
| if (ret) { |
| ERR(NULL, "Unable to find the netconf-server container in the YANG data."); |
| goto cleanup; |
| } |
| |
| /* iterate through all the nodes and make sure there is no operation attribute */ |
| LY_LIST_FOR(root, tree) { |
| LYD_TREE_DFS_BEGIN(tree, iter) { |
| if (lyd_find_meta(iter->meta, NULL, "yang:operation")) { |
| ERR(NULL, "Unexpected operation attribute in the YANG data."); |
| ret = 1; |
| goto cleanup; |
| } |
| LYD_TREE_DFS_END(tree, iter); |
| } |
| } |
| |
| /* delete the current configuration */ |
| nc_server_config_listen(NULL, NC_OP_DELETE); |
| nc_server_config_ch(NULL, NC_OP_DELETE); |
| #ifdef NC_ENABLED_SSH_TLS |
| nc_server_config_ks_keystore(NULL, NC_OP_DELETE); |
| nc_server_config_ts_truststore(NULL, NC_OP_DELETE); |
| |
| /* configure keystore */ |
| ret = nc_server_config_fill_keystore(data, NC_OP_CREATE); |
| if (ret) { |
| ERR(NULL, "Filling keystore failed."); |
| goto cleanup; |
| } |
| |
| /* configure truststore */ |
| ret = nc_server_config_fill_truststore(data, NC_OP_CREATE); |
| if (ret) { |
| ERR(NULL, "Filling truststore failed."); |
| goto cleanup; |
| } |
| #endif /* NC_ENABLED_SSH_TLS */ |
| |
| /* configure netconf-server */ |
| ret = nc_server_config_fill_netconf_server(data, NC_OP_CREATE); |
| if (ret) { |
| ERR(NULL, "Filling netconf-server failed."); |
| goto cleanup; |
| } |
| |
| cleanup: |
| /* UNLOCK */ |
| pthread_rwlock_unlock(&server_opts.config_lock); |
| return ret; |
| } |
| |
| API int |
| nc_server_config_setup_path(const struct ly_ctx *ctx, const char *path) |
| { |
| struct lyd_node *tree = NULL; |
| int ret = 0; |
| |
| NC_CHECK_ARG_RET(NULL, path, 1); |
| |
| ret = lyd_parse_data_path(ctx, path, LYD_UNKNOWN, LYD_PARSE_NO_STATE | LYD_PARSE_STRICT, LYD_VALIDATE_NO_STATE, &tree); |
| if (ret) { |
| goto cleanup; |
| } |
| |
| ret = nc_server_config_setup_data(tree); |
| if (ret) { |
| goto cleanup; |
| } |
| |
| cleanup: |
| lyd_free_all(tree); |
| return ret; |
| } |