session CHANGE api completely rewritten

It was simply wrong before, global settings
for all listening connections is not enough.
diff --git a/src/session.c b/src/session.c
index f53e105..23cf3cf 100644
--- a/src/session.c
+++ b/src/session.c
@@ -488,28 +488,28 @@
     mod = ly_ctx_get_module(ctx, "ietf-netconf", NULL);
     if (mod) {
         if (lys_features_state(mod, "writable-running") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:writable-running:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:writable-running:1.0", &cpblts, &size, &count);
         }
         if (lys_features_state(mod, "candidate") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:candidate:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:candidate:1.0", &cpblts, &size, &count);
             if (lys_features_state(mod, "confirmed-commit") == 1) {
-                add_cpblt(ctx, "urn:ietf:params:netconf:confirmed-commit:1.1", &cpblts, &size, &count);
+                add_cpblt(ctx, "urn:ietf:params:netconf:capability:confirmed-commit:1.1", &cpblts, &size, &count);
             }
         }
         if (lys_features_state(mod, "rollback-on-error") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:rollback-on-error:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:rollback-on-error:1.0", &cpblts, &size, &count);
         }
         if (lys_features_state(mod, "validate") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:validate:1.1", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:validate:1.1", &cpblts, &size, &count);
         }
         if (lys_features_state(mod, "startup") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:startup:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:startup:1.0", &cpblts, &size, &count);
         }
         if (lys_features_state(mod, "url") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:url:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:url:1.0", &cpblts, &size, &count);
         }
         if (lys_features_state(mod, "xpath") == 1) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:xpath:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:xpath:1.0", &cpblts, &size, &count);
         }
     }
 
@@ -518,7 +518,7 @@
         if (!server_opts.wd_basic_mode) {
             VRB("with-defaults capability will not be advertised even though \"ietf-netconf-with-defaults\" model is present, unknown basic-mode.");
         } else {
-            strcpy(str, "urn:ietf:params:netconf:with-defaults:1.0");
+            strcpy(str, "urn:ietf:params:netconf:capability:with-defaults:1.0");
             switch (server_opts.wd_basic_mode) {
             case NC_WD_ALL:
                 strcat(str, "?basic-mode=report-all");
@@ -557,9 +557,9 @@
 
     mod = ly_ctx_get_module(ctx, "nc-notifications", NULL);
     if (mod) {
-        add_cpblt(ctx, "urn:ietf:params:netconf:notification:1.0", &cpblts, &size, &count);
+        add_cpblt(ctx, "urn:ietf:params:netconf:capability:notification:1.0", &cpblts, &size, &count);
         if (server_opts.interleave_capab) {
-            add_cpblt(ctx, "urn:ietf:params:netconf:interleave:1.0", &cpblts, &size, &count);
+            add_cpblt(ctx, "urn:ietf:params:netconf:capability:interleave:1.0", &cpblts, &size, &count);
         }
     }
 
diff --git a/src/session_client.c b/src/session_client.c
index f1db4bf..afd5756 100644
--- a/src/session_client.c
+++ b/src/session_client.c
@@ -41,29 +41,29 @@
 
 static const char *ncds2str[] = {NULL, "config", "url", "running", "startup", "candidate"};
 
-static char *schema_searchpath;
+static struct nc_client_opts client_opts;
 
 API int
-nc_schema_searchpath(const char *path)
+nc_client_schema_searchpath(const char *path)
 {
-    if (schema_searchpath) {
-        free(schema_searchpath);
+    if (client_opts.schema_searchpath) {
+        free(client_opts.schema_searchpath);
     }
 
     if (path) {
-        schema_searchpath = strdup(path);
-        if (!schema_searchpath) {
+        client_opts.schema_searchpath = strdup(path);
+        if (!client_opts.schema_searchpath) {
             ERRMEM;
             return 1;
         }
     } else {
-        schema_searchpath = NULL;
+        client_opts.schema_searchpath = NULL;
     }
 
     return 0;
 }
 
-/* SCHEMAS_DIR not used */
+/* SCHEMAS_DIR not used (implicitly) */
 static int
 ctx_check_and_load_model(struct nc_session *session, const char *cpblt)
 {
@@ -851,6 +851,102 @@
     return reply;
 }
 
+int
+nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
+{
+    int sock;
+
+    if (!address || !port) {
+        ERRARG;
+        return -1;
+    }
+
+    sock = nc_sock_listen(address, port);
+    if (sock == -1) {
+        return -1;
+    }
+
+    ++client_opts.ch_bind_count;
+    client_opts.ch_binds = realloc(client_opts.ch_binds, client_opts.ch_bind_count * sizeof *client_opts.ch_binds);
+
+    client_opts.ch_binds[client_opts.ch_bind_count - 1].address = strdup(address);
+    client_opts.ch_binds[client_opts.ch_bind_count - 1].port = port;
+    client_opts.ch_binds[client_opts.ch_bind_count - 1].sock = sock;
+    client_opts.ch_binds[client_opts.ch_bind_count - 1].ti = ti;
+
+    return 0;
+}
+
+int
+nc_client_ch_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
+{
+    uint32_t i;
+    int ret = -1;
+
+    if (!address && !port && !ti) {
+        for (i = 0; i < client_opts.ch_bind_count; ++i) {
+            close(client_opts.ch_binds[i].sock);
+            free((char *)client_opts.ch_binds[i].address);
+
+            ret = 0;
+        }
+        free(client_opts.ch_binds);
+        client_opts.ch_binds = NULL;
+        client_opts.ch_bind_count = 0;
+    } else {
+        for (i = 0; i < client_opts.ch_bind_count; ++i) {
+            if ((!address || !strcmp(client_opts.ch_binds[i].address, address))
+                    && (!port || (client_opts.ch_binds[i].port == port))
+                    && (!ti || (client_opts.ch_binds[i].ti == ti))) {
+                close(client_opts.ch_binds[i].sock);
+                free((char *)client_opts.ch_binds[i].address);
+
+                --client_opts.ch_bind_count;
+                memcpy(&client_opts.ch_binds[i], &client_opts.ch_binds[client_opts.ch_bind_count], sizeof *client_opts.ch_binds);
+
+                ret = 0;
+            }
+        }
+    }
+
+    return ret;
+}
+
+API int
+nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session)
+{
+    int sock;
+    char *host = NULL;
+    uint16_t port, idx;
+
+    if (!client_opts.ch_binds || !session) {
+        ERRARG;
+        return -1;
+    }
+
+    sock = nc_sock_accept_binds(client_opts.ch_binds, client_opts.ch_bind_count, timeout, &host, &port, &idx);
+
+    if (sock < 1) {
+        return sock;
+    }
+
+    if (client_opts.ch_binds[idx].ti == NC_TI_LIBSSH) {
+        *session = nc_accept_callhome_sock_ssh(sock, host, port, ctx);
+    } else if (client_opts.ch_binds[idx].ti == NC_TI_OPENSSL) {
+        *session = nc_accept_callhome_sock_tls(sock, host, port, ctx);
+    } else {
+        *session = NULL;
+    }
+
+    free(host);
+
+    if (!(*session)) {
+        return -1;
+    }
+
+    return 1;
+}
+
 API NC_MSG_TYPE
 nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout, struct nc_reply **reply)
 {
@@ -1431,67 +1527,3 @@
     *msgid = cur_msgid;
     return NC_MSG_RPC;
 }
-
-/* CALL HOME */
-
-int
-nc_sock_accept(int sock, int timeout, char **peer_host, uint16_t *peer_port)
-{
-    struct pollfd pfd = {-1, POLLIN, 0};
-    struct sockaddr_storage remote;
-    socklen_t addr_size = sizeof(remote);
-    int ret, status;
-
-    pfd.fd = sock;
-    pfd.revents = 0;
-    while (1) {
-        DBG("Waiting %ums for incoming Call Home connections.", timeout);
-        status = poll(&pfd, 1, timeout);
-
-        if (status == 0) {
-            /* timeout */
-            ERR("Timeout for Call Home listen expired.");
-            return -1;
-        } else if ((status == -1) && (errno == EINTR)) {
-            /* poll was interrupted - try it again */
-            continue;
-        } else if (status < 0) {
-            /* poll failed - something wrong happened */
-            ERR("Call Home poll failed (%s).", strerror(errno));
-            return -1;
-        } else if (status > 0) {
-            if (pfd.revents & (POLLHUP | POLLERR)) {
-                /* close pipe/fd - other side already did it */
-                ERR("Call Home listening socket was closed.");
-                return -1;
-            } else if (pfd.revents & POLLIN) {
-                /* accept call home */
-                ret = accept(pfd.fd, (struct sockaddr *)&remote, &addr_size);
-                break;
-            }
-        }
-    }
-
-    /* fill some server info, if interested */
-    if (remote.ss_family == AF_INET) {
-        struct sockaddr_in *remote_in = (struct sockaddr_in *)&remote;
-        if (peer_port) {
-            *peer_port = ntohs(remote_in->sin_port);
-        }
-        if (peer_host) {
-            *peer_host = malloc(INET6_ADDRSTRLEN);
-            inet_ntop(AF_INET, &(remote_in->sin_addr), *peer_host, INET6_ADDRSTRLEN);
-        }
-    } else if (remote.ss_family == AF_INET6) {
-        struct sockaddr_in6 *remote_in = (struct sockaddr_in6 *)&remote;
-        if (peer_port) {
-            *peer_port = ntohs(remote_in->sin6_port);
-        }
-        if (peer_host) {
-            *peer_host = malloc(INET6_ADDRSTRLEN);
-            inet_ntop(AF_INET6, &(remote_in->sin6_addr), *peer_host, INET6_ADDRSTRLEN);
-        }
-    }
-
-    return ret;
-}
diff --git a/src/session_client.h b/src/session_client.h
index ed03d4d..9f493d6 100644
--- a/src/session_client.h
+++ b/src/session_client.h
@@ -52,7 +52,7 @@
  * @param[in] path Directory where to search for YANG/YIN schemas.
  * @return 0 on success, 1 on (memory allocation) failure.
  */
-int nc_schema_searchpath(const char *path);
+int nc_client_schema_searchpath(const char *path);
 
 /**
  * @brief Connect to the NETCONF server via proviaded input/output file descriptors.
@@ -76,14 +76,185 @@
  */
 struct nc_session *nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx);
 
+#if defined(ENABLE_SSH) || defined(ENABLE_TLS)
+
+/* TODO */
+int nc_accept_callhome(int timeout, struct ly_ctx *ctx, struct nc_session **session);
+
+#endif /* ENABLE_SSH || ENABLE_TLS */
+
 #ifdef ENABLE_SSH
 
 /**
- * @brief Destroy any dynamically allocated SSH-specific client context.
+ * @brief Add an SSH public and private key pair to be used for client authentication.
+ *
+ * Private key can be encrypted, the passphrase will be asked for before using it.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] pub_key Path to the public key.
+ * @param[in] priv_key Path to the private key.
+ *
+ * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise.
+ */
+int nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key);
+
+/**
+ * @brief Remove an SSH public and private key pair that was used for client authentication.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] idx Index of the keypair starting with 0.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_del_keypair(int idx);
+
+/**
+ * @brief Get the number of public an private key pairs set to be used for client authentication.
+ *
+ * @return Keypair count.
+ */
+int nc_client_ssh_get_keypair_count(void);
+
+/**
+ * @brief Get a specific keypair set to be used for client authentication.
+ *
+ * @param[in] idx Index of the specific keypair.
+ * @param[out] pub_key Path to the public key.
+ * @param[out] priv_key Path to the private key.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key);
+
+/**
+ * @brief Set SSH authentication method preference.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] auth_type Authentication method to modify the prefrence of.
+ * @param[in] pref Preference of \p auth_type. Negative values disable the method.
+ */
+void nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref);
+
+/**
+ * @brief Get SSH authentication method preference.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] auth_type Authentication method to retrieve the prefrence of.
+ *
+ * @return Preference of the \p auth_type.
+ */
+int16_t nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type);
+
+/**
+ * @brief Set client SSH username used for authentication.
+ *
+ * @param[in] username Username to use.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_set_username(const char *username);
+
+/* Call Home */
+
+/**
+ * @brief Add a new client bind and start listening on it for SSH Call Home connections.
+ *
+ * @param[in] address IP address to bind to.
+ * @param[in] port Port to bind to.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_ch_add_bind_listen(const char *address, uint16_t port);
+
+/**
+ * @brief Remove an SSH listening client bind.
+ *
+ * @param[in] address IP address the socket was bound to. NULL matches all.
+ * @param[in] port Port the socket was bound to. 0 matches all.
+ * @return 0 on success, -1 on not found.
+ */
+int nc_client_ssh_ch_del_bind(const char *address, uint16_t port);
+
+/**
+ * @brief Add an SSH public and private key pair to be used for Call Home client authentication.
+ *
+ * Private key can be encrypted, the passphrase will be asked for before using it.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] pub_key Path to the public key.
+ * @param[in] priv_key Path to the private key.
+ *
+ * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise.
+ */
+int nc_client_ssh_ch_add_keypair(const char *pub_key, const char *priv_key);
+
+/**
+ * @brief Remove an SSH public and private key pair that was used for Call Home client authentication.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] idx Index of the keypair starting with 0.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_ch_del_keypair(int idx);
+
+/**
+ * @brief Get the number of public an private key pairs set to be used for Call Home client authentication.
+ *
+ * @return Keypair count.
+ */
+int nc_client_ssh_ch_get_keypair_count(void);
+
+/**
+ * @brief Get a specific keypair set to be used for Call Home client authentication.
+ *
+ * @param[in] idx Index of the specific keypair.
+ * @param[out] pub_key Path to the public key.
+ * @param[out] priv_key Path to the private key.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_ch_get_keypair(int idx, const char **pub_key, const char **priv_key);
+
+/**
+ * @brief Set SSH Call Home authentication method preference.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] auth_type Authentication method to modify the prefrence of.
+ * @param[in] pref Preference of \p auth_type. Negative values disable the method.
+ */
+void nc_client_ssh_ch_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref);
+
+/**
+ * @brief Get SSH Call Home authentication method preference.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
+ *
+ * @param[in] auth_type Authentication method to retrieve the prefrence of.
+ *
+ * @return Preference of the \p auth_type.
+ */
+int16_t nc_client_ssh_ch_get_auth_pref(NC_SSH_AUTH_TYPE auth_type);
+
+/**
+ * @brief Set client Call Home SSH username used for authentication.
+ *
+ * @param[in] username Username to use.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_ssh_ch_set_username(const char *username);
+
+/**
+ * @brief Destroy any dynamically allocated SSH-specific client context (including Call Home).
  *
  * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
  */
-void nc_ssh_client_destroy(void);
+void nc_client_ssh_destroy(void);
 
 /**
  * @brief Connect to the NETCONF server using SSH transport (via libssh).
@@ -96,8 +267,6 @@
  * @param[in] host Hostname or address (both Ipv4 and IPv6 are accepted) of the target server.
  *                 'localhost' is used by default if NULL is specified.
  * @param[in] port Port number of the target server. Default value 830 is used if 0 is specified.
- * @param[in] username Name of the user to login to the server. The user running the application (detected from the
- *                     effective UID) is used if NULL is specified.
  * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
  *                (ignoring what is actually supported by the server side). If not set,
  *                YANG context is created for the session using \<get-schema\> (if supported
@@ -107,7 +276,7 @@
  *                the session context will not include all the models supported by the server.
  * @return Created NETCONF session object or NULL on error.
  */
-struct nc_session *nc_connect_ssh(const char *host, uint16_t port, const char* username, struct ly_ctx *ctx);
+struct nc_session *nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx);
 
 /**
  * @brief Connect to the NETCONF server using the provided SSH (libssh) session.
@@ -150,124 +319,102 @@
  */
 struct nc_session *nc_connect_ssh_channel(struct nc_session *session, struct ly_ctx *ctx);
 
-/**
- * @brief Accept a Call Home SSH connection from a NETCONF server.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
- *
- * @param[in] host Host the NETCONF client will listen on.
- * @param[in] port Port the NETCONF client will listen on.
- * @param[in] username Name of the user to login to the server. The user running the application (detected from the
- *                     effective UID) is used if NULL is specified.
- * @param[in] timeout Timeout for reading in milliseconds. Use negative value for infinite
- *                    waiting and 0 for immediate return if data are not available on wire.
- * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
- *                (ignoring what is actually supported by the server side). If not set,
- *                YANG context is created for the session using \<get-schema\> (if supported
- *                by the server side) or/and by searching for YANG schemas in the searchpath
- *                (see nc_schema_searchpath()). In every case except not providing context
- *                to connect to a server supporting \<get-schema\> it is possible that
- *                the session context will not include all the models supported by the server.
- * @return Created NETCONF session object or NULL on error.
- */
-struct nc_session *nc_callhome_accept_ssh(const char *host, uint16_t port, const char *username, int timeout, struct ly_ctx *ctx);
-
-/**
- * @brief Add an SSH public and private key pair to be used for client authentication.
- *
- * Private key can be encrypted, the passphrase will be asked for before using it.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
- *
- * @param[in] pub_key Path to the public key.
- * @param[in] priv_key Path to the private key.
- *
- * @return EXIT_SUCCESS on success, EXIT_FAILURE otherwise.
- */
-int nc_ssh_client_add_keypair(const char *pub_key, const char *priv_key);
-
-/**
- * @brief Remove an SSH public and private key pair that was used for client authentication.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
- *
- * @param[in] idx Index of the keypair starting with 0.
- *
- * @return 0 on success, -1 on error.
- */
-int nc_ssh_client_del_keypair(int idx);
-
-/**
- * @brief Get the number of public an private key pairs set to be used for client authentication.
- *
- * @return Keypair count.
- */
-int nc_ssh_client_get_keypair_count(void);
-
-/**
- * @brief Get a specific keypair set to be used for client authentication.
- *
- * @param[in] idx Index of the specific keypair.
- * @param[out] pub_key Path to the public key.
- * @param[out] priv_key Path to the private key.
- *
- * @return 0 on success, -1 on error.
- */
-int nc_ssh_client_get_keypair(int idx, const char **pub_key, const char **priv_key);
-
-/**
- * @brief Set SSH authentication method preference.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
- *
- * @param[in] auth_type Authentication method to modify the prefrence of.
- * @param[in] pref Preference of \p auth_type. Negative values disable the method.
- */
-void nc_ssh_client_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, short int pref);
-
-/**
- * @brief Get SSH authentication method preference.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with libssh support.
- *
- * @param[in] auth_type Authentication method to retrieve the prefrence of.
- *
- * @return Preference of the \p auth_type.
- */
-short int nc_ssh_client_get_auth_pref(NC_SSH_AUTH_TYPE auth_type);
-
 #endif /* ENABLE_SSH */
 
 #ifdef ENABLE_TLS
 
 /**
- * @brief Initialize libssl context with certificates.
- *
- * Must be called before calling nc_connect_tls() or nc_connect_libssl()!
+ * @brief Set client authentication identity - a certificate and a private key.
  *
  * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
  *
  * @param[in] client_cert Path to the file containing the client certificate. If NULL, only initializes libssl/libcrypto.
  * @param[in] client_key Path to the file containing the private key for the \p client_cert.
  *                       If NULL, key is expected to be stored with \p client_cert.
+ *
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_set_cert_key(const char *client_cert, const char *client_key);
+
+/**
+ * @brief Set client trusted CA certificates.
+ *
  * @param[in] ca_file Location of the CA certificate file used to verify server certificates.
  *                    For more info, see the documentation for SSL_CTX_load_verify_locations() from OpenSSL.
  * @param[in] ca_dir Location of the CA certificates directory used to verify the server certificates.
  *                   For more info, see the documentation for SSL_CTX_load_verify_locations() from OpenSSL.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_set_trusted_ca_certs(const char *ca_file, const char *ca_dir);
+
+/**
+ * @brief Set client Certificate Revocation Lists.
+ *
  * @param[in] crl_file Location of the CRL certificate file used to check for revocated certificates.
  * @param[in] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_set_crl(const char *crl_file, const char *crl_dir);
+
+/* Call Home */
+
+/**
+ * @brief Add a new client bind and start listening on it for TLS Call Home connections.
+ *
+ * @param[in] address IP address to bind to.
+ * @param[in] port Port to bind to.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port);
+
+/**
+ * @brief Remove a TLS listening client bind.
+ *
+ * @param[in] address IP address the socket was bound to. NULL matches all.
+ * @param[in] port Port the socket was bound to. 0 matches all.
+ * @return 0 on success, -1 on not found.
+ */
+int nc_client_tls_ch_del_bind(const char *address, uint16_t port);
+
+/**
+ * @brief Set client Call Home authentication identity - a certificate and a private key.
+ *
+ * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
+ *
+ * @param[in] client_cert Path to the file containing the client certificate. If NULL, only initializes libssl/libcrypto.
+ * @param[in] client_key Path to the file containing the private key for the \p client_cert.
+ *                       If NULL, key is expected to be stored with \p client_cert.
  *
  * @return 0 on success, -1 on error.
  */
-int nc_tls_client_init(const char *client_cert, const char *client_key, const char *ca_file, const char *ca_dir,
-                       const char *crl_file, const char *crl_dir);
+int nc_client_tls_ch_set_cert_key(const char *client_cert, const char *client_key);
 
 /**
- * @brief Destroy any dynamically allocated TLS-specific client data.
+ * @brief Set client Call Home trusted CA certificates.
+ *
+ * @param[in] ca_file Location of the CA certificate file used to verify server certificates.
+ *                    For more info, see the documentation for SSL_CTX_load_verify_locations() from OpenSSL.
+ * @param[in] ca_dir Location of the CA certificates directory used to verify the server certificates.
+ *                   For more info, see the documentation for SSL_CTX_load_verify_locations() from OpenSSL.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_ch_set_trusted_ca_certs(const char *ca_file, const char *ca_dir);
+
+/**
+ * @brief Set client Call Home Certificate Revocation Lists.
+ *
+ * @param[in] crl_file Location of the CRL certificate file used to check for revocated certificates.
+ * @param[in] crl_dir Location of the CRL certificate directory used to check for revocated certificates.
+ * @return 0 on success, -1 on error.
+ */
+int nc_client_tls_ch_set_crl(const char *crl_file, const char *crl_dir);
+
+/**
+ * @brief Destroy any dynamically allocated TLS-specific client data (including Call Home).
  *
  * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
  */
-void nc_tls_client_destroy(void);
+void nc_client_tls_destroy(void);
 
 /**
  * @brief Connect to the NETCONF server using TLS transport (via libssl)
@@ -310,26 +457,6 @@
  */
 struct nc_session *nc_connect_libssl(SSL *tls, struct ly_ctx *ctx);
 
-/**
- * @brief Accept a Call Home TLS connection from a NETCONF server.
- *
- * Function is provided only via nc_client.h header file and only when libnetconf2 is compiled with TLS support.
- *
- * @param[in] host Host the NETCONF client will listen on.
- * @param[in] port Port the NETCONF client will listen on.
- * @param[in] timeout Timeout for reading in milliseconds. Use negative value for infinite
- *                    waiting and 0 for immediate return if data are not available on wire.
- * @param[in] ctx Optional parameter. If set, provides strict YANG context for the session
- *                (ignoring what is actually supported by the server side). If not set,
- *                YANG context is created for the session using \<get-schema\> (if supported
- *                by the server side) or/and by searching for YANG schemas in the searchpath
- *                (see nc_schema_searchpath()). In every case except not providing context
- *                to connect to a server supporting \<get-schema\> it is possible that
- *                the session context will not include all the models supported by the server.
- * @return Created NETCONF session object or NULL on error.
- */
-struct nc_session *nc_callhome_accept_tls(const char *host, uint16_t port, int timeout, struct ly_ctx *ctx);
-
 #endif /* ENABLE_TLS */
 
 /**
diff --git a/src/session_client_ssh.c b/src/session_client_ssh.c
index cfa0f55..4e4a36f 100644
--- a/src/session_client_ssh.c
+++ b/src/session_client_ssh.c
@@ -48,12 +48,16 @@
 
 #include "libnetconf.h"
 
-static struct nc_ssh_client_opts ssh_opts = {
+static struct nc_client_ssh_opts ssh_opts = {
     .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 3}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 1}}
 };
 
+static struct nc_client_ssh_opts ssh_ch_opts = {
+    .auth_pref = {{NC_SSH_AUTH_INTERACTIVE, 1}, {NC_SSH_AUTH_PASSWORD, 2}, {NC_SSH_AUTH_PUBLICKEY, 3}}
+};
+
 API void
-nc_ssh_client_destroy(void)
+nc_client_ssh_destroy(void)
 {
     int i;
 
@@ -544,8 +548,8 @@
     return -1;
 }
 
-API int
-nc_ssh_client_add_keypair(const char *pub_key, const char *priv_key)
+static int
+_nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key, struct nc_client_ssh_opts *opts)
 {
     int i;
     FILE *key;
@@ -556,15 +560,15 @@
         return -1;
     }
 
-    for (i = 0; i < ssh_opts.key_count; ++i) {
-        if (!strcmp(ssh_opts.keys[i].pubkey_path, pub_key) || !strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
-            if (strcmp(ssh_opts.keys[i].pubkey_path, pub_key)) {
+    for (i = 0; i < opts->key_count; ++i) {
+        if (!strcmp(opts->keys[i].pubkey_path, pub_key) || !strcmp(opts->keys[i].privkey_path, priv_key)) {
+            if (strcmp(opts->keys[i].pubkey_path, pub_key)) {
                 WRN("Private key \"%s\" found with another public key \"%s\".",
-                    priv_key, ssh_opts.keys[i].pubkey_path);
+                    priv_key, opts->keys[i].pubkey_path);
                 continue;
-            } else if (strcmp(ssh_opts.keys[i].privkey_path, priv_key)) {
+            } else if (strcmp(opts->keys[i].privkey_path, priv_key)) {
                 WRN("Public key \"%s\" found with another private key \"%s\".",
-                    pub_key, ssh_opts.keys[i].privkey_path);
+                    pub_key, opts->keys[i].privkey_path);
                 continue;
             }
 
@@ -573,12 +577,12 @@
         }
     }
 
-    /* add the keys safely */
-    ++ssh_opts.key_count;
-    ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
-    ssh_opts.keys[ssh_opts.key_count - 1].pubkey_path = strdup(pub_key);
-    ssh_opts.keys[ssh_opts.key_count - 1].privkey_path = strdup(priv_key);
-    ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 0;
+    /* add the keys */
+    ++opts->key_count;
+    opts->keys = realloc(opts->keys, opts->key_count * sizeof *opts->keys);
+    opts->keys[opts->key_count - 1].pubkey_path = strdup(pub_key);
+    opts->keys[opts->key_count - 1].privkey_path = strdup(priv_key);
+    opts->keys[opts->key_count - 1].privkey_crypt = 0;
 
     /* check encryption */
     if ((key = fopen(priv_key, "r"))) {
@@ -596,7 +600,7 @@
         }
         fclose(key);
         if (strcasestr(line, "encrypted")) {
-            ssh_opts.keys[ssh_opts.key_count - 1].privkey_crypt = 1;
+            opts->keys[opts->key_count - 1].privkey_crypt = 1;
         }
     }
 
@@ -604,80 +608,195 @@
 }
 
 API int
-nc_ssh_client_del_keypair(int idx)
+nc_client_ssh_add_keypair(const char *pub_key, const char *priv_key)
 {
-    if (idx >= ssh_opts.key_count) {
+    return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_opts);
+}
+
+API int
+nc_client_ssh_ch_add_keypair(const char *pub_key, const char *priv_key)
+{
+    return _nc_client_ssh_add_keypair(pub_key, priv_key, &ssh_ch_opts);
+}
+
+static int
+_nc_client_ssh_del_keypair(int idx, struct nc_client_ssh_opts *opts)
+{
+    if (idx >= opts->key_count) {
         ERRARG;
         return -1;
     }
 
-    free(ssh_opts.keys[idx].pubkey_path);
-    free(ssh_opts.keys[idx].privkey_path);
+    free(opts->keys[idx].pubkey_path);
+    free(opts->keys[idx].privkey_path);
 
-    --ssh_opts.key_count;
+    --opts->key_count;
 
-    memcpy(ssh_opts.keys + idx, ssh_opts.keys + ssh_opts.key_count, sizeof *ssh_opts.keys);
-    ssh_opts.keys = realloc(ssh_opts.keys, ssh_opts.key_count * sizeof *ssh_opts.keys);
+    memcpy(opts->keys + idx, opts->keys + opts->key_count, sizeof *opts->keys);
+    opts->keys = realloc(opts->keys, opts->key_count * sizeof *opts->keys);
 
     return 0;
 }
 
 API int
-nc_ssh_client_get_keypair_count(void)
+nc_client_ssh_del_keypair(int idx)
 {
-    return ssh_opts.key_count;
+    return _nc_client_ssh_del_keypair(idx, &ssh_opts);
 }
 
 API int
-nc_ssh_client_get_keypair(int idx, const char **pub_key, const char **priv_key)
+nc_client_ssh_ch_del_keypair(int idx)
 {
-    if ((idx >= ssh_opts.key_count) || (!pub_key && !priv_key)) {
+    return _nc_client_ssh_del_keypair(idx, &ssh_ch_opts);
+}
+
+static int
+_nc_client_ssh_get_keypair_count(struct nc_client_ssh_opts *opts)
+{
+    return opts->key_count;
+}
+
+API int
+nc_client_ssh_get_keypair_count(void)
+{
+    return _nc_client_ssh_get_keypair_count(&ssh_opts);
+}
+
+API int
+nc_client_ssh_ch_get_keypair_count(void)
+{
+    return _nc_client_ssh_get_keypair_count(&ssh_ch_opts);
+}
+
+static int
+_nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key, struct nc_client_ssh_opts *opts)
+{
+    if ((idx >= opts->key_count) || (!pub_key && !priv_key)) {
         ERRARG;
         return -1;
     }
 
     if (pub_key) {
-        *pub_key = ssh_opts.keys[idx].pubkey_path;
+        *pub_key = opts->keys[idx].pubkey_path;
     }
     if (priv_key) {
-        *priv_key = ssh_opts.keys[idx].privkey_path;
+        *priv_key = opts->keys[idx].privkey_path;
     }
 
     return 0;
 }
 
-API void
-nc_ssh_client_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, short int pref)
+API int
+nc_client_ssh_get_keypair(int idx, const char **pub_key, const char **priv_key)
+{
+    return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_opts);
+}
+
+API int
+nc_client_ssh_ch_get_keypair(int idx, const char **pub_key, const char **priv_key)
+{
+    return _nc_client_ssh_get_keypair(idx, pub_key, priv_key, &ssh_ch_opts);
+}
+
+static void
+_nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref, struct nc_client_ssh_opts *opts)
 {
     if (pref < 0) {
         pref = -1;
     }
 
     if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
-        ssh_opts.auth_pref[0].value = pref;
+        opts->auth_pref[0].value = pref;
     } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
-        ssh_opts.auth_pref[1].value = pref;
+        opts->auth_pref[1].value = pref;
     } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
-        ssh_opts.auth_pref[2].value = pref;
+        opts->auth_pref[2].value = pref;
     }
 }
 
-API short int
-nc_ssh_client_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
+API void
+nc_client_ssh_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
 {
-    short int pref = 0;
+    _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_opts);
+}
+
+API void
+nc_client_ssh_ch_set_auth_pref(NC_SSH_AUTH_TYPE auth_type, int16_t pref)
+{
+    _nc_client_ssh_set_auth_pref(auth_type, pref, &ssh_ch_opts);
+}
+
+static int16_t
+_nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type, struct nc_client_ssh_opts *opts)
+{
+    int16_t pref = 0;
 
     if (auth_type == NC_SSH_AUTH_INTERACTIVE) {
-        pref = ssh_opts.auth_pref[0].value;
+        pref = opts->auth_pref[0].value;
     } else if (auth_type == NC_SSH_AUTH_PASSWORD) {
-        pref = ssh_opts.auth_pref[1].value;
+        pref = opts->auth_pref[1].value;
     } else if (auth_type == NC_SSH_AUTH_PUBLICKEY) {
-        pref = ssh_opts.auth_pref[2].value;
+        pref = opts->auth_pref[2].value;
     }
 
     return pref;
 }
 
+API int16_t
+nc_client_ssh_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
+{
+    return _nc_client_ssh_get_auth_pref(auth_type, &ssh_opts);
+}
+
+API int16_t
+nc_client_ssh_ch_get_auth_pref(NC_SSH_AUTH_TYPE auth_type)
+{
+    return _nc_client_ssh_get_auth_pref(auth_type, &ssh_ch_opts);
+}
+
+static int
+_nc_client_ssh_set_username(const char *username, struct nc_client_ssh_opts *opts)
+{
+    if (opts->username) {
+        free(opts->username);
+    }
+    if (username) {
+        opts->username = strdup(username);
+        if (!opts->username) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->username = NULL;
+    }
+
+    return 0;
+}
+
+API int
+nc_client_ssh_set_username(const char *username)
+{
+    return _nc_client_ssh_set_username(username, &ssh_opts);
+}
+
+API int
+nc_client_ssh_ch_set_username(const char *username)
+{
+    return _nc_client_ssh_set_username(username, &ssh_ch_opts);
+}
+
+API int
+nc_client_ssh_ch_add_bind_listen(const char *address, uint16_t port)
+{
+    return nc_client_ch_add_bind_listen(address, port, NC_TI_LIBSSH);
+}
+
+API int
+nc_client_ssh_ch_del_bind(const char *address, uint16_t port)
+{
+    return nc_client_ch_del_bind(address, port, NC_TI_LIBSSH);
+}
+
 /* Establish a secure SSH connection, authenticate, and create a channel with the 'netconf' subsystem.
  * Host, port, username, and a connected socket is expected to be set.
  */
@@ -879,10 +998,11 @@
 }
 
 API struct nc_session *
-nc_connect_ssh(const char *host, uint16_t port, const char *username, struct ly_ctx *ctx)
+nc_connect_ssh(const char *host, uint16_t port, struct ly_ctx *ctx)
 {
     const int timeout = NC_SSH_TIMEOUT;
     int sock;
+    char *username;
     struct passwd *pw;
     struct nc_session *session = NULL;
 
@@ -895,7 +1015,7 @@
         port = NC_PORT_SSH;
     }
 
-    if (!username) {
+    if (!ssh_opts.username) {
         pw = getpwuid(getuid());
         if (!pw) {
             ERR("Unknown username for the SSH connection (%s).", strerror(errno));
@@ -903,6 +1023,8 @@
         } else {
             username = pw->pw_name;
         }
+    } else {
+        username = ssh_opts.username;
     }
 
     /* prepare session structure */
@@ -1051,13 +1173,16 @@
 
         /* remember username */
         if (!username) {
-            pw = getpwuid(getuid());
-            if (!pw) {
-                ERR("Unknown username for the SSH connection (%s).", strerror(errno));
-                goto fail;
+            if (!ssh_opts.username) {
+                pw = getpwuid(getuid());
+                if (!pw) {
+                    ERR("Unknown username for the SSH connection (%s).", strerror(errno));
+                    goto fail;
+                }
+                username = strdup(pw->pw_name);
+            } else {
+                username = strdup(ssh_opts.username);
             }
-
-            username = strdup(pw->pw_name);
             ssh_options_set(session->ti.libssh.session, SSH_OPTIONS_USER, username);
         }
 
@@ -1190,30 +1315,13 @@
     return NULL;
 }
 
-API struct nc_session *
-nc_callhome_accept_ssh(const char *host, uint16_t port, const char *username, int timeout, struct ly_ctx *ctx)
+struct nc_session *
+nc_accept_callhome_sock_ssh(int sock, const char *host, uint16_t port, struct ly_ctx *ctx)
 {
     const int ssh_timeout = NC_SSH_TIMEOUT;
-    int sock, listen_sock;
-    char *server_host;
+    struct passwd *pw;
     ssh_session sess;
 
-    if (!port) {
-        port = NC_PORT_CH_SSH;
-    }
-
-    listen_sock = nc_sock_listen(host, port);
-    if (listen_sock < 0) {
-        return NULL;
-    }
-
-    sock = nc_sock_accept(listen_sock, timeout, &server_host, NULL);
-    close(listen_sock);
-
-    if (sock == -1) {
-        return NULL;
-    }
-
     sess = ssh_new();
     if (!sess) {
         ERR("Unable to initialize an SSH session.");
@@ -1222,11 +1330,18 @@
     }
 
     ssh_options_set(sess, SSH_OPTIONS_FD, &sock);
-    ssh_options_set(sess, SSH_OPTIONS_HOST, server_host);
+    ssh_options_set(sess, SSH_OPTIONS_HOST, host);
     ssh_options_set(sess, SSH_OPTIONS_PORT, &port);
     ssh_options_set(sess, SSH_OPTIONS_TIMEOUT, &ssh_timeout);
-    if (username) {
-        ssh_options_set(sess, SSH_OPTIONS_USER, username);
+    if (!ssh_ch_opts.username) {
+        pw = getpwuid(getuid());
+        if (!pw) {
+            ERR("Unknown username for the SSH connection (%s).", strerror(errno));
+            return NULL;
+        }
+        ssh_options_set(sess, SSH_OPTIONS_USER, pw->pw_name);
+    } else {
+        ssh_options_set(sess, SSH_OPTIONS_USER, ssh_ch_opts.username);
     }
     if (ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS,
                         "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"
@@ -1235,7 +1350,5 @@
         ssh_options_set(sess, SSH_OPTIONS_HOSTKEYS, "ssh-ed25519,ssh-rsa,ssh-dss,ssh-rsa1");
     }
 
-    free(server_host);
-
     return nc_connect_libssh(sess, ctx);
 }
diff --git a/src/session_client_tls.c b/src/session_client_tls.c
index cb1bc9b..f999c21 100644
--- a/src/session_client_tls.c
+++ b/src/session_client_tls.c
@@ -33,7 +33,10 @@
 
 #include "libnetconf.h"
 
-static struct nc_tls_client_opts tls_opts;
+static struct nc_client_tls_opts tls_opts;
+static struct nc_client_tls_opts tls_ch_opts;
+
+static int tlsauth_ch;
 
 static int
 tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
@@ -47,11 +50,19 @@
     EVP_PKEY *pubkey;
     int i, n, rc;
     ASN1_TIME *next_update = NULL;
+    struct nc_client_tls_opts *opts;
 
     if (!preverify_ok) {
         return 0;
     }
 
+    opts = (tlsauth_ch ? &tls_ch_opts : &tls_opts);
+
+    if (!opts->crl_store) {
+        /* nothing to check */
+        return 1;
+    }
+
     cert = X509_STORE_CTX_get_current_cert(x509_ctx);
     subject = X509_get_subject_name(cert);
     issuer = X509_get_issuer_name(cert);
@@ -59,7 +70,7 @@
     /* try to retrieve a CRL corresponding to the _subject_ of
      * the current certificate in order to verify it's integrity */
     memset((char *)&obj, 0, sizeof obj);
-    X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
+    X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL);
     rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
     X509_STORE_CTX_cleanup(&store_ctx);
     crl = obj.data.crl;
@@ -97,7 +108,7 @@
     /* try to retrieve a CRL corresponding to the _issuer_ of
      * the current certificate in order to check for revocation */
     memset((char *)&obj, 0, sizeof obj);
-    X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
+    X509_STORE_CTX_init(&store_ctx, opts->crl_store, NULL, NULL);
     rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
     X509_STORE_CTX_cleanup(&store_ctx);
     crl = obj.data.crl;
@@ -119,94 +130,280 @@
     return 1; /* success */
 }
 
-API int
-nc_tls_client_init(const char *client_cert, const char *client_key, const char *ca_file, const char *ca_dir,
-                   const char *crl_file, const char *crl_dir)
+static int
+_nc_client_tls_set_cert_key(const char *client_cert, const char *client_key, int ch)
 {
-    const char *key_ = client_key;
-    X509_LOOKUP *lookup;
-
-    if (tls_opts.tls_ctx) {
-        VRB("TLS context reinitialization.");
-        SSL_CTX_free(tls_opts.tls_ctx);
-        tls_opts.tls_ctx = NULL;
-    }
+    struct nc_client_tls_opts *opts;
 
     if (!client_cert) {
         ERRARG;
         return -1;
     }
 
-    /* prepare global SSL context, allow only mandatory TLS 1.2  */
-    if (!(tls_opts.tls_ctx = SSL_CTX_new(TLSv1_2_client_method()))) {
-        ERR("Unable to create OpenSSL context (%s).", ERR_reason_error_string(ERR_get_error()));
-        return -1;
-    }
+    opts = (ch ? &tls_ch_opts : &tls_opts);
 
-    if (crl_file || crl_dir) {
-        /* set the revocation store with the correct paths for the callback */
-        tls_opts.tls_store = X509_STORE_new();
-        tls_opts.tls_store->cache = 0;
+    free(opts->cert_path);
+    free(opts->key_path);
 
-        if (crl_file) {
-            if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_file()))) {
-                ERR("Failed to add lookup method to CRL checking.");
-                return -1;
-            }
-            if (X509_LOOKUP_add_dir(lookup, crl_file, X509_FILETYPE_PEM) != 1) {
-                ERR("Failed to add the revocation lookup file \"%s\".", crl_file);
-                return -1;
-            }
+    if (client_cert) {
+        opts->cert_path = strdup(client_cert);
+        if (!opts->cert_path) {
+            ERRMEM;
+            return -1;
         }
-
-        if (crl_dir) {
-            if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_hash_dir()))) {
-                ERR("Failed to add lookup method to CRL checking.");
-                return -1;
-            }
-            if (X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM) != 1) {
-                ERR("Failed to add the revocation lookup directory \"%s\".", crl_dir);
-                return -1;
-            }
-        }
-
-        SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
     } else {
-        /* CRL checking will be skipped */
-        SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, NULL);
+        opts->cert_path = NULL;
     }
 
-    /* get peer certificate */
-    if (SSL_CTX_use_certificate_file(tls_opts.tls_ctx, client_cert, SSL_FILETYPE_PEM) != 1) {
-        ERR("Loading a peer certificate from \'%s\' failed (%s).", client_cert, ERR_reason_error_string(ERR_get_error()));
-        return -1;
+    if (client_key) {
+        opts->key_path = strdup(client_key);
+        if (!opts->cert_path) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->key_path = NULL;
     }
 
-    if (!key_) {
-        /*
-         * if the file with private key not specified, expect that the private
-         * key is stored altogether with the certificate
-         */
-        key_ = client_cert;
-    }
-    if (SSL_CTX_use_PrivateKey_file(tls_opts.tls_ctx, key_, SSL_FILETYPE_PEM) != 1) {
-        ERR("Loading the client certificate from \'%s\' failed (%s).", key_, ERR_reason_error_string(ERR_get_error()));
-        return -1;
-    }
-
-    if (!SSL_CTX_load_verify_locations(tls_opts.tls_ctx, ca_file, ca_dir)) {
-        ERR("Failed to load the locations of trusted CA certificates (%s).", ERR_reason_error_string(ERR_get_error()));
-        return -1;
-    }
+    opts->tls_ctx_change = 1;
 
     return 0;
 }
 
-API void
-nc_tls_client_destroy(void)
+API int
+nc_client_tls_set_cert_key(const char *client_cert, const char *client_key)
 {
-    SSL_CTX_free(tls_opts.tls_ctx);
-    tls_opts.tls_ctx = NULL;
+    return _nc_client_tls_set_cert_key(client_cert, client_key, 0);
+}
+
+API int
+nc_client_tls_ch_set_cert_key(const char *client_cert, const char *client_key)
+{
+    return _nc_client_tls_set_cert_key(client_cert, client_key, 1);
+}
+
+static int
+_nc_client_tls_set_trusted_ca_certs(const char *ca_file, const char *ca_dir, int ch)
+{
+    struct nc_client_tls_opts *opts;
+
+    if (!ca_file && !ca_dir) {
+        ERRARG;
+        return -1;
+    }
+
+    opts = (ch ? &tls_ch_opts : &tls_opts);
+
+    free(opts->ca_file);
+    free(opts->ca_dir);
+
+    if (ca_file) {
+        opts->ca_file = strdup(ca_file);
+        if (!opts->ca_file) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->ca_file = NULL;
+    }
+
+    if (ca_dir) {
+        opts->ca_dir = strdup(ca_dir);
+        if (!opts->ca_dir) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->ca_dir = NULL;
+    }
+
+    opts->tls_ctx_change = 1;
+
+    return 0;
+}
+
+API int
+nc_client_tls_set_trusted_ca_certs(const char *ca_file, const char *ca_dir)
+{
+    return _nc_client_tls_set_trusted_ca_certs(ca_file, ca_dir, 0);
+}
+
+API int
+nc_client_tls_ch_set_trusted_ca_certs(const char *ca_file, const char *ca_dir)
+{
+    return _nc_client_tls_set_trusted_ca_certs(ca_file, ca_dir, 1);
+}
+
+static int
+_nc_client_tls_set_crl(const char *crl_file, const char *crl_dir, int ch)
+{
+    struct nc_client_tls_opts *opts;
+
+    if (!crl_file && !crl_dir) {
+        ERRARG;
+        return -1;
+    }
+
+    opts = (ch ? &tls_ch_opts : &tls_opts);
+
+    free(opts->crl_file);
+    free(opts->crl_dir);
+
+    if (crl_file) {
+        opts->crl_file = strdup(crl_file);
+        if (!opts->crl_file) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->crl_file = NULL;
+    }
+
+    if (crl_dir) {
+        opts->crl_dir = strdup(crl_dir);
+        if (!opts->crl_dir) {
+            ERRMEM;
+            return -1;
+        }
+    } else {
+        opts->crl_dir = NULL;
+    }
+
+    opts->crl_store_change = 1;
+
+    return 0;
+}
+
+API int
+nc_client_tls_set_crl(const char *crl_file, const char *crl_dir)
+{
+    return _nc_client_tls_set_crl(crl_file, crl_dir, 0);
+}
+
+API int
+nc_client_tls_ch_set_crl(const char *crl_file, const char *crl_dir)
+{
+    return _nc_client_tls_set_crl(crl_file, crl_dir, 1);
+}
+
+API int
+nc_client_tls_ch_add_bind_listen(const char *address, uint16_t port)
+{
+    return nc_client_ch_add_bind_listen(address, port, NC_TI_OPENSSL);
+}
+
+API int
+nc_client_tls_ch_del_bind(const char *address, uint16_t port)
+{
+    return nc_client_ch_del_bind(address, port, NC_TI_OPENSSL);
+}
+
+API void
+nc_client_tls_destroy(void)
+{
+    int count = 0;
+    struct nc_client_tls_opts *opts;
+
+repeat:
+    if (count == 0) {
+        opts = &tls_ch_opts;
+    } else if (count == 1) {
+        opts = &tls_opts;
+    } else {
+        return;
+    }
+
+    free(opts->cert_path);
+    free(opts->key_path);
+    free(opts->ca_file);
+    free(opts->ca_dir);
+    SSL_CTX_free(opts->tls_ctx);
+
+    free(opts->crl_file);
+    free(opts->crl_dir);
+    X509_STORE_free(opts->crl_store);
+
+    ++count;
+    goto repeat;
+}
+
+static int
+nc_client_tls_opts_update(int ch)
+{
+    char *key;
+    X509_LOOKUP *lookup;
+    struct nc_client_tls_opts *opts;
+
+    opts = (ch ? &tls_ch_opts : &tls_opts);
+
+    if (!opts->tls_ctx || opts->tls_ctx_change) {
+        SSL_CTX_free(tls_opts.tls_ctx);
+
+        /* prepare global SSL context, allow only mandatory TLS 1.2  */
+        if (!(opts->tls_ctx = SSL_CTX_new(TLSv1_2_client_method()))) {
+            ERR("Unable to create OpenSSL context (%s).", ERR_reason_error_string(ERR_get_error()));
+            return -1;
+        }
+        SSL_CTX_set_verify(opts->tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
+
+        /* get peer certificate */
+        if (SSL_CTX_use_certificate_file(opts->tls_ctx, opts->cert_path, SSL_FILETYPE_PEM) != 1) {
+            ERR("Loading the client certificate from \'%s\' failed (%s).", opts->cert_path, ERR_reason_error_string(ERR_get_error()));
+            return -1;
+        }
+
+        /* if the file with private key not specified, expect that the private key is stored with the certificate */
+        if (!opts->key_path) {
+            key = opts->cert_path;
+        } else {
+            key = opts->key_path;
+        }
+        if (SSL_CTX_use_PrivateKey_file(opts->tls_ctx, key, SSL_FILETYPE_PEM) != 1) {
+            ERR("Loading the client priavte key from \'%s\' failed (%s).", key, ERR_reason_error_string(ERR_get_error()));
+            return -1;
+        }
+
+        if (!SSL_CTX_load_verify_locations(opts->tls_ctx, opts->ca_file, opts->ca_dir)) {
+            ERR("Failed to load the locations of trusted CA certificates (%s).", ERR_reason_error_string(ERR_get_error()));
+            return -1;
+        }
+    }
+
+    if (opts->crl_store_change || (!opts->crl_store && (opts->crl_file || opts->crl_dir))) {
+        /* set the revocation store with the correct paths for the callback */
+        X509_STORE_free(opts->crl_store);
+
+        opts->crl_store = X509_STORE_new();
+        if (!opts->crl_store) {
+            ERR("Unable to create a certificate store (%s).", ERR_reason_error_string(ERR_get_error()));
+            return -1;
+        }
+        opts->crl_store->cache = 0;
+
+        if (opts->crl_file) {
+            if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_file()))) {
+                ERR("Failed to add lookup method to CRL checking.");
+                return -1;
+            }
+            if (X509_LOOKUP_add_dir(lookup, opts->crl_file, X509_FILETYPE_PEM) != 1) {
+                ERR("Failed to add the revocation lookup file \"%s\".", opts->crl_file);
+                return -1;
+            }
+        }
+
+        if (opts->crl_dir) {
+            if (!(lookup = X509_STORE_add_lookup(opts->crl_store, X509_LOOKUP_hash_dir()))) {
+                ERR("Failed to add lookup method to CRL checking.");
+                return -1;
+            }
+            if (X509_LOOKUP_add_dir(lookup, opts->crl_dir, X509_FILETYPE_PEM) != 1) {
+                ERR("Failed to add the revocation lookup directory \"%s\".", opts->crl_dir);
+                return -1;
+            }
+        }
+    }
+
+    return 0;
 }
 
 API struct nc_session *
@@ -215,8 +412,7 @@
     struct nc_session *session = NULL;
     int sock, verify;
 
-    /* was init called? */
-    if (!tls_opts.tls_ctx) {
+    if (!tls_opts.cert_path || (!tls_opts.ca_file && !tls_opts.ca_dir)) {
         ERRARG;
         return NULL;
     }
@@ -230,6 +426,11 @@
         port = NC_PORT_TLS;
     }
 
+    /* create/update TLS structures */
+    if (nc_client_tls_opts_update(0)) {
+        return NULL;
+    }
+
     /* prepare session structure */
     session = calloc(1, sizeof *session);
     if (!session) {
@@ -265,6 +466,7 @@
     SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY);
 
     /* connect and perform the handshake */
+    tlsauth_ch = 0;
     if (SSL_connect(session->ti.tls) != 1) {
         ERR("Connecting over TLS failed (%s).", ERR_reason_error_string(ERR_get_error()));
         goto fail;
@@ -366,31 +568,19 @@
     return NULL;
 }
 
-API struct nc_session *
-nc_callhome_accept_tls(const char *host, uint16_t port, int timeout, struct ly_ctx *ctx)
+struct nc_session *
+nc_accept_callhome_sock_tls(int sock, const char *host, uint16_t port, struct ly_ctx *ctx)
 {
-    int listen_sock, sock, verify;
-    char *server_host;
+    int verify;
     SSL *tls;
     struct nc_session *session;
 
-    if (!port) {
-        port = NC_PORT_CH_TLS;
-    }
-
-    listen_sock = nc_sock_listen(host, port);
-    if (listen_sock < 0) {
+    if (nc_client_tls_opts_update(1)) {
+        close(sock);
         return NULL;
     }
 
-    sock = nc_sock_accept(listen_sock, timeout, &server_host, NULL);
-    close(listen_sock);
-
-    if (sock < 0) {
-        return NULL;
-    }
-
-    if (!(tls = SSL_new(tls_opts.tls_ctx))) {
+    if (!(tls = SSL_new(tls_ch_opts.tls_ctx))) {
         ERR("Failed to create new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
         close(sock);
         return NULL;
@@ -402,6 +592,7 @@
     SSL_set_mode(tls, SSL_MODE_AUTO_RETRY);
 
     /* connect and perform the handshake */
+    tlsauth_ch = 1;
     if (SSL_connect(tls) != 1) {
         ERR("Connecting over TLS failed (%s).", ERR_reason_error_string(ERR_get_error()));
         SSL_free(tls);
@@ -421,7 +612,7 @@
     session = nc_connect_libssl(tls, ctx);
     if (session) {
         /* store information into session and the dictionary */
-        session->host = lydict_insert_zc(session->ctx, server_host);
+        session->host = lydict_insert(session->ctx, host, 0);
         session->port = port;
         session->username = lydict_insert(session->ctx, "certificate-based", 0);
     }
diff --git a/src/session_p.h b/src/session_p.h
index 93e2844..1e89216 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -44,32 +44,32 @@
 /* number of all supported authentication methods */
 #   define NC_SSH_AUTH_COUNT 3
 
-struct nc_ssh_client_opts {
+struct nc_client_ssh_opts {
     /* SSH authentication method preferences */
     struct {
         NC_SSH_AUTH_TYPE type;
-        short int value;
+        int16_t value;
     } auth_pref[NC_SSH_AUTH_COUNT];
 
     /* SSH key pairs */
     struct {
         char *pubkey_path;
         char *privkey_path;
-        int privkey_crypt;
+        int8_t privkey_crypt;
     } *keys;
-    int key_count;
+    uint16_t key_count;
+
+    char *username;
 };
 
-struct nc_ssh_server_opts {
+struct nc_server_ssh_opts {
     ssh_bind sshbind;
-    pthread_mutex_t sshbind_lock;
 
     struct {
         const char *path;
         const char *username;
     } *authkeys;
     uint16_t authkey_count;
-    pthread_mutex_t authkey_lock;
 
     int auth_methods;
     uint16_t auth_attempts;
@@ -83,17 +83,23 @@
 #   include <openssl/bio.h>
 #   include <openssl/ssl.h>
 
-struct nc_tls_client_opts {
+struct nc_client_tls_opts {
+    char *cert_path;
+    char *key_path;
+    char *ca_file;
+    char *ca_dir;
+    int8_t tls_ctx_change;
     SSL_CTX *tls_ctx;
-    X509_STORE *tls_store;
+
+    char *crl_file;
+    char *crl_dir;
+    int8_t crl_store_change;
+    X509_STORE *crl_store;
 };
 
-struct nc_tls_server_opts {
+struct nc_server_tls_opts {
     SSL_CTX *tls_ctx;
-    pthread_mutex_t tls_ctx_lock;
-
     X509_STORE *crl_store;
-    pthread_mutex_t crl_lock;
 
     struct nc_ctn {
         uint32_t id;
@@ -102,11 +108,22 @@
         const char *name;
         struct nc_ctn *next;
     } *ctn;
-    pthread_mutex_t ctn_lock;
 };
 
 #endif /* ENABLE_TLS */
 
+struct nc_client_opts {
+    char *schema_searchpath;
+
+    struct nc_bind {
+        const char *address;
+        uint16_t port;
+        int sock;
+        NC_TRANSPORT_IMPL ti;
+    } *ch_binds;
+    uint16_t ch_bind_count;
+};
+
 struct nc_server_opts {
     struct ly_ctx *ctx;
     pthread_mutex_t ctx_lock;
@@ -118,14 +135,15 @@
     uint16_t hello_timeout;
     uint16_t idle_timeout;
 
-    struct nc_bind {
-        const char *address;
-        uint16_t port;
-        int sock;
-        NC_TRANSPORT_IMPL ti;
-    } *binds;
-    uint16_t bind_count;
-    pthread_mutex_t bind_lock;
+    struct nc_bind *binds;
+    struct nc_endpt {
+        const char *name;
+        void *ti_opts;
+        pthread_mutex_t endpt_lock;
+    } *endpts;
+    uint16_t endpt_count;
+    /* WRITE - working with binds/endpoints, READ - modifying a specific bind/endpoint, holding that endpt_lock too */
+    pthread_rwlock_t endpt_array_lock;
 
     uint32_t new_session_id;
     pthread_spinlock_t sid_lock;
@@ -220,6 +238,7 @@
     struct nc_msg_cont *notifs;    /**< queue for notifications received instead of RPC reply */
 
     /* server side only data */
+    void *ti_opts;
     time_t last_rpc;               /**< time the last RPC was received on this session */
 #ifdef ENABLE_SSH
     /* SSH session authenticated */
@@ -303,15 +322,58 @@
  * @param[in] binds Structure with the listening sockets.
  * @param[in] bind_count Number of \p binds.
  * @param[in] timeout Timeout for accepting.
- * @param[out] ti Type of transport of the accepted connection. Can be NULL.
  * @param[out] host Host of the remote peer. Can be NULL.
  * @param[out] port Port of the new connection. Can be NULL.
+ * @param[out] idx Index of the bind that was accepted. Can be NULL.
  * @return Accepted socket of the new connection, -1 on error.
  */
-int nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, NC_TRANSPORT_IMPL *ti, char **host, uint16_t *port);
+int nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, char **host, uint16_t *port, uint16_t *idx);
+
+/**
+ * @brief Add a new endpoint and start listening on it.
+ *
+ * @param[in] name Unique arbitrary name.
+ * @param[in] address IP address to bind to.
+ * @param[in] port Port to bind to.
+ * @param[in] ti Expected transport protocol of incoming connections.
+ * @return 0 on success, -1 on error.
+ */
+int nc_server_add_endpt_listen(const char *name, const char *address, uint16_t port, NC_TRANSPORT_IMPL ti);
+
+/**
+ * @brief Stop listening on and remove an endpoint.
+ *
+ * @param[in] address Name of the endpoint. NULL matches all the names.
+ * @param[in] ti Expected transport. 0 matches all.
+ * @return 0 on success, -1 on not finding any match.
+ */
+int nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti);
+
+/**
+ * @brief Find an endpoint.
+ *
+ * Caller must hold endpt_array_lock for reading.
+ *
+ * @param[in] name Endpoint name.
+ * @param[in] ti Endpoind transport.
+ * @return Endpoint, NULL on error.
+ */
+struct nc_endpt *nc_server_get_endpt(const char *name, NC_TRANSPORT_IMPL ti);
+
+/* TODO */
+int nc_client_ch_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti);
+
+/* TODO */
+int nc_client_ch_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti);
+
+/* TODO */
+int nc_connect_callhome(const char *host, uint16_t port, NC_TRANSPORT_IMPL ti, int timeout, struct nc_session **session);
 
 #ifdef ENABLE_SSH
 
+/* TODO */
+struct nc_session *nc_accept_callhome_sock_ssh(int sock, const char *host, uint16_t port, struct ly_ctx *ctx);
+
 /**
  * @brief Establish SSH transport on a socket.
  *
@@ -321,7 +383,7 @@
  * @param[in] ch Whether to accept a Call Home session or a standard one.
  * @return 1 on success, 0 on timeout, -1 on error.
  */
-int nc_accept_ssh_session(struct nc_session *session, int sock, int timeout, int ch);
+int nc_accept_ssh_session(struct nc_session *session, int sock, int timeout);
 
 /**
  * @brief Callback called when a new SSH message is received.
@@ -349,10 +411,16 @@
  */
 int nc_ssh_pollin(struct nc_session *session, int *timeout);
 
-#endif
+/* TODO */
+void nc_server_ssh_opts_clear(struct nc_server_ssh_opts *opts);
+
+#endif /* ENABLE_SSH */
 
 #ifdef ENABLE_TLS
 
+/* TODO */
+struct nc_session *nc_accept_callhome_sock_tls(int sock, const char *host, uint16_t port, struct ly_ctx *ctx);
+
 /**
  * @brief Establish TLS transport on a socket.
  *
@@ -362,9 +430,12 @@
  * @param[in] ch Whether to accept a Call Home session or a standard one.
  * @return 1 on success, 0 on timeout, -1 on error.
  */
-int nc_accept_tls_session(struct nc_session *session, int sock, int timeout, int ch);
+int nc_accept_tls_session(struct nc_session *session, int sock, int timeout);
 
-#endif
+/* TODO */
+void nc_server_tls_opts_clear(struct nc_server_tls_opts *opts);
+
+#endif /* ENABLE_TLS */
 
 /**
  * Functions
diff --git a/src/session_server.c b/src/session_server.c
index 74097e9..d1d643f 100644
--- a/src/session_server.c
+++ b/src/session_server.c
@@ -37,9 +37,24 @@
 #include "session_server.h"
 
 struct nc_server_opts server_opts = {
-    .ctx_lock = PTHREAD_MUTEX_INITIALIZER,
-    .bind_lock = PTHREAD_MUTEX_INITIALIZER
+    .endpt_array_lock = PTHREAD_RWLOCK_INITIALIZER
 };
+extern struct nc_server_ssh_opts ssh_ch_opts;
+extern struct nc_server_tls_opts tls_ch_opts;
+
+struct nc_endpt *
+nc_server_get_endpt(const char *name, NC_TRANSPORT_IMPL ti)
+{
+    uint16_t i;
+
+    for (i = 0; i < server_opts.endpt_count; ++i) {
+        if ((server_opts.binds[i].ti == ti) && !strcmp(server_opts.endpts[i].name, name)) {
+            return &server_opts.endpts[i];
+        }
+    }
+
+    return NULL;
+}
 
 API void
 nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
@@ -131,7 +146,7 @@
 }
 
 int
-nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, NC_TRANSPORT_IMPL *ti, char **host, uint16_t *port)
+nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, char **host, uint16_t *port, uint16_t *idx)
 {
     uint16_t i;
     struct pollfd *pfd;
@@ -178,8 +193,8 @@
         return -1;
     }
 
-    if (ti) {
-        *ti = binds[i].ti;
+    if (idx) {
+        *idx = i;
     }
 
     /* host was requested */
@@ -330,7 +345,7 @@
     pthread_spin_destroy(&server_opts.sid_lock);
 
 #if defined(ENABLE_SSH) || defined(ENABLE_TLS)
-    nc_server_del_bind(NULL, 0, 0);
+    nc_server_del_endpt(NULL, 0);
 #endif
 }
 
@@ -776,81 +791,160 @@
 
 #if defined(ENABLE_SSH) || defined(ENABLE_TLS)
 
-API int
-nc_server_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
+int
+nc_server_add_endpt_listen(const char *name, const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
 {
     int sock;
+    uint16_t i;
 
-    if (!address || !port || ((ti != NC_TI_LIBSSH) && (ti != NC_TI_OPENSSL))) {
+    if (!name || !address || !port) {
         ERRARG;
         return -1;
     }
 
+    /* READ LOCK */
+    pthread_rwlock_rdlock(&server_opts.endpt_array_lock);
+
+    /* check name uniqueness */
+    for (i = 0; i < server_opts.endpt_count; ++i) {
+        if (!strcmp(server_opts.endpts[i].name, name)) {
+            ERR("Endpoint \"%s\" already exists.", name);
+            return -1;
+        }
+    }
+
+    /* READ UNLOCK */
+    pthread_rwlock_unlock(&server_opts.endpt_array_lock);
+
     sock = nc_sock_listen(address, port);
     if (sock == -1) {
         return -1;
     }
 
-    /* LOCK */
-    pthread_mutex_lock(&server_opts.bind_lock);
+    /* WRITE LOCK */
+    pthread_rwlock_wrlock(&server_opts.endpt_array_lock);
 
-    ++server_opts.bind_count;
-    server_opts.binds = realloc(server_opts.binds, server_opts.bind_count * sizeof *server_opts.binds);
+    ++server_opts.endpt_count;
+    server_opts.binds = realloc(server_opts.binds, server_opts.endpt_count * sizeof *server_opts.binds);
+    server_opts.endpts = realloc(server_opts.endpts, server_opts.endpt_count * sizeof *server_opts.endpts);
 
     nc_ctx_lock(-1, NULL);
-    server_opts.binds[server_opts.bind_count - 1].address = lydict_insert(server_opts.ctx, address, 0);
+    server_opts.endpts[server_opts.endpt_count - 1].name = lydict_insert(server_opts.ctx, name, 0);
+    server_opts.binds[server_opts.endpt_count - 1].address = lydict_insert(server_opts.ctx, address, 0);
     nc_ctx_unlock();
-    server_opts.binds[server_opts.bind_count - 1].port = port;
-    server_opts.binds[server_opts.bind_count - 1].sock = sock;
-    server_opts.binds[server_opts.bind_count - 1].ti = ti;
+    server_opts.binds[server_opts.endpt_count - 1].port = port;
+    server_opts.binds[server_opts.endpt_count - 1].sock = sock;
+    server_opts.binds[server_opts.endpt_count - 1].ti = ti;
+    switch (ti) {
+#ifdef ENABLE_SSH
+    case NC_TI_LIBSSH:
+        server_opts.endpts[server_opts.endpt_count - 1].ti_opts = calloc(1, sizeof(struct nc_server_ssh_opts));
+        break;
+#endif
+#ifdef ENABLE_TLS
+    case NC_TI_OPENSSL:
+        server_opts.endpts[server_opts.endpt_count - 1].ti_opts = calloc(1, sizeof(struct nc_server_tls_opts));
+        break;
+#endif
+    default:
+        ERRINT;
+        server_opts.endpts[server_opts.endpt_count - 1].ti_opts = NULL;
+        break;
+    }
+    pthread_mutex_init(&server_opts.endpts[server_opts.endpt_count - 1].endpt_lock, NULL);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&server_opts.bind_lock);
+    /* WRITE UNLOCK */
+    pthread_rwlock_unlock(&server_opts.endpt_array_lock);
 
     return 0;
 }
 
-API int
-nc_server_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
+int
+nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti)
 {
     uint32_t i;
     int ret = -1;
 
-    /* LOCK */
-    pthread_mutex_lock(&server_opts.bind_lock);
+    /* WRITE LOCK */
+    pthread_rwlock_wrlock(&server_opts.endpt_array_lock);
 
-    if (!address && !port && !ti) {
+    if (!name && !ti) {
+        /* remove all */
         nc_ctx_lock(-1, NULL);
-        for (i = 0; i < server_opts.bind_count; ++i) {
-            close(server_opts.binds[i].sock);
+        for (i = 0; i < server_opts.endpt_count; ++i) {
+            lydict_remove(server_opts.ctx, server_opts.endpts[i].name);
             lydict_remove(server_opts.ctx, server_opts.binds[i].address);
+            close(server_opts.binds[i].sock);
+            pthread_mutex_destroy(&server_opts.endpts[i].endpt_lock);
+            switch (server_opts.binds[i].ti) {
+#ifdef ENABLE_SSH
+            case NC_TI_LIBSSH:
+                nc_server_ssh_opts_clear(server_opts.endpts[i].ti_opts);
+                break;
+#endif
+#ifdef ENABLE_TLS
+            case NC_TI_OPENSSL:
+                nc_server_tls_opts_clear(server_opts.endpts[i].ti_opts);
+                break;
+#endif
+            default:
+                ERRINT;
+                break;
+            }
+            free(server_opts.endpts[i].ti_opts);
 
             ret = 0;
         }
         nc_ctx_unlock();
-        free(server_opts.binds);
-        server_opts.binds = NULL;
-        server_opts.bind_count = 0;
+        free(server_opts.endpts);
+        server_opts.endpts = NULL;
+        server_opts.endpt_count = 0;
+
     } else {
-        for (i = 0; i < server_opts.bind_count; ++i) {
-            if ((!address || !strcmp(server_opts.binds[i].address, address))
-                    && (!port || (server_opts.binds[i].port == port))
-                    && (!ti || (server_opts.binds[i].ti == ti))) {
-                close(server_opts.binds[i].sock);
+        /* remove one name endpoint or all ti endpoints */
+        for (i = 0; i < server_opts.endpt_count; ++i) {
+            if ((server_opts.binds[i].ti == ti) &&
+                    (!name || !strcmp(server_opts.endpts[i].name, name))) {
+
                 nc_ctx_lock(-1, NULL);
+                lydict_remove(server_opts.ctx, server_opts.endpts[i].name);
                 lydict_remove(server_opts.ctx, server_opts.binds[i].address);
                 nc_ctx_unlock();
+                close(server_opts.binds[i].sock);
+                pthread_mutex_destroy(&server_opts.endpts[i].endpt_lock);
+                switch (server_opts.binds[i].ti) {
+#ifdef ENABLE_SSH
+                case NC_TI_LIBSSH:
+                    nc_server_ssh_opts_clear(server_opts.endpts[i].ti_opts);
+                    break;
+#endif
+#ifdef ENABLE_TLS
+                case NC_TI_OPENSSL:
+                    nc_server_tls_opts_clear(server_opts.endpts[i].ti_opts);
+                    break;
+#endif
+                default:
+                    ERRINT;
+                    break;
+                }
+                free(server_opts.endpts[i].ti_opts);
 
-                --server_opts.bind_count;
-                memcpy(&server_opts.binds[i], &server_opts.binds[server_opts.bind_count], sizeof *server_opts.binds);
+                --server_opts.endpt_count;
+                memcpy(&server_opts.binds[i], &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
+                memcpy(&server_opts.endpts[i], &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
 
                 ret = 0;
+
+                if (name) {
+                    /* one name endpoint removed, they are unique, we're done */
+                    break;
+                }
             }
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&server_opts.bind_lock);
+    /* WRITE UNLOCK */
+    pthread_rwlock_unlock(&server_opts.endpt_array_lock);
 
     return ret;
 }
@@ -858,25 +952,23 @@
 API int
 nc_accept(int timeout, struct nc_session **session)
 {
-    NC_TRANSPORT_IMPL ti;
     int sock, ret;
     char *host = NULL;
-    uint16_t port;
+    uint16_t port, idx;
 
-    if (!server_opts.ctx || !server_opts.binds || !session) {
+    if (!server_opts.ctx || !server_opts.endpt_count || !session) {
         ERRARG;
         return -1;
     }
 
-    /* LOCK */
-    pthread_mutex_lock(&server_opts.bind_lock);
+    /* WRITE LOCK */
+    pthread_rwlock_wrlock(&server_opts.endpt_array_lock);
 
-    ret = nc_sock_accept_binds(server_opts.binds, server_opts.bind_count, timeout, &ti, &host, &port);
-
-    /* UNLOCK */
-    pthread_mutex_unlock(&server_opts.bind_lock);
+    ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, timeout, &host, &port, &idx);
 
     if (ret < 0) {
+        /* WRITE UNLOCK */
+        pthread_rwlock_unlock(&server_opts.endpt_array_lock);
         return ret;
     }
     sock = ret;
@@ -886,7 +978,8 @@
         ERRMEM;
         close(sock);
         free(host);
-        return -1;
+        ret = -1;
+        goto fail;
     }
     (*session)->status = NC_STATUS_STARTING;
     (*session)->side = NC_SERVER;
@@ -907,14 +1000,16 @@
     }
     pthread_mutex_init((*session)->ti_lock, NULL);
 
+    (*session)->ti_opts = server_opts.endpts[idx].ti_opts;
+
     /* sock gets assigned to session or closed */
-    if (ti == NC_TI_LIBSSH) {
-        ret = nc_accept_ssh_session(*session, sock, timeout, 0);
+    if (server_opts.binds[idx].ti == NC_TI_LIBSSH) {
+        ret = nc_accept_ssh_session(*session, sock, timeout);
         if (ret < 1) {
             goto fail;
         }
-    } else if (ti == NC_TI_OPENSSL) {
-        ret = nc_accept_tls_session(*session, sock, timeout, 0);
+    } else if (server_opts.binds[idx].ti == NC_TI_OPENSSL) {
+        ret = nc_accept_tls_session(*session, sock, timeout);
         if (ret < 1) {
             goto fail;
         }
@@ -925,6 +1020,9 @@
         goto fail;
     }
 
+    /* WRITE UNLOCK */
+    pthread_rwlock_unlock(&server_opts.endpt_array_lock);
+
     /* assign new SID atomically */
     /* LOCK */
     pthread_spin_lock(&server_opts.sid_lock);
@@ -934,20 +1032,24 @@
 
     /* NETCONF handshake */
     if (nc_handshake(*session)) {
-        ret = -1;
-        goto fail;
+        nc_session_free(*session);
+        *session = NULL;
+        return -1;
     }
     (*session)->status = NC_STATUS_RUNNING;
 
     return 1;
 
 fail:
+    /* WRITE UNLOCK */
+    pthread_rwlock_unlock(&server_opts.endpt_array_lock);
+
     nc_session_free(*session);
     *session = NULL;
     return ret;
 }
 
-API int
+int
 nc_connect_callhome(const char *host, uint16_t port, NC_TRANSPORT_IMPL ti, int timeout, struct nc_session **session)
 {
     int sock, ret;
@@ -989,12 +1091,14 @@
 
     /* sock gets assigned to session or closed */
     if (ti == NC_TI_LIBSSH) {
-        ret = nc_accept_ssh_session(*session, sock, timeout, 1);
+        (*session)->ti_opts = &ssh_ch_opts;
+        ret = nc_accept_ssh_session(*session, sock, timeout);
         if (ret < 1) {
             goto fail;
         }
     } else if (ti == NC_TI_OPENSSL) {
-        ret = nc_accept_tls_session(*session, sock, timeout, 1);
+        (*session)->ti_opts = &tls_ch_opts;
+        ret = nc_accept_tls_session(*session, sock, timeout);
         if (ret < 1) {
             goto fail;
         }
diff --git a/src/session_server.h b/src/session_server.h
index ec38347..14009c2 100644
--- a/src/session_server.h
+++ b/src/session_server.h
@@ -215,27 +215,7 @@
 #if defined(ENABLE_SSH) || defined(ENABLE_TLS)
 
 /**
- * @brief Add a new server bind and start listening on it.
- *
- * @param[in] address IP address to bind to.
- * @param[in] port Port to bind to.
- * @param[in] ti Expected transport protocol of incoming connections.
- * @return 0 on success, -1 on error.
- */
-int nc_server_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti);
-
-/**
- * @brief Stop listening on and remove a server bind.
- *
- * @param[in] address IP address the bind was bound to. NULL matches all the addresses.
- * @param[in] port Port the bind was bound to. NULL matches all the ports.
- * @param[in] ti Expected transport. 0 matches all.
- * @return 0 on success, -1 on not finding any match.
- */
-int nc_server_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti);
-
-/**
- * @brief Accept new sessions on all the listening binds.
+ * @brief Accept new sessions on all the listening endpoints.
  *
  * @param[in] timeout Timeout for receiving a new connection in milliseconds, 0 for
  * non-blocking call, -1 for infinite waiting.
@@ -244,19 +224,6 @@
  */
 int nc_accept(int timeout, struct nc_session **session);
 
-/**
- * @brief Establish a Call Home connection with a listening NETCONF client.
- *
- * @param[in] host Host the client is listening on.
- * @param[in] port Port the client is listening on.
- * @param[in] ti Transport protocol to use.
- * @param[in] timeout Timeout for transport-related operations, 0 for non-blocking
- * call, -1 for infinite waiting.
- * @param[out] session New Call Home session.
- * @return 1 on success, 0 on timeout, -1 on error.
- */
-int nc_connect_callhome(const char *host, uint16_t port, NC_TRANSPORT_IMPL ti, int timeout, struct nc_session **session);
-
 #endif /* ENABLE_SSH || ENABLE_TLS */
 
 #ifdef ENABLE_SSH
@@ -272,78 +239,114 @@
 int nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session);
 
 /**
- * @brief Set SSH host keys the server will identify itself with. Each of RSA, DSA, and
+ * @brief Add a new SSH endpoint and start listening on it.
+ *
+ * @param[in] name Arbitrary unique endpoint name. There can be a TLS endpoint with
+ * the same name.
+ * @param[in] address IP address to listen on.
+ * @param[in] port Port to listen on.
+ * @return 0 on success, -1 on error.
+ */
+int nc_server_ssh_add_endpt_listen(const char *name, const char *address, uint16_t port);
+
+/**
+ * @brief Stop listening on and remove an SSH endpoint.
+ *
+ * @param[in] name Endpoint name. NULL matches all (SSH) endpoints.
+ * @return 0 on success, -1 on not finding any match.
+ */
+int nc_server_ssh_del_endpt(const char *name);
+
+/**
+ * @brief Set endpoint SSH host keys the server will identify itself with. Each of RSA, DSA, and
  * ECDSA keys can be set. If the particular type was already set, it is replaced.
  *
  * @param[in] privkey_path Path to a private key.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_set_hostkey(const char *privkey_path);
+int nc_server_ssh_endpt_set_hostkey(const char *endpt_name, const char *privkey_path);
 
 /**
- * @brief Set SSH banner the server will send to every client.
+ * @brief Set endpoint SSH banner the server will send to every client.
  *
  * @param[in] banner SSH banner.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_set_banner(const char *banner);
+int nc_server_ssh_endpt_set_banner(const char *endpt_name, const char *banner);
 
 /**
- * @brief Set accepted SSH authentication methods. All (publickey, password, interactive)
+ * @brief Set endpoint accepted SSH authentication methods. All (publickey, password, interactive)
  * are supported by default.
  *
  * @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_set_auth_methods(int auth_methods);
+int nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods);
 
 /**
- * @brief Set SSH authentication attempts of every client. 3 by default.
+ * @brief Set endpoint SSH authentication attempts of every client. 3 by default.
  *
  * @param[in] auth_attempts Failed authentication attempts before a client is dropped.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_set_auth_attempts(uint16_t auth_attempts);
+int nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts);
 
 /**
- * @brief Set SSH authentication timeout. 10 seconds by default.
+ * @brief Set endpoint SSH authentication timeout. 10 seconds by default.
  *
  * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_set_auth_timeout(uint16_t auth_timeout);
+int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout);
 
 /**
- * @brief Add an authorized client SSH public key. This public key can be used for
+ * @brief Add an endpoint authorized client SSH public key. This public key can be used for
  * publickey authentication afterwards.
  *
  * @param[in] pubkey_path Path to the public key.
  * @param[in] username Username that the client with the public key must use.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_add_authkey(const char *pubkey_path, const char *username);
+int nc_server_ssh_endpt_add_authkey(const char *endpt_name, const char *pubkey_path, const char *username);
 
 /**
- * @brief Remove an authorized client SSH public key.
+ * @brief Remove an endpoint authorized client SSH public key.
  *
  * @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys.
  * @param[in] username Username for an authorized public key. NULL matches all the usernames.
  * @return 0 on success, -1 on not finding any match.
  */
-int nc_ssh_server_del_authkey(const char *pubkey_path, const char *username);
+int nc_server_ssh_endpt_del_authkey(const char *endpt_name, const char *pubkey_path, const char *username);
+
+/**
+ * @brief Free all the various SSH server options (excluding Call Home).
+ */
+void nc_server_ssh_opts_free(void);
 
 /*
  * Call Home
  */
 
 /**
+ * @brief Establish an SSH Call Home connection with a listening NETCONF client.
+ *
+ * @param[in] host Host the client is listening on.
+ * @param[in] port Port the client is listening on.
+ * @param[in] timeout Timeout for transport-related operations, 0 for non-blocking
+ * call, -1 for infinite waiting.
+ * @param[out] session New Call Home session.
+ * @return 1 on success, 0 on timeout, -1 on error.
+ */
+int nc_connect_callhome_ssh(const char *host, uint16_t port, int timeout, struct nc_session **session);
+
+/**
  * @brief Set Call Home SSH host keys the server will identify itself with. Each of RSA, DSA, and
  * ECDSA keys can be set. If the particular type was already set, it is replaced.
  *
  * @param[in] privkey_path Path to a private key.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_set_hostkey(const char *privkey_path);
+int nc_server_ssh_ch_set_hostkey(const char *privkey_path);
 
 /**
  * @brief Set Call Home SSH banner the server will send to every client.
@@ -351,7 +354,7 @@
  * @param[in] banner SSH banner.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_set_banner(const char *banner);
+int nc_server_ssh_ch_set_banner(const char *banner);
 
 /**
  * @brief Set accepted Call Home SSH authentication methods. All (publickey, password, interactive)
@@ -360,7 +363,7 @@
  * @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_set_auth_methods(int auth_methods);
+int nc_server_ssh_ch_set_auth_methods(int auth_methods);
 
 /**
  * @brief Set Call Home SSH authentication attempts of every client. 3 by default.
@@ -368,7 +371,7 @@
  * @param[in] auth_attempts Failed authentication attempts before a client is dropped.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_set_auth_attempts(uint16_t auth_attempts);
+int nc_server_ssh_ch_set_auth_attempts(uint16_t auth_attempts);
 
 /**
  * @brief Set Call Home SSH authentication timeout. 10 seconds by default.
@@ -376,7 +379,7 @@
  * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_set_auth_timeout(uint16_t auth_timeout);
+int nc_server_ssh_ch_set_auth_timeout(uint16_t auth_timeout);
 
 /**
  * @brief Add an authorized Call Home client SSH public key. This public key can be used for
@@ -386,7 +389,7 @@
  * @param[in] username Username that the client with the public key must use.
  * @return 0 on success, -1 on error.
  */
-int nc_ssh_server_ch_add_authkey(const char *pubkey_path, const char *username);
+int nc_server_ssh_ch_add_authkey(const char *pubkey_path, const char *username);
 
 /**
  * @brief Remove an authorized Call Home client SSH public key.
@@ -395,25 +398,42 @@
  * @param[in] username Username for an authorized public key. NULL matches all the usernames.
  * @return 0 on success, -1 on not finding any match.
  */
-int nc_ssh_server_ch_del_authkey(const char *pubkey_path, const char *username);
+int nc_server_ssh_ch_del_authkey(const char *pubkey_path, const char *username);
 
-/**
- * @brief Free all the various SSH server options (including Call Home ones).
- */
-void nc_ssh_server_free_opts(void);
+/* TODO */
+void nc_server_ssh_ch_opts_clear(void);
 
 #endif /* ENABLE_SSH */
 
 #ifdef ENABLE_TLS
 
 /**
+ * @brief Add a new TLS endpoint and start listening on it.
+ *
+ * @param[in] name Arbitrary unique endpoint name. There can be an SSH endpoint with
+ * the same name.
+ * @param[in] address IP address to listen on.
+ * @param[in] port Port to listen on.
+ * @return 0 on success, -1 on error.
+ */
+int nc_server_tls_add_endpt_listen(const char *name, const char *address, uint16_t port);
+
+/**
+ * @brief Stop listening on and remove a TLS endpoint.
+ *
+ * @param[in] name Endpoint name. NULL matches all (TLS) endpoints.
+ * @return 0 on success, -1 on not finding any match.
+ */
+int nc_server_tls_del_endpt(const char *name);
+
+/**
  * @brief Set server TLS certificate. Alternative to nc_tls_server_set_cert_path().
  * There can only be one certificate for each key type, it is replaced if already set.
  *
  * @param[in] cert Base64-encoded certificate in ASN.1 DER encoding.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_cert(const char *cert);
+int nc_server_tls_endpt_set_cert(const char *endpt_name, const char *cert);
 
 /**
  * @brief Set server TLS certificate. Alternative to nc_tls_server_set_cert().
@@ -422,7 +442,7 @@
  * @param[in] cert_path Path to a certificate file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_cert_path(const char *cert_path);
+int nc_server_tls_endpt_set_cert_path(const char *endpt_name, const char *cert_path);
 
 /**
  * @brief Set server TLS private key matching the certificate.
@@ -433,7 +453,7 @@
  * @param[in] is_rsa Whether \p privkey are the data of an RSA (1) or DSA (0) key.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_key(const char *privkey, int is_rsa);
+int nc_server_tls_endpt_set_key(const char *endpt_name, const char *privkey, int is_rsa);
 
 /**
  * @brief Set server TLS private key matching the certificate.
@@ -443,7 +463,7 @@
  * @param[in] privkey_path Path to a private key file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_key_path(const char *privkey_path);
+int nc_server_tls_endpt_set_key_path(const char *endpt_name, const char *privkey_path);
 
 /**
  * @brief Add a trusted certificate. Can be both a CA or a client one.
@@ -451,7 +471,7 @@
  * @param[in] cert Base64-enocded certificate in ASN.1DER encoding.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_add_trusted_cert(const char *cert);
+int nc_server_tls_endpt_add_trusted_cert(const char *endpt_name, const char *cert);
 
 /**
  * @brief Add a trusted certificate. Can be both a CA or a client one.
@@ -459,7 +479,7 @@
  * @param[in] cert_path Path to a trusted certificate file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_add_trusted_cert_path(const char *cert_path);
+int nc_server_tls_endpt_add_trusted_cert_path(const char *endpt_name, const char *cert_path);
 
 /**
  * @brief Set trusted Certificate Authority certificate locations. There can only be
@@ -471,13 +491,13 @@
  * (c_rehash utility can be used to create hashes) with PEM files. Can be NULL.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path);
+int nc_server_tls_endpt_set_trusted_cacert_locations(const char *endpt_name, const char *cacert_file_path, const char *cacert_dir_path);
 
 /**
  * @brief Destroy and clean all the set certificates and private keys. CRLs and
  * CTN entries are not affected.
  */
-void nc_tls_server_destroy_certs(void);
+void nc_server_tls_endpt_destroy_certs(const char *endpt_name);
 
 /**
  * @brief Set Certificate Revocation List locations. There can only be one file
@@ -488,13 +508,13 @@
  * can be used to create hashes) with PEM files. Can be NULL.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_set_crl_locations(const char *crl_file_path, const char *crl_dir_path);
+int nc_server_tls_endpt_set_crl_locations(const char *endpt_name, const char *crl_file_path, const char *crl_dir_path);
 
 /**
  * @brief Destroy and clean CRLs. Certificates, priavte keys, and CTN entries are
  * not affected.
  */
-void nc_tls_server_destroy_crls(void);
+void nc_server_tls_endpt_destroy_crls(const char *endpt_name);
 
 /**
  * @brief Add a Cert-to-name entry.
@@ -505,7 +525,7 @@
  * @param[in] name Specific username if \p map_type == NC_TLS_CTN_SPECIFED. Must be NULL otherwise.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
+int nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
 
 /**
  * @brief Remove a Cert-to-name entry.
@@ -516,20 +536,32 @@
  * @param[in] name Specific username for the entry. NULL matches all the usernames.
  * @return 0 on success, -1 on not finding any match.
  */
-int nc_tls_server_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
+int nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
 
 /*
  * Call Home
  */
 
 /**
+ * @brief Establish a TLS Call Home connection with a listening NETCONF client.
+ *
+ * @param[in] host Host the client is listening on.
+ * @param[in] port Port the client is listening on.
+ * @param[in] timeout Timeout for transport-related operations, 0 for non-blocking
+ * call, -1 for infinite waiting.
+ * @param[out] session New Call Home session.
+ * @return 1 on success, 0 on timeout, -1 on error.
+ */
+int nc_connect_callhome_tls(const char *host, uint16_t port, int timeout, struct nc_session **session);
+
+/**
  * @brief Set server Call Home TLS certificate. Alternative to nc_tls_server_set_cert_path().
  * There can only be one certificate for each key type, it is replaced if already set.
  *
  * @param[in] cert Base64-encoded certificate in ASN.1 DER encoding.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_cert(const char *cert);
+int nc_server_tls_ch_set_cert(const char *cert);
 
 /**
  * @brief Set server Call Home TLS certificate. Alternative to nc_tls_server_set_cert().
@@ -538,7 +570,7 @@
  * @param[in] cert_path Path to a certificate file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_cert_path(const char *cert_path);
+int nc_server_tls_ch_set_cert_path(const char *cert_path);
 
 /**
  * @brief Set server Call Home TLS private key matching the certificate.
@@ -549,7 +581,7 @@
  * @param[in] is_rsa Whether \p privkey are the data of an RSA (1) or DSA (0) key.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_key(const char *privkey, int is_rsa);
+int nc_server_tls_ch_set_key(const char *privkey, int is_rsa);
 
 /**
  * @brief Set server Call Home TLS private key matching the certificate.
@@ -559,7 +591,7 @@
  * @param[in] privkey_path Path to a private key file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_key_path(const char *privkey_path);
+int nc_server_tls_ch_set_key_path(const char *privkey_path);
 
 /**
  * @brief Add a Call Home trusted certificate. Can be both a CA or a client one.
@@ -567,7 +599,7 @@
  * @param[in] cert Base64-enocded certificate in ASN.1DER encoding.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_add_trusted_cert(const char *cert);
+int nc_server_tls_ch_add_trusted_cert(const char *cert);
 
 /**
  * @brief Add a Call Home trusted certificate. Can be both a CA or a client one.
@@ -575,7 +607,7 @@
  * @param[in] cert_path Path to a trusted certificate file in PEM format.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_add_trusted_cert_path(const char *cert_path);
+int nc_server_tls_ch_add_trusted_cert_path(const char *cert_path);
 
 /**
  * @brief Set trusted Call Home Certificate Authority certificate locations. There
@@ -587,13 +619,13 @@
  * (c_rehash utility can be used to create hashes) with PEM files. Can be NULL.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path);
+int nc_server_tls_ch_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path);
 
 /**
  * @brief Destroy and clean all the set Call Home certificates and private keys.
  * CRLs and CTN entries are not affected.
  */
-void nc_tls_server_ch_destroy_certs(void);
+void nc_server_tls_ch_destroy_certs(void);
 
 /**
  * @brief Set Call Home Certificate Revocation List locations. There can only be
@@ -604,13 +636,13 @@
  * can be used to create hashes) with PEM files. Can be NULL.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_set_crl_locations(const char *crl_file_path, const char *crl_dir_path);
+int nc_server_tls_ch_set_crl_locations(const char *crl_file_path, const char *crl_dir_path);
 
 /**
  * @brief Destroy and clean Call Home CRLs. Call Home certificates, private keys,
  * and CTN entries are not affected.
  */
-void nc_tls_server_ch_destroy_crls(void);
+void nc_server_tls_ch_destroy_crls(void);
 
 /**
  * @brief Add a Call Home Cert-to-name entry.
@@ -621,7 +653,7 @@
  * @param[in] name Specific username if \p map_type == NC_TLS_CTN_SPECIFED. Must be NULL otherwise.
  * @return 0 on success, -1 on error.
  */
-int nc_tls_server_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
+int nc_server_tls_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
 
 /**
  * @brief Remove a Call Home Cert-to-name entry.
@@ -632,12 +664,10 @@
  * @param[in] name Specific username for the entry. NULL matches all the usernames.
  * @return 0 on success, -1 on not finding any match.
  */
-int nc_tls_server_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
+int nc_server_tls_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name);
 
-/**
- * @brief Free all the various TLS server options (including Call Home ones).
- */
-void nc_tls_server_free_opts(void);
+/* TODO */
+void nc_server_tls_ch_opts_clear(void);
 
 #endif /* ENABLE_TLS */
 
diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c
index 900adc4..31838e3 100644
--- a/src/session_server_ssh.c
+++ b/src/session_server_ssh.c
@@ -33,39 +33,33 @@
 #include "libnetconf.h"
 #include "session_server.h"
 
+struct nc_server_ssh_opts ssh_ch_opts = {
+    .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE,
+    .auth_attempts = 3,
+    .auth_timeout = 10
+};
 extern struct nc_server_opts server_opts;
 
-struct nc_ssh_server_opts ssh_opts = {
-    .sshbind_lock = PTHREAD_MUTEX_INITIALIZER,
-    .authkey_lock = PTHREAD_MUTEX_INITIALIZER,
-    .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE,
-    .auth_attempts = 3,
-    .auth_timeout = 10
-};
+API int
+nc_server_ssh_add_endpt_listen(const char *name, const char *address, uint16_t port)
+{
+    return nc_server_add_endpt_listen(name, address, port, NC_TI_LIBSSH);
+}
 
-struct nc_ssh_server_opts ssh_ch_opts = {
-    .sshbind_lock = PTHREAD_MUTEX_INITIALIZER,
-    .authkey_lock = PTHREAD_MUTEX_INITIALIZER,
-    .auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD | NC_SSH_AUTH_INTERACTIVE,
-    .auth_attempts = 3,
-    .auth_timeout = 10
-};
+API int
+nc_server_ssh_del_endpt(const char *name)
+{
+    return nc_server_del_endpt(name, NC_TI_LIBSSH);
+}
 
 static int
-_nc_ssh_server_set_hostkey(const char *privkey_path, int ch)
+nc_server_ssh_set_hostkey(const char *privkey_path, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!privkey_path) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->sshbind_lock);
-
     if (!opts->sshbind) {
         opts->sshbind = ssh_bind_new();
         if (!opts->sshbind) {
@@ -83,43 +77,38 @@
         goto fail;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->sshbind_lock);
-    return 0;
-
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->sshbind_lock);
     return -1;
 }
 
 API int
-nc_ssh_server_set_hostkey(const char *privkey_path)
+nc_server_ssh_endpt_set_hostkey(const char *endpt_name, const char *privkey_path)
 {
-    return _nc_ssh_server_set_hostkey(privkey_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_set_hostkey(privkey_path, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_set_hostkey(const char *privkey_path)
+nc_server_ssh_ch_set_hostkey(const char *privkey_path)
 {
-    return _nc_ssh_server_set_hostkey(privkey_path, 1);
+    return nc_server_ssh_set_hostkey(privkey_path, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_set_banner(const char *banner, int ch)
+nc_server_ssh_set_banner(const char *banner, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!banner) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->sshbind_lock);
-
     if (!opts->sshbind) {
         opts->sshbind = ssh_bind_new();
         if (!opts->sshbind) {
@@ -130,128 +119,137 @@
 
     ssh_bind_options_set(opts->sshbind, SSH_BIND_OPTIONS_BANNER, banner);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->sshbind_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->sshbind_lock);
     return -1;
 }
 
 API int
-nc_ssh_server_set_banner(const char *banner)
+nc_server_ssh_endpt_set_banner(const char *endpt_name, const char *banner)
 {
-    return _nc_ssh_server_set_banner(banner, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_set_banner(banner, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_set_banner(const char *banner)
+nc_server_ssh_ch_set_banner(const char *banner)
 {
-    return _nc_ssh_server_set_banner(banner, 1);
+    return nc_server_ssh_set_banner(banner, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_set_auth_methods(int auth_methods, int ch)
+nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!(auth_methods & NC_SSH_AUTH_PUBLICKEY) && !(auth_methods & NC_SSH_AUTH_PASSWORD)
             && !(auth_methods & NC_SSH_AUTH_INTERACTIVE)) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
     opts->auth_methods = auth_methods;
     return 0;
 }
 
 API int
-nc_ssh_server_set_auth_methods(int auth_methods)
+nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods)
 {
-    return _nc_ssh_server_set_auth_methods(auth_methods, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_set_auth_methods(auth_methods, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_set_auth_methods(int auth_methods)
+nc_server_ssh_ch_set_auth_methods(int auth_methods)
 {
-    return _nc_ssh_server_set_auth_methods(auth_methods, 1);
+    return nc_server_ssh_set_auth_methods(auth_methods, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_set_auth_attempts(uint16_t auth_attempts, int ch)
+nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!auth_attempts) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
     opts->auth_attempts = auth_attempts;
     return 0;
 }
 
 API int
-nc_ssh_server_set_auth_attempts(uint16_t auth_attempts)
+nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts)
 {
-    return _nc_ssh_server_set_auth_attempts(auth_attempts, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_set_auth_attempts(auth_attempts, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_set_ch_auth_attempts(uint16_t auth_attempts)
+nc_server_ssh_set_ch_auth_attempts(uint16_t auth_attempts)
 {
-    return _nc_ssh_server_set_auth_attempts(auth_attempts, 1);
+    return nc_server_ssh_set_auth_attempts(auth_attempts, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_set_auth_timeout(uint16_t auth_timeout, int ch)
+nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!auth_timeout) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
     opts->auth_timeout = auth_timeout;
     return 0;
 }
 
 API int
-nc_ssh_server_set_auth_timeout(uint16_t auth_timeout)
+nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout)
 {
-    return _nc_ssh_server_set_auth_timeout(auth_timeout, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_set_auth_timeout(auth_timeout, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_set_auth_timeout(uint16_t auth_timeout)
+nc_server_ssh_ch_set_auth_timeout(uint16_t auth_timeout)
 {
-    return _nc_ssh_server_set_auth_timeout(auth_timeout, 1);
+    return nc_server_ssh_set_auth_timeout(auth_timeout, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_add_authkey(const char *pubkey_path, const char *username, int ch)
+nc_server_ssh_add_authkey(const char *pubkey_path, const char *username, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
-
     if (!pubkey_path || !username) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->authkey_lock);
-
     ++opts->authkey_count;
     opts->authkeys = realloc(opts->authkeys, opts->authkey_count * sizeof *opts->authkeys);
 
@@ -260,36 +258,35 @@
     opts->authkeys[opts->authkey_count - 1].username = lydict_insert(server_opts.ctx, username, 0);
     nc_ctx_unlock();
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->authkey_lock);
-
     return 0;
 }
 
 API int
-nc_ssh_server_add_authkey(const char *pubkey_path, const char *username)
+nc_server_ssh_endpt_add_authkey(const char *endpt_name, const char *pubkey_path, const char *username)
 {
-    return _nc_ssh_server_add_authkey(pubkey_path, username, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_add_authkey(pubkey_path, username, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_add_authkey(const char *pubkey_path, const char *username)
+nc_server_ssh_ch_add_authkey(const char *pubkey_path, const char *username)
 {
-    return _nc_ssh_server_add_authkey(pubkey_path, username, 1);
+    return nc_server_ssh_add_authkey(pubkey_path, username, &ssh_ch_opts);
 }
 
 static int
-_nc_ssh_server_del_authkey(const char *pubkey_path, const char *username, int ch)
+nc_server_ssh_del_authkey(const char *pubkey_path, const char *username, struct nc_server_ssh_opts *opts)
 {
-    struct nc_ssh_server_opts *opts;
     uint32_t i;
     int ret = -1;
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->authkey_lock);
-
     if (!pubkey_path && !username) {
         nc_ctx_lock(-1, NULL);
         for (i = 0; i < opts->authkey_count; ++i) {
@@ -319,54 +316,44 @@
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->authkey_lock);
-
     return ret;
 }
 
 API int
-nc_ssh_server_del_authkey(const char *pubkey_path, const char *username)
+nc_server_ssh_endpt_del_authkey(const char *endpt_name, const char *pubkey_path, const char *username)
 {
-    return _nc_ssh_server_del_authkey(pubkey_path, username, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_LIBSSH);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_ssh_del_authkey(pubkey_path, username, endpt->ti_opts);
 }
 
 API int
-nc_ssh_server_ch_del_authkey(const char *pubkey_path, const char *username)
+nc_server_ssh_ch_del_authkey(const char *pubkey_path, const char *username)
 {
-    return _nc_ssh_server_del_authkey(pubkey_path, username, 1);
+    return nc_server_ssh_del_authkey(pubkey_path, username, &ssh_ch_opts);
+}
+
+void
+nc_server_ssh_opts_clear(struct nc_server_ssh_opts *opts)
+{
+    if (opts->sshbind) {
+        ssh_bind_free(opts->sshbind);
+        opts->sshbind = NULL;
+    }
+
+    nc_server_ssh_del_authkey(NULL, NULL, opts);
 }
 
 API void
-nc_ssh_server_free_opts(void)
+nc_server_ssh_ch_opts_clear(void)
 {
-    /* LOCK */
-    pthread_mutex_lock(&ssh_opts.sshbind_lock);
-
-    if (ssh_opts.sshbind) {
-        ssh_bind_free(ssh_opts.sshbind);
-        ssh_opts.sshbind = NULL;
-    }
-
-    /* UNLOCK */
-    pthread_mutex_unlock(&ssh_opts.sshbind_lock);
-
-    nc_ssh_server_del_authkey(NULL, NULL);
-
-    /* Call Home */
-
-    /* LOCK */
-    pthread_mutex_lock(&ssh_ch_opts.sshbind_lock);
-
-    if (ssh_ch_opts.sshbind) {
-        ssh_bind_free(ssh_ch_opts.sshbind);
-        ssh_ch_opts.sshbind = NULL;
-    }
-
-    /* UNLOCK */
-    pthread_mutex_unlock(&ssh_ch_opts.sshbind_lock);
-
-    nc_ssh_server_ch_del_authkey(NULL, NULL);
+    nc_server_ssh_opts_clear(&ssh_ch_opts);
 }
 
 static char *
@@ -487,21 +474,18 @@
 }
 
 static const char *
-auth_pubkey_compare_key(ssh_key key)
+auth_pubkey_compare_key(struct nc_server_ssh_opts *opts, ssh_key key)
 {
     uint32_t i;
     ssh_key pub_key;
     const char *username = NULL;
 
-    /* LOCK */
-    pthread_mutex_lock(&ssh_opts.authkey_lock);
-
-    for (i = 0; i < ssh_opts.authkey_count; ++i) {
-        if (ssh_pki_import_pubkey_file(ssh_opts.authkeys[i].path, &pub_key) != SSH_OK) {
-            if (eaccess(ssh_opts.authkeys[i].path, R_OK)) {
-                WRN("Failed to import the public key \"%s\" (%s).", ssh_opts.authkeys[i].path, strerror(errno));
+    for (i = 0; i < opts->authkey_count; ++i) {
+        if (ssh_pki_import_pubkey_file(opts->authkeys[i].path, &pub_key) != SSH_OK) {
+            if (eaccess(opts->authkeys[i].path, R_OK)) {
+                WRN("Failed to import the public key \"%s\" (%s).", opts->authkeys[i].path, strerror(errno));
             } else {
-                WRN("Failed to import the public key \"%s\" (%s).", __func__, ssh_opts.authkeys[i].path, ssh_get_error(pub_key));
+                WRN("Failed to import the public key \"%s\" (%s).", __func__, opts->authkeys[i].path, ssh_get_error(pub_key));
             }
             continue;
         }
@@ -514,13 +498,10 @@
         ssh_key_free(pub_key);
     }
 
-    if (i < ssh_opts.authkey_count) {
-        username = ssh_opts.authkeys[i].username;
+    if (i < opts->authkey_count) {
+        username = opts->authkeys[i].username;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&ssh_opts.authkey_lock);
-
     return username;
 }
 
@@ -538,7 +519,7 @@
         return;
 
     } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) {
-        if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) {
+        if ((username = auth_pubkey_compare_key(session->ti_opts, ssh_message_auth_pubkey(msg))) == NULL) {
             VRB("User \"%s\" tried to use an unknown (unauthorized) public key.", session->username);
 
         } else if (strcmp(session->username, username)) {
@@ -769,7 +750,7 @@
             return 0;
         }
 
-        if (session->ssh_auth_attempts >= ssh_opts.auth_attempts) {
+        if (session->ssh_auth_attempts >= ((struct nc_server_ssh_opts *)session->ti_opts)->auth_attempts) {
             /* too many failed attempts */
             ssh_message_reply_default(msg);
             return 0;
@@ -982,13 +963,19 @@
     return 1;
 }
 
-int
-nc_accept_ssh_session(struct nc_session *session, int sock, int timeout, int ch)
+API int
+nc_connect_callhome_ssh(const char *host, uint16_t port, int timeout, struct nc_session **session)
 {
-    struct nc_ssh_server_opts *opts;
+    return nc_connect_callhome(host, port, NC_TI_LIBSSH, timeout, session);
+}
+
+int
+nc_accept_ssh_session(struct nc_session *session, int sock, int timeout)
+{
+    struct nc_server_ssh_opts *opts;
     int libssh_auth_methods = 0, elapsed = 0, ret;
 
-    opts = (ch ? &ssh_ch_opts : &ssh_opts);
+    opts = session->ti_opts;
 
     /* other transport-specific data */
     session->ti_type = NC_TI_LIBSSH;
@@ -1014,23 +1001,12 @@
     /* remember that this session was just set as nc_sshcb_msg() parameter */
     session->flags |= NC_SESSION_SSH_MSG_CB;
 
-    /* LOCK */
-    ret = nc_timedlock(&opts->sshbind_lock, timeout, &elapsed);
-    if (ret < 1) {
-        return ret;
-    }
-
     if (ssh_bind_accept_fd(opts->sshbind, session->ti.libssh.session, sock) == SSH_ERROR) {
-        ERR("SSH failed to accept a new connection (%s).", ssh_get_error(ssh_opts.sshbind));
+        ERR("SSH failed to accept a new connection (%s).", ssh_get_error(opts->sshbind));
         close(sock);
-        /* UNLOCK */
-        pthread_mutex_unlock(&opts->sshbind_lock);
         return -1;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->sshbind_lock);
-
     if (ssh_handle_key_exchange(session->ti.libssh.session) != SSH_OK) {
         ERR("SSH key exchange error (%s).", ssh_get_error(session->ti.libssh.session));
         return -1;
diff --git a/src/session_server_tls.c b/src/session_server_tls.c
index 1dd5cf9..d40fcaa 100644
--- a/src/session_server_tls.c
+++ b/src/session_server_tls.c
@@ -33,20 +33,9 @@
 #include "libnetconf.h"
 #include "session_server.h"
 
+struct nc_server_tls_opts tls_ch_opts;
 extern struct nc_server_opts server_opts;
 
-struct nc_tls_server_opts tls_opts = {
-    .tls_ctx_lock = PTHREAD_MUTEX_INITIALIZER,
-    .crl_lock = PTHREAD_MUTEX_INITIALIZER,
-    .ctn_lock = PTHREAD_MUTEX_INITIALIZER
-};
-
-struct nc_tls_server_opts tls_ch_opts = {
-    .tls_ctx_lock = PTHREAD_MUTEX_INITIALIZER,
-    .crl_lock = PTHREAD_MUTEX_INITIALIZER,
-    .ctn_lock = PTHREAD_MUTEX_INITIALIZER
-};
-
 static pthread_key_t verify_key;
 static pthread_once_t verify_once = PTHREAD_ONCE_INIT;
 
@@ -466,7 +455,7 @@
     STACK_OF(X509) *cert_stack;
     EVP_PKEY *pubkey;
     struct nc_session* session;
-    struct nc_tls_server_opts *opts;
+    struct nc_server_tls_opts *opts;
     long serial;
     int i, n, rc, depth;
     char *cp;
@@ -481,7 +470,7 @@
         return 0;
     }
 
-    opts = (session->flags & NC_SESSION_CALLHOME ? &tls_ch_opts : &tls_opts);
+    opts = session->ti_opts;
 
     /* get the last certificate, that is the peer (client) certificate */
     if (!session->tls_cert) {
@@ -535,9 +524,6 @@
     VRB("Cert verify: issuer:  %s.", cp);
     OPENSSL_free(cp);
 
-    /* LOCK */
-    pthread_mutex_lock(&opts->crl_lock);
-
     /* check for revocation if set */
     if (opts->crl_store) {
         /* try to retrieve a CRL corresponding to the _subject_ of
@@ -570,8 +556,6 @@
                 if (pubkey) {
                     EVP_PKEY_free(pubkey);
                 }
-                /* UNLOCK */
-                pthread_mutex_unlock(&opts->crl_lock);
                 return 0;
             }
             if (pubkey) {
@@ -583,16 +567,12 @@
                 ERR("Cert verify CRL: invalid nextUpdate field.");
                 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
                 X509_OBJECT_free_contents(&obj);
-                /* UNLOCK */
-                pthread_mutex_unlock(&opts->crl_lock);
                 return 0;
             }
             if (X509_cmp_current_time(next_update) < 0) {
                 ERR("Cert verify CRL: expired - revoking all certificates.");
                 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
                 X509_OBJECT_free_contents(&obj);
-                /* UNLOCK */
-                pthread_mutex_unlock(&opts->crl_lock);
                 return 0;
             }
             X509_OBJECT_free_contents(&obj);
@@ -617,8 +597,6 @@
                     OPENSSL_free(cp);
                     X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
                     X509_OBJECT_free_contents(&obj);
-                    /* UNLOCK */
-                    pthread_mutex_unlock(&opts->crl_lock);
                     return 0;
                 }
             }
@@ -626,20 +604,13 @@
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->crl_lock);
-
     /* cert-to-name already successful */
     if (session->username) {
         return 1;
     }
 
-    /* LOCK */
-    pthread_mutex_lock(&opts->ctn_lock);
     /* cert-to-name */
     rc = nc_tls_cert_to_name(opts->ctn, cert, &map_type, &username);
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->ctn_lock);
 
     if (rc) {
         if (rc == -1) {
@@ -682,10 +653,21 @@
     return 0;
 }
 
-static int
-_nc_tls_server_set_cert(const char *cert, int ch)
+API int
+nc_server_tls_add_endpt_listen(const char *name, const char *address, uint16_t port)
 {
-    struct nc_tls_server_opts *opts;
+    return nc_server_add_endpt_listen(name, address, port, NC_TI_OPENSSL);
+}
+
+API int
+nc_server_tls_del_endpt(const char *name)
+{
+    return nc_server_del_endpt(name, NC_TI_OPENSSL);
+}
+
+static int
+nc_server_tls_set_cert(const char *cert, struct nc_server_tls_opts *opts)
+{
     X509 *x509_cert;
 
     if (!cert) {
@@ -693,11 +675,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -715,43 +692,40 @@
     }
     X509_free(x509_cert);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_cert(const char *cert)
+nc_server_tls_endpt_set_cert(const char *endpt_name, const char *cert)
 {
-    return _nc_tls_server_set_cert(cert, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_cert(cert, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_cert(const char *cert)
+nc_server_tls_ch_set_cert(const char *cert)
 {
-    return _nc_tls_server_set_cert(cert, 1);
+    return nc_server_tls_set_cert(cert, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_set_cert_path(const char *cert_path, int ch)
+nc_server_tls_set_cert_path(const char *cert_path, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
-
     if (!cert_path) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -766,32 +740,35 @@
         goto fail;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_cert_path(const char *cert_path)
+nc_server_tls_set_endpt_cert_path(const char *endpt_name, const char *cert_path)
 {
-    return _nc_tls_server_set_cert_path(cert_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_cert_path(cert_path, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_cert_path(const char *cert_path)
+nc_server_tls_ch_set_cert_path(const char *cert_path)
 {
-    return _nc_tls_server_set_cert_path(cert_path, 1);
+    return nc_server_tls_set_cert_path(cert_path, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_set_key(const char *privkey, int is_rsa, int ch)
+nc_server_tls_set_key(const char *privkey, int is_rsa, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     EVP_PKEY *key;;
 
     if (!privkey) {
@@ -799,11 +776,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -821,43 +793,40 @@
     }
     EVP_PKEY_free(key);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_key(const char *privkey, int is_rsa)
+nc_server_tls_endpt_set_key(const char *endpt_name, const char *privkey, int is_rsa)
 {
-    return _nc_tls_server_set_key(privkey, is_rsa, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_key(privkey, is_rsa, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_key(const char *privkey, int is_rsa)
+nc_server_tls_ch_set_key(const char *privkey, int is_rsa)
 {
-    return _nc_tls_server_set_key(privkey, is_rsa, 1);
+    return nc_server_tls_set_key(privkey, is_rsa, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_set_key_path(const char *privkey_path, int ch)
+nc_server_tls_set_key_path(const char *privkey_path, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
-
     if (!privkey_path) {
         ERRARG;
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -872,32 +841,35 @@
         goto fail;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_key_path(const char *privkey_path)
+nc_server_tls_endpt_set_key_path(const char *endpt_name, const char *privkey_path)
 {
-    return _nc_tls_server_set_key_path(privkey_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_key_path(privkey_path, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_key_path(const char *privkey_path)
+nc_server_tls_ch_set_key_path(const char *privkey_path)
 {
-    return _nc_tls_server_set_key_path(privkey_path, 1);
+    return nc_server_tls_set_key_path(privkey_path, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_add_trusted_cert(const char *cert, int ch)
+nc_server_tls_add_trusted_cert(const char *cert, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     X509_STORE *cert_store;
     X509 *x509_cert;
 
@@ -906,11 +878,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -934,32 +901,35 @@
     }
     X509_free(x509_cert);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_add_trusted_cert(const char *cert)
+nc_server_tls_endpt_add_trusted_cert(const char *endpt_name, const char *cert)
 {
-    return _nc_tls_server_add_trusted_cert(cert, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_add_trusted_cert(cert, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_add_trusted_cert(const char *cert)
+nc_server_tls_ch_add_trusted_cert(const char *cert)
 {
-    return _nc_tls_server_add_trusted_cert(cert, 1);
+    return nc_server_tls_add_trusted_cert(cert, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_add_trusted_cert_path(const char *cert_path, int ch)
+nc_server_tls_add_trusted_cert_path(const char *cert_path, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     X509_STORE *cert_store;
     X509 *x509_cert;
 
@@ -968,11 +938,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -998,32 +963,35 @@
     }
     X509_free(x509_cert);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_add_trusted_cert_path(const char *cert_path)
+nc_server_tls_endpt_add_trusted_cert_path(const char *endpt_name, const char *cert_path)
 {
-    return _nc_tls_server_add_trusted_cert_path(cert_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_add_trusted_cert_path(cert_path, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_add_trusted_cert_path(const char *cert_path)
+nc_server_tls_ch_add_trusted_cert_path(const char *cert_path)
 {
-    return _nc_tls_server_add_trusted_cert_path(cert_path, 1);
+    return nc_server_tls_add_trusted_cert_path(cert_path, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path, int ch)
+nc_server_tls_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     X509_STORE *cert_store;
     X509_LOOKUP *lookup;
 
@@ -1032,11 +1000,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
         opts->tls_ctx = SSL_CTX_new(TLSv1_2_server_method());
         if (!opts->tls_ctx) {
@@ -1078,67 +1041,66 @@
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&tls_opts.tls_ctx_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path)
+nc_server_tls_endpt_set_trusted_cacert_locations(const char *endpt_name, const char *cacert_file_path, const char *cacert_dir_path)
 {
-    return _nc_tls_server_set_trusted_cacert_locations(cacert_file_path, cacert_dir_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_trusted_cacert_locations(cacert_file_path, cacert_dir_path, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path)
+nc_server_tls_ch_set_trusted_cacert_locations(const char *cacert_file_path, const char *cacert_dir_path)
 {
-    return _nc_tls_server_set_trusted_cacert_locations(cacert_file_path, cacert_dir_path, 1);
+    return nc_server_tls_set_trusted_cacert_locations(cacert_file_path, cacert_dir_path, &tls_ch_opts);
 }
 
 static void
-_nc_tls_server_destroy_certs(int ch)
+nc_server_tls_destroy_certs(struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
-
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->tls_ctx_lock);
-
     if (!opts->tls_ctx) {
-        /* UNLOCK */
-        pthread_mutex_unlock(&opts->tls_ctx_lock);
         return;
     }
 
     SSL_CTX_free(opts->tls_ctx);
     opts->tls_ctx = NULL;
-
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
 }
 
 API void
-nc_tls_server_destroy_certs(void)
+nc_server_tls_endpt_destroy_certs(const char *endpt_name)
 {
-    _nc_tls_server_destroy_certs(0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return;
+    }
+
+    nc_server_tls_destroy_certs(endpt->ti_opts);
 }
 
 API void
-nc_tls_server_ch_destroy_certs(void)
+nc_server_tls_ch_destroy_certs(void)
 {
-    _nc_tls_server_destroy_certs(1);
+    nc_server_tls_destroy_certs(&tls_ch_opts);
 }
 
 static int
-_nc_tls_server_set_crl_locations(const char *crl_file_path, const char *crl_dir_path, int ch)
+nc_server_tls_set_crl_locations(const char *crl_file_path, const char *crl_dir_path, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     X509_LOOKUP *lookup;
 
     if (!crl_file_path && !crl_dir_path) {
@@ -1146,11 +1108,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->crl_lock);
-
     if (!opts->crl_store) {
         opts->crl_store = X509_STORE_new();
     }
@@ -1181,67 +1138,66 @@
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->crl_lock);
     return 0;
 
 fail:
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->crl_lock);
     return -1;
 }
 
 API int
-nc_tls_server_set_crl_locations(const char *crl_file_path, const char *crl_dir_path)
+nc_server_tls_endpt_set_crl_locations(const char *endpt_name, const char *crl_file_path, const char *crl_dir_path)
 {
-    return _nc_tls_server_set_crl_locations(crl_file_path, crl_dir_path, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_set_crl_locations(crl_file_path, crl_dir_path, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_set_crl_locations(const char *crl_file_path, const char *crl_dir_path)
+nc_server_tls_ch_set_crl_locations(const char *crl_file_path, const char *crl_dir_path)
 {
-    return _nc_tls_server_set_crl_locations(crl_file_path, crl_dir_path, 1);
+    return nc_server_tls_set_crl_locations(crl_file_path, crl_dir_path, &tls_ch_opts);
 }
 
 static void
-_nc_tls_server_destroy_crls(int ch)
+nc_server_tls_destroy_crls(struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
-
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->crl_lock);
-
     if (!opts->crl_store) {
-        /* UNLOCK */
-        pthread_mutex_unlock(&opts->crl_lock);
         return;
     }
 
     X509_STORE_free(opts->crl_store);
     opts->crl_store = NULL;
-
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->crl_lock);
 }
 
 API void
-nc_tls_server_destroy_crls(void)
+nc_server_tls_endpt_destroy_crls(const char *endpt_name)
 {
-    _nc_tls_server_destroy_crls(0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return;
+    }
+
+    nc_server_tls_destroy_crls(endpt->ti_opts);
 }
 
 API void
-nc_tls_server_ch_destroy_crls(void)
+nc_server_tls_ch_destroy_crls(void)
 {
-    _nc_tls_server_destroy_crls(1);
+    nc_server_tls_destroy_crls(&tls_ch_opts);
 }
 
 static int
-_nc_tls_server_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, int ch)
+nc_server_tls_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     struct nc_ctn *ctn, *new;
 
     if (!fingerprint || !map_type || ((map_type == NC_TLS_CTN_SPECIFIED) && !name)
@@ -1250,8 +1206,6 @@
         return -1;
     }
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
     new = malloc(sizeof *new);
 
     nc_ctx_lock(-1, NULL);
@@ -1262,9 +1216,6 @@
     new->map_type = map_type;
     new->next = NULL;
 
-    /* LOCK */
-    pthread_mutex_lock(&opts->ctn_lock);
-
     if (!opts->ctn) {
         /* the first item */
         opts->ctn = new;
@@ -1279,36 +1230,35 @@
         ctn->next = new;
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->ctn_lock);
-
     return 0;
 }
 
 API int
-nc_tls_server_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
+nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
 {
-    return _nc_tls_server_add_ctn(id, fingerprint, map_type, name, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_add_ctn(id, fingerprint, map_type, name, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
+nc_server_tls_ch_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
 {
-    return _nc_tls_server_add_ctn(id, fingerprint, map_type, name, 1);
+    return nc_server_tls_add_ctn(id, fingerprint, map_type, name, &tls_ch_opts);
 }
 
 static int
-_nc_tls_server_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, int ch)
+nc_server_tls_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name, struct nc_server_tls_opts *opts)
 {
-    struct nc_tls_server_opts *opts;
     struct nc_ctn *ctn, *next, *prev;
     int ret = -1;
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
-
-    /* LOCK */
-    pthread_mutex_lock(&opts->ctn_lock);
-
     if ((id < 0) && !fingerprint && !map_type && !name) {
         ctn = opts->ctn;
         nc_ctx_lock(-1, NULL);
@@ -1323,7 +1273,7 @@
             ret = 0;
         }
         nc_ctx_unlock();
-        tls_opts.ctn = NULL;
+        opts->ctn = NULL;
     } else {
         prev = NULL;
         ctn = opts->ctn;
@@ -1341,7 +1291,7 @@
                     prev->next = ctn->next;
                     next = ctn->next;
                 } else {
-                    tls_opts.ctn = ctn->next;
+                    opts->ctn = ctn->next;
                     next = ctn->next;
                 }
                 free(ctn);
@@ -1355,34 +1305,41 @@
         }
     }
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->ctn_lock);
-
     return ret;
 }
 
 API int
-nc_tls_server_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
+nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
 {
-    return _nc_tls_server_del_ctn(id, fingerprint, map_type, name, 0);
+    struct nc_endpt *endpt;
+
+    endpt = nc_server_get_endpt(endpt_name, NC_TI_OPENSSL);
+    if (!endpt) {
+        ERR("Endpoint \"%s\" was not found.", endpt_name);
+        return -1;
+    }
+
+    return nc_server_tls_del_ctn(id, fingerprint, map_type, name, endpt->ti_opts);
 }
 
 API int
-nc_tls_server_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
+nc_server_tls_ch_del_ctn(int64_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE map_type, const char *name)
 {
-    return _nc_tls_server_del_ctn(id, fingerprint, map_type, name, 1);
+    return nc_server_tls_del_ctn(id, fingerprint, map_type, name, &tls_ch_opts);
 }
 
 API void
-nc_tls_server_free_opts(void)
+nc_server_tls_ch_opts_clear(void)
 {
-    nc_tls_server_destroy_certs();
-    nc_tls_server_destroy_crls();
-    nc_tls_server_del_ctn(-1, NULL, 0, NULL);
+    nc_server_tls_opts_clear(&tls_ch_opts);
+}
 
-    nc_tls_server_ch_destroy_certs();
-    nc_tls_server_ch_destroy_crls();
-    nc_tls_server_ch_del_ctn(-1, NULL, 0, NULL);
+void
+nc_server_tls_opts_clear(struct nc_server_tls_opts *opts)
+{
+    nc_server_tls_destroy_certs(opts);
+    nc_server_tls_destroy_crls(opts);
+    nc_server_tls_del_ctn(-1, NULL, 0, NULL, opts);
 }
 
 static void
@@ -1391,15 +1348,21 @@
     pthread_key_create(&verify_key, NULL);
 }
 
-int
-nc_accept_tls_session(struct nc_session *session, int sock, int timeout, int ch)
+API int
+nc_connect_callhome_tls(const char *host, uint16_t port, int timeout, struct nc_session **session)
 {
-    struct nc_tls_server_opts *opts;
+    return nc_connect_callhome(host, port, NC_TI_OPENSSL, timeout, session);
+}
+
+int
+nc_accept_tls_session(struct nc_session *session, int sock, int timeout)
+{
+    struct nc_server_tls_opts *opts;
     struct pollfd pfd;
     struct timespec old_ts, new_ts;
     int ret, elapsed = 0;
 
-    opts = (ch ? &tls_ch_opts : &tls_opts);
+    opts = session->ti_opts;
 
     pfd.fd = sock;
     pfd.events = POLLIN;
@@ -1433,17 +1396,8 @@
     /* data waiting, prepare session */
     session->ti_type = NC_TI_OPENSSL;
 
-    /* LOCK */
-    ret = nc_timedlock(&opts->tls_ctx_lock, timeout, &elapsed);
-    if (ret < 1) {
-        return ret;
-    }
-
     session->ti.tls = SSL_new(opts->tls_ctx);
 
-    /* UNLOCK */
-    pthread_mutex_unlock(&opts->tls_ctx_lock);
-
     if (!session->ti.tls) {
         ERR("Failed to create TLS structure from context.");
         close(sock);