diff --git a/src/session_p.h b/src/session_p.h index 02658ad2..86bbace6 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -437,11 +437,7 @@ struct nc_server_opts { void *pubkey_auth_data; void (*pubkey_auth_data_free)(void *data); - int (*interactive_auth_sess_clb)(const struct nc_session *session, ssh_session ssh_sess, ssh_message msg, void *user_data); - void *interactive_auth_sess_data; - void (*interactive_auth_sess_data_free)(void *data); - - int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data); + int (*interactive_auth_clb)(const struct nc_session *session, ssh_session ssh_sess, ssh_message msg, void *user_data); void *interactive_auth_data; void (*interactive_auth_data_free)(void *data); diff --git a/src/session_server.c b/src/session_server.c index 4da1bc94..fee4d70e 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -29,24 +29,30 @@ #include #include #include +#include #include #include #include #include +#ifdef NC_ENABLED_SSH_TLS +#include +#endif + #include "compat.h" -#include "config_new_ssh.h" -#include "libnetconf.h" +#include "config.h" +#include "log_p.h" +#include "messages_p.h" +#include "messages_server.h" +#include "server_config_p.h" +#include "session.h" +#include "session_p.h" #include "session_server.h" #include "session_server_ch.h" struct nc_server_opts server_opts = { -#ifdef NC_ENABLED_SSH - .authkey_lock = PTHREAD_MUTEX_INITIALIZER, -#endif - .bind_lock = PTHREAD_MUTEX_INITIALIZER, - .endpt_lock = PTHREAD_RWLOCK_INITIALIZER, - .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER + .config_lock = PTHREAD_RWLOCK_INITIALIZER, + .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER, }; static nc_rpc_clb global_rpc_clb = NULL; @@ -57,13 +63,10 @@ nc_server_endpt_lock_get(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx) uint16_t i; struct nc_endpt *endpt = NULL; - if (!name) { - ERRARG("endpt_name"); - return NULL; - } + NC_CHECK_ARG_RET(NULL, name, NULL); - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_lock); + /* READ LOCK */ + pthread_rwlock_rdlock(&server_opts.config_lock); for (i = 0; i < server_opts.endpt_count; ++i) { if (!strcmp(server_opts.endpts[i].name, name) && (!ti || (server_opts.endpts[i].ti == ti))) { @@ -75,7 +78,7 @@ nc_server_endpt_lock_get(const char *name, NC_TRANSPORT_IMPL ti, uint16_t *idx) if (!endpt) { ERR(NULL, "Endpoint \"%s\" was not found.", name); /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); + pthread_rwlock_unlock(&server_opts.config_lock); return NULL; } @@ -95,16 +98,13 @@ nc_server_ch_client_lock(const char *name, const char *endpt_name, NC_TRANSPORT_ *client_p = NULL; - if (!name) { - ERRARG("client_name"); - return NULL; - } + NC_CHECK_ARG_RET(NULL, name, NULL); /* READ LOCK */ pthread_rwlock_rdlock(&server_opts.ch_client_lock); for (i = 0; i < server_opts.ch_client_count; ++i) { - if (!strcmp(server_opts.ch_clients[i].name, name)) { + if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) { client = &server_opts.ch_clients[i]; if (!endpt_name && !ti) { /* return only client */ @@ -122,7 +122,7 @@ nc_server_ch_client_lock(const char *name, const char *endpt_name, NC_TRANSPORT_ } if (!client) { - ERR(NULL, "Call Home client \"%s\" was not found.", name); + VRB(NULL, "Call Home client \"%s\" was not found.", name); /* READ UNLOCK */ pthread_rwlock_unlock(&server_opts.ch_client_lock); @@ -155,10 +155,10 @@ API void nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason) { if (!session) { - ERRARG("session"); + ERRARG(session, "session"); return; } else if (!reason) { - ERRARG("reason"); + ERRARG(session, "reason"); return; } @@ -172,10 +172,10 @@ API void nc_session_set_killed_by(struct nc_session *session, uint32_t sid) { if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) { - ERRARG("session"); + ERRARG(session, "session"); return; } else if (!sid) { - ERRARG("sid"); + ERRARG(session, "sid"); return; } @@ -186,16 +186,97 @@ API void nc_session_set_status(struct nc_session *session, NC_STATUS status) { if (!session) { - ERRARG("session"); + ERRARG(session, "session"); return; } else if (!status) { - ERRARG("status"); + ERRARG(session, "status"); return; } session->status = status; } +API int +nc_server_init_ctx(struct ly_ctx **ctx) +{ + int new_ctx = 0, i, ret = 0; + struct lys_module *module; + /* all features */ + const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL}; + /* all features (module has no features) */ + const char *ietf_netconf_monitoring_features[] = {NULL}; + + NC_CHECK_ARG_RET(NULL, ctx, 1); + + if (!*ctx) { + /* context not given, create a new one */ + if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) { + ERR(NULL, "Couldn't create new libyang context.\n"); + ret = 1; + goto cleanup; + } + new_ctx = 1; + } + + if (new_ctx) { + /* new context created, implement both modules */ + if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) { + ERR(NULL, "Loading module \"ietf-netconf\" failed.\n"); + ret = 1; + goto cleanup; + } + + if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) { + ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n"); + ret = 1; + goto cleanup; + } + + goto cleanup; + } + + module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf"); + if (module) { + /* ietf-netconf module is present, check features */ + for (i = 0; ietf_netconf_features[i]; i++) { + if (lys_feature_value(module, ietf_netconf_features[i])) { + /* feature not found, enable all of them */ + if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) { + ERR(NULL, "Loading module \"ietf-netconf\" failed.\n"); + ret = 1; + goto cleanup; + } + + break; + } + } + } else { + /* ietf-netconf module not found, add it */ + if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) { + ERR(NULL, "Loading module \"ietf-netconf\" failed.\n"); + ret = 1; + goto cleanup; + } + } + + module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring"); + if (!module) { + /* ietf-netconf-monitoring module not found, add it */ + if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) { + ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n"); + ret = 1; + goto cleanup; + } + } + +cleanup: + if (new_ctx && ret) { + ly_ctx_destroy(*ctx); + *ctx = NULL; + } + return ret; +} + int nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka) { @@ -229,7 +310,7 @@ nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka goto fail; } - if (nc_sock_enable_keepalive(sock, ka)) { + if (nc_sock_configure_keepalive(sock, ka)) { goto fail; } @@ -271,7 +352,6 @@ nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno)); goto fail; } - return sock; fail: @@ -434,7 +514,7 @@ sock_host_inet6(const struct sockaddr_in6 *addr, 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) +nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host, uint16_t *port, uint16_t *idx) { sigset_t sigmask, origmask; uint16_t i, j, pfd_count, client_port; @@ -450,6 +530,9 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch return -1; } + /* LOCK */ + pthread_mutex_lock(bind_lock); + for (i = 0, pfd_count = 0; i < bind_count; ++i) { if (binds[i].sock < 0) { /* invalid socket */ @@ -478,10 +561,14 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch if (!ret) { /* we timeouted */ free(pfd); + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return 0; } else if (ret == -1) { ERR(NULL, "Poll failed (%s).", strerror(errno)); free(pfd); + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return -1; } @@ -506,9 +593,10 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch } } free(pfd); - if (sock == -1) { ERRINT; + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return -1; } @@ -516,6 +604,8 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len); if (client_sock < 0) { ERR(NULL, "Accept failed (%s).", strerror(errno)); + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return -1; } @@ -561,10 +651,14 @@ nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, int timeout, ch if (idx) { *idx = i; } + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return client_sock; fail: close(client_sock); + /* UNLOCK */ + pthread_mutex_unlock(bind_lock); return -1; } @@ -676,7 +770,7 @@ nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *se * @param[in] ctx Context to initialize. */ static void -nc_server_init_ctx(const struct ly_ctx *ctx) +nc_server_init_cb_ctx(const struct ly_ctx *ctx) { struct lysc_node *rpc; @@ -707,8 +801,6 @@ nc_server_init(void) pthread_rwlockattr_t attr, *attr_p = NULL; int r; - nc_init(); - server_opts.new_session_id = 1; server_opts.new_client_id = 1; @@ -724,7 +816,7 @@ nc_server_init(void) } #endif - if ((r = pthread_rwlock_init(&server_opts.endpt_lock, attr_p))) { + if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) { ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r)); goto error; } @@ -736,6 +828,19 @@ nc_server_init(void) if (attr_p) { pthread_rwlockattr_destroy(attr_p); } + +#ifdef NC_ENABLED_SSH_TLS + if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) { + ERR(NULL, "%s: failed to init CURL.", __func__); + goto error; + } +#endif + + if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) { + ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r)); + goto error; + } + return 0; error: @@ -760,11 +865,12 @@ nc_server_destroy(void) server_opts.content_id_data_free(server_opts.content_id_data); } -#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS) - nc_server_del_endpt(NULL, 0); - nc_server_ch_del_client(NULL); -#endif -#ifdef NC_ENABLED_SSH + nc_server_config_listen(NULL, NC_OP_DELETE); + nc_server_config_ch(NULL, NC_OP_DELETE); + + pthread_mutex_destroy(&server_opts.bind_lock); + +#ifdef NC_ENABLED_SSH_TLS if (server_opts.passwd_auth_data && server_opts.passwd_auth_data_free) { server_opts.passwd_auth_data_free(server_opts.passwd_auth_data); } @@ -777,60 +883,31 @@ nc_server_destroy(void) server_opts.pubkey_auth_data = NULL; server_opts.pubkey_auth_data_free = NULL; - if (server_opts.interactive_auth_sess_data && server_opts.interactive_auth_sess_data_free) { - server_opts.interactive_auth_sess_data_free(server_opts.interactive_auth_sess_data); - } - server_opts.interactive_auth_sess_data = NULL; - server_opts.interactive_auth_sess_data_free = NULL; - if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) { server_opts.interactive_auth_data_free(server_opts.interactive_auth_data); } server_opts.interactive_auth_data = NULL; server_opts.interactive_auth_data_free = NULL; - nc_server_ssh_del_authkey(NULL, NULL, 0, NULL); - - if (server_opts.hostkey_data && server_opts.hostkey_data_free) { - server_opts.hostkey_data_free(server_opts.hostkey_data); - } - server_opts.hostkey_data = NULL; - server_opts.hostkey_data_free = NULL; - - /* PAM */ - free(server_opts.conf_name); - free(server_opts.conf_dir); - server_opts.conf_name = NULL; - server_opts.conf_dir = NULL; -#endif -#ifdef NC_ENABLED_TLS - if (server_opts.server_cert_data && server_opts.server_cert_data_free) { - server_opts.server_cert_data_free(server_opts.server_cert_data); - } - server_opts.server_cert_data = NULL; - server_opts.server_cert_data_free = NULL; - if (server_opts.trusted_cert_list_data && server_opts.trusted_cert_list_data_free) { - server_opts.trusted_cert_list_data_free(server_opts.trusted_cert_list_data); - } - server_opts.trusted_cert_list_data = NULL; - server_opts.trusted_cert_list_data_free = NULL; -#endif - nc_destroy(); + nc_server_config_ks_keystore(NULL, NC_OP_DELETE); + nc_server_config_ts_truststore(NULL, NC_OP_DELETE); + curl_global_cleanup(); +#endif /* NC_ENABLED_SSH_TLS */ } API int nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported) { if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) { - ERRARG("basic_mode"); + ERRARG(NULL, "basic_mode"); return -1; } else if (also_supported && !(also_supported & (NC_WD_ALL | NC_WD_ALL_TAG | NC_WD_TRIM | NC_WD_EXPLICIT))) { - ERRARG("also_supported"); + ERRARG(NULL, "also_supported"); return -1; } - server_opts.wd_basic_mode = basic_mode; - server_opts.wd_also_supported = also_supported; + ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode); + ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported); return 0; } @@ -838,15 +915,15 @@ API void nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported) { if (!basic_mode && !also_supported) { - ERRARG("basic_mode and also_supported"); + ERRARG(NULL, "basic_mode and also_supported"); return; } if (basic_mode) { - *basic_mode = server_opts.wd_basic_mode; + *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode); } if (also_supported) { - *also_supported = server_opts.wd_also_supported; + *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported); } } @@ -856,7 +933,7 @@ nc_server_set_capability(const char *value) void *mem; if (!value || !value[0]) { - ERRARG("value must not be empty"); + ERRARG(NULL, "value must not be empty"); return EXIT_FAILURE; } @@ -894,12 +971,6 @@ nc_server_get_hello_timeout(void) return server_opts.hello_timeout; } -API void -nc_server_set_idle_timeout(uint16_t idle_timeout) -{ - server_opts.idle_timeout = idle_timeout; -} - API uint16_t nc_server_get_idle_timeout(void) { @@ -912,25 +983,18 @@ nc_accept_inout(int fdin, int fdout, const char *username, const struct ly_ctx * NC_MSG_TYPE msgtype; struct timespec ts_cur; - if (!ctx) { - ERRARG("ctx"); - return NC_MSG_ERROR; - } else if (fdin < 0) { - ERRARG("fdin"); + NC_CHECK_ARG_RET(NULL, ctx, username, session, NC_MSG_ERROR); + + if (fdin < 0) { + ERRARG(NULL, "fdin"); return NC_MSG_ERROR; } else if (fdout < 0) { - ERRARG("fdout"); - return NC_MSG_ERROR; - } else if (!username) { - ERRARG("username"); - return NC_MSG_ERROR; - } else if (!session) { - ERRARG("session"); + ERRARG(NULL, "fdout"); return NC_MSG_ERROR; } /* init ctx as needed */ - nc_server_init_ctx(ctx); + nc_server_init_cb_ctx(ctx); /* prepare session structure */ *session = nc_new_session(NC_SERVER, 0); @@ -1171,13 +1235,7 @@ nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session) { uint8_t q_id; - if (!ps) { - ERRARG("ps"); - return -1; - } else if (!session) { - ERRARG("session"); - return -1; - } + NC_CHECK_ARG_RET(session, ps, session, -1); /* LOCK */ if (nc_ps_lock(ps, &q_id, __func__)) { @@ -1242,13 +1300,7 @@ nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session) uint8_t q_id; int ret, ret2; - if (!ps) { - ERRARG("ps"); - return -1; - } else if (!session) { - ERRARG("session"); - return -1; - } + NC_CHECK_ARG_RET(session, ps, session, -1); /* LOCK */ if (nc_ps_lock(ps, &q_id, __func__)) { @@ -1269,10 +1321,7 @@ nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx) uint8_t q_id; struct nc_session *ret = NULL; - if (!ps) { - ERRARG("ps"); - return NULL; - } + NC_CHECK_ARG_RET(NULL, ps, NULL); /* LOCK */ if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) { @@ -1296,10 +1345,7 @@ nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match uint16_t i; struct nc_session *ret = NULL; - if (!ps) { - ERRARG("ps"); - return NULL; - } + NC_CHECK_ARG_RET(NULL, ps, NULL); /* LOCK */ if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) { @@ -1325,10 +1371,7 @@ nc_ps_session_count(struct nc_pollsession *ps) uint8_t q_id; uint16_t session_count; - if (!ps) { - ERRARG("ps"); - return 0; - } + NC_CHECK_ARG_RET(NULL, ps, 0); /* LOCK (just for memory barrier so that we read the current value) */ if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) { @@ -1379,13 +1422,9 @@ nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_serv struct lyd_node *e; int r, ret; - if (!session) { - ERRARG("session"); - return NC_PSPOLL_ERROR; - } else if (!rpc) { - ERRARG("rpc"); - return NC_PSPOLL_ERROR; - } else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) { + NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR); + + if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) { ERR(session, "Invalid session to receive RPCs."); return NC_PSPOLL_ERROR; } @@ -1475,10 +1514,10 @@ nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, /* check parameters */ if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) { - ERRARG("session"); + ERRARG(NULL, "session"); return NC_MSG_ERROR; } else if (!notif || !notif->ntf || !notif->eventtime) { - ERRARG("notif"); + ERRARG(NULL, "notif"); return NC_MSG_ERROR; } @@ -1590,14 +1629,14 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon struct pollfd pfd; int r, ret = 0; -#ifdef NC_ENABLED_SSH +#ifdef NC_ENABLED_SSH_TLS ssh_message ssh_msg; struct nc_session *new; -#endif +#endif /* NC_ENABLED_SSH_TLS */ /* check timeout first */ if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout && - (now_mono >= session->opts.server.last_rpc + server_opts.idle_timeout)) { + (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) { sprintf(msg, "session idle timeout elapsed"); session->status = NC_STATUS_INVALID; session->term_reason = NC_SESSION_TERM_TIMEOUT; @@ -1613,8 +1652,37 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon } switch (session->ti_type) { -#ifdef NC_ENABLED_SSH +#ifdef NC_ENABLED_SSH_TLS case NC_TI_LIBSSH: + ssh_msg = ssh_message_get(session->ti.libssh.session); + if (ssh_msg) { + nc_session_ssh_msg(session, NULL, ssh_msg, NULL); + if (session->ti.libssh.next) { + for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) { + if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel && + (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { + /* new NETCONF SSH channel */ + ret = NC_PSPOLL_SSH_CHANNEL; + break; + } + } + if (new != session) { + ssh_message_free(ssh_msg); + break; + } + } + if (!ret) { + /* just some SSH message */ + ret = NC_PSPOLL_SSH_MSG; + } + ssh_message_free(ssh_msg); + + /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC + * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed + */ + break; + } + r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0); if (r == SSH_EOF) { sprintf(msg, "SSH channel unexpected EOF"); @@ -1627,35 +1695,13 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon session->term_reason = NC_SESSION_TERM_OTHER; ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR; } else if (!r) { - if (session->flags & NC_SESSION_SSH_NEW_MSG) { - /* new SSH message */ - session->flags &= ~NC_SESSION_SSH_NEW_MSG; - if (session->ti.libssh.next) { - for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) { - if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel && - (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { - /* new NETCONF SSH channel */ - ret = NC_PSPOLL_SSH_CHANNEL; - break; - } - } - if (new != session) { - break; - } - } - - /* just some SSH message */ - ret = NC_PSPOLL_SSH_MSG; - } else { - ret = NC_PSPOLL_TIMEOUT; - } + /* no application data received */ + ret = NC_PSPOLL_TIMEOUT; } else { /* we have some application data */ ret = NC_PSPOLL_RPC; } break; -#endif -#ifdef NC_ENABLED_TLS case NC_TI_OPENSSL: r = SSL_pending(session->ti.tls); if (!r) { @@ -1695,7 +1741,7 @@ nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mon ret = NC_PSPOLL_RPC; } break; -#endif +#endif /* NC_ENABLED_SSH_TLS */ case NC_TI_FD: case NC_TI_UNIX: pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock; @@ -1747,10 +1793,7 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) struct nc_ps_session *cur_ps_session; struct nc_server_rpc *rpc = NULL; - if (!ps) { - ERRARG("ps"); - return NC_PSPOLL_ERROR; - } + NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR); /* PS LOCK */ if (nc_ps_lock(ps, &q_id, __func__)) { @@ -1803,10 +1846,10 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) cur_ps_session->state = NC_PS_STATE_NONE; break; case NC_PSPOLL_TIMEOUT: -#ifdef NC_ENABLED_SSH +#ifdef NC_ENABLED_SSH_TLS case NC_PSPOLL_SSH_CHANNEL: case NC_PSPOLL_SSH_MSG: -#endif +#endif /* NC_ENABLED_SSH_TLS */ cur_ps_session->state = NC_PS_STATE_NONE; break; case NC_PSPOLL_RPC: @@ -1871,10 +1914,10 @@ nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session) case NC_PSPOLL_RPC: case NC_PSPOLL_SESSION_TERM: case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR: -#ifdef NC_ENABLED_SSH +#ifdef NC_ENABLED_SSH_TLS case NC_PSPOLL_SSH_CHANNEL: case NC_PSPOLL_SSH_MSG: -#endif +#endif /* NC_ENABLED_SSH_TLS */ if (session) { *session = cur_session; } @@ -1929,7 +1972,7 @@ nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *)) struct nc_session *session; if (!ps) { - ERRARG("ps"); + ERRARG(NULL, "ps"); return; } @@ -2031,1297 +2074,224 @@ nc_accept_unix(struct nc_session *session, int sock) } API int -nc_server_add_endpt(const char *name, NC_TRANSPORT_IMPL ti) +nc_server_endpt_count(void) +{ + return server_opts.endpt_count; +} + +API int +nc_server_is_endpt(const char *name) { uint16_t i; - int ret = 0; + int found = 0; if (!name) { - ERRARG("name"); - return -1; + return found; } - /* BIND LOCK */ - pthread_mutex_lock(&server_opts.bind_lock); - - /* ENDPT WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_lock); + /* CONFIG READ LOCK */ + pthread_rwlock_rdlock(&server_opts.config_lock); /* check name uniqueness */ for (i = 0; i < server_opts.endpt_count; ++i) { if (!strcmp(server_opts.endpts[i].name, name)) { - ERR(NULL, "Endpoint \"%s\" already exists.", name); - ret = -1; - goto cleanup; + found = 1; + break; } } - server_opts.endpts = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts); - if (!server_opts.endpts) { - ERRMEM; - ret = -1; - goto cleanup; + /* CONFIG UNLOCK */ + pthread_rwlock_unlock(&server_opts.config_lock); + + return found; +} + +API NC_MSG_TYPE +nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session) +{ + NC_MSG_TYPE msgtype; + int sock, ret; + char *host = NULL; + uint16_t port, bind_idx; + struct timespec ts_cur; + + NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR); + + /* init ctx as needed */ + nc_server_init_cb_ctx(ctx); + + /* CONFIG LOCK */ + pthread_rwlock_rdlock(&server_opts.config_lock); + + if (!server_opts.endpt_count) { + ERR(NULL, "No endpoints to accept sessions on."); + /* CONFIG UNLOCK */ + pthread_rwlock_unlock(&server_opts.config_lock); + return NC_MSG_ERROR; + } + + ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, &server_opts.bind_lock, timeout, &host, &port, &bind_idx); + if (ret < 1) { + free(host); + /* CONFIG UNLOCK */ + pthread_rwlock_unlock(&server_opts.config_lock); + if (!ret) { + return NC_MSG_WOULDBLOCK; + } + return NC_MSG_ERROR; } - memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts); - ++server_opts.endpt_count; - server_opts.endpts[server_opts.endpt_count - 1].name = strdup(name); - server_opts.endpts[server_opts.endpt_count - 1].ti = ti; - server_opts.endpts[server_opts.endpt_count - 1].ka.idle_time = 1; - server_opts.endpts[server_opts.endpt_count - 1].ka.max_probes = 10; - server_opts.endpts[server_opts.endpt_count - 1].ka.probe_interval = 5; + sock = ret; - server_opts.binds = nc_realloc(server_opts.binds, server_opts.endpt_count * sizeof *server_opts.binds); - if (!server_opts.binds) { + *session = nc_new_session(NC_SERVER, 0); + if (!(*session)) { ERRMEM; - ret = -1; + close(sock); + free(host); + msgtype = NC_MSG_ERROR; goto cleanup; } + (*session)->status = NC_STATUS_STARTING; + (*session)->ctx = (struct ly_ctx *)ctx; + (*session)->flags = NC_SESSION_SHAREDCTX; + (*session)->host = host; + (*session)->port = port; - memset(&server_opts.binds[server_opts.endpt_count - 1], 0, sizeof *server_opts.binds); - server_opts.binds[server_opts.endpt_count - 1].sock = -1; - - switch (ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - server_opts.endpts[server_opts.endpt_count - 1].opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); - if (!server_opts.endpts[server_opts.endpt_count - 1].opts.ssh) { - ERRMEM; - ret = -1; + /* sock gets assigned to session or closed */ +#ifdef NC_ENABLED_SSH_TLS + if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) { + ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT); + if (ret < 0) { + msgtype = NC_MSG_ERROR; + goto cleanup; + } else if (!ret) { + msgtype = NC_MSG_WOULDBLOCK; goto cleanup; } - server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_methods = - NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD; - server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_attempts = 3; - server_opts.endpts[server_opts.endpt_count - 1].opts.ssh->auth_timeout = 30; - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - server_opts.endpts[server_opts.endpt_count - 1].opts.tls = calloc(1, sizeof(struct nc_server_tls_opts)); - if (!server_opts.endpts[server_opts.endpt_count - 1].opts.tls) { - ERRMEM; - ret = -1; + } else if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) { + (*session)->data = server_opts.endpts[bind_idx].opts.tls; + ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT); + if (ret < 0) { + msgtype = NC_MSG_ERROR; + goto cleanup; + } else if (!ret) { + msgtype = NC_MSG_WOULDBLOCK; goto cleanup; } - break; -#endif - case NC_TI_UNIX: - server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts)); - if (!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock) { - ERRMEM; - ret = -1; + } else +#endif /* NC_ENABLED_SSH_TLS */ + if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) { + (*session)->data = server_opts.endpts[bind_idx].opts.unixsock; + ret = nc_accept_unix(*session, sock); + if (ret < 0) { + msgtype = NC_MSG_ERROR; goto cleanup; } - server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode_t)-1; - server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid_t)-1; - server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid_t)-1; - break; - default: + } else { ERRINT; - ret = -1; + close(sock); + msgtype = NC_MSG_ERROR; goto cleanup; } -cleanup: - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); + (*session)->data = NULL; - return ret; -} + /* CONFIG UNLOCK */ + pthread_rwlock_unlock(&server_opts.config_lock); -API int -nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti) -{ - uint32_t i; - int ret = -1; + /* assign new SID atomically */ + (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id); - /* BIND LOCK */ - pthread_mutex_lock(&server_opts.bind_lock); - - /* ENDPT WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.endpt_lock); - - if (!name && !ti) { - /* remove all endpoints */ - for (i = 0; i < server_opts.endpt_count; ++i) { - free(server_opts.endpts[i].name); - switch (server_opts.endpts[i].ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - nc_server_ssh_clear_opts(server_opts.endpts[i].opts.ssh); - free(server_opts.endpts[i].opts.ssh); - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - nc_server_tls_clear_opts(server_opts.endpts[i].opts.tls); - free(server_opts.endpts[i].opts.tls); - break; -#endif - case NC_TI_UNIX: - free(server_opts.endpts[i].opts.unixsock); - break; - default: - ERRINT; - /* won't get here ...*/ - break; - } - ret = 0; - } - free(server_opts.endpts); - server_opts.endpts = NULL; - - /* remove all binds */ - for (i = 0; i < server_opts.endpt_count; ++i) { - free(server_opts.binds[i].address); - if (server_opts.binds[i].sock > -1) { - close(server_opts.binds[i].sock); - } - } - free(server_opts.binds); - server_opts.binds = NULL; - - server_opts.endpt_count = 0; - - } else { - /* remove one endpoint with bind(s) or all endpoints using one transport protocol */ - for (i = 0; i < server_opts.endpt_count; ++i) { - if ((name && !strcmp(server_opts.endpts[i].name, name)) || (!name && (server_opts.endpts[i].ti == ti))) { - /* remove endpt */ - free(server_opts.endpts[i].name); - switch (server_opts.endpts[i].ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - nc_server_ssh_clear_opts(server_opts.endpts[i].opts.ssh); - free(server_opts.endpts[i].opts.ssh); - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - nc_server_tls_clear_opts(server_opts.endpts[i].opts.tls); - free(server_opts.endpts[i].opts.tls); - break; -#endif - case NC_TI_UNIX: - free(server_opts.endpts[i].opts.unixsock); - break; - default: - ERRINT; - break; - } - - /* remove bind(s) */ - free(server_opts.binds[i].address); - if (server_opts.binds[i].sock > -1) { - close(server_opts.binds[i].sock); - } - - /* move last endpt and bind(s) to the empty space */ - --server_opts.endpt_count; - if (!server_opts.endpt_count) { - free(server_opts.binds); - server_opts.binds = NULL; - free(server_opts.endpts); - server_opts.endpts = NULL; - } else if (i < server_opts.endpt_count) { - memcpy(&server_opts.binds[i], &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds); - memcpy(&server_opts.endpts[i], &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts); - } - - ret = 0; - if (name) { - break; - } - } - } - } - - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - - return ret; -} - -API int -nc_server_endpt_count(void) -{ - return server_opts.endpt_count; -} - -API int -nc_server_is_endpt(const char *name) -{ - uint16_t i; - int found = 0; - - if (!name) { - return found; - } - - /* ENDPT READ LOCK */ - pthread_rwlock_rdlock(&server_opts.endpt_lock); - - /* check name uniqueness */ - for (i = 0; i < server_opts.endpt_count; ++i) { - if (!strcmp(server_opts.endpts[i].name, name)) { - found = 1; - break; - } - } - - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return found; -} - -int -nc_server_endpt_set_address_port(const char *endpt_name, const char *address, uint16_t port) -{ - struct nc_endpt *endpt; - struct nc_bind *bind = NULL; - uint16_t i; - int sock = -1, set_addr, ret = 0; - - if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } else if ((!address && !port) || (address && port)) { - ERRARG("address and port"); - return -1; - } - - if (address) { - set_addr = 1; - } else { - set_addr = 0; - } - - /* BIND LOCK */ - pthread_mutex_lock(&server_opts.bind_lock); - - /* ENDPT LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, 0, &i); - if (!endpt) { - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - return -1; - } - - bind = &server_opts.binds[i]; - - if (set_addr) { - port = bind->port; - } else { - address = bind->address; - } - - if (!set_addr && (endpt->ti == NC_TI_UNIX)) { - ret = -1; - goto cleanup; - } - - /* we have all the information we need to create a listening socket */ - if (address && (port || (endpt->ti == NC_TI_UNIX))) { - /* create new socket, close the old one */ - if (endpt->ti == NC_TI_UNIX) { - sock = nc_sock_listen_unix(address, endpt->opts.unixsock); - } else { - sock = nc_sock_listen_inet(address, port, &endpt->ka); - } - if (sock == -1) { - ret = -1; - goto cleanup; - } - - if (bind->sock > -1) { - close(bind->sock); - } - bind->sock = sock; - } /* else we are just setting address or port */ - - if (set_addr) { - free(bind->address); - bind->address = strdup(address); - } else { - bind->port = port; - } - - if (sock > -1) { - switch (endpt->ti) { - case NC_TI_UNIX: - VRB(NULL, "Listening on %s for UNIX connections.", address); - break; -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - VRB(NULL, "Listening on %s:%u for SSH connections.", address, port); - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - VRB(NULL, "Listening on %s:%u for TLS connections.", address, port); - break; -#endif - default: - ERRINT; - break; - } - } - -cleanup: - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - - return ret; -} - -API int -nc_server_endpt_set_address(const char *endpt_name, const char *address) -{ - return nc_server_endpt_set_address_port(endpt_name, address, 0); -} - -#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS) - -API int -nc_server_endpt_set_port(const char *endpt_name, uint16_t port) -{ - return nc_server_endpt_set_address_port(endpt_name, NULL, port); -} - -#endif - -API int -nc_server_endpt_set_perms(const char *endpt_name, mode_t mode, uid_t uid, gid_t gid) -{ - struct nc_endpt *endpt; - uint16_t i; - int ret = 0; - - if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } else if (mode == 0) { - ERRARG("mode"); - return -1; - } - - /* ENDPT LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, 0, &i); - if (!endpt) { - return -1; - } - - if (endpt->ti != NC_TI_UNIX) { - ret = -1; - goto cleanup; - } - - endpt->opts.unixsock->mode = mode; - endpt->opts.unixsock->uid = uid; - endpt->opts.unixsock->gid = gid; - -cleanup: - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_endpt_enable_keepalives(const char *endpt_name, int enable) -{ - struct nc_endpt *endpt; - int ret = 0; - - if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } - - /* ENDPT LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, 0, NULL); - if (!endpt) { - return -1; - } - - endpt->ka.enabled = (enable ? 1 : 0); - - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int max_probes, int probe_interval) -{ - struct nc_endpt *endpt; - int ret = 0; - - if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } - - /* ENDPT LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, 0, NULL); - if (!endpt) { - return -1; - } - - if (idle_time > -1) { - endpt->ka.idle_time = idle_time; - } - if (max_probes > -1) { - endpt->ka.max_probes = max_probes; - } - if (probe_interval > -1) { - endpt->ka.probe_interval = probe_interval; - } - - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API NC_MSG_TYPE -nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session) -{ - NC_MSG_TYPE msgtype; - int sock, ret; - char *host = NULL; - uint16_t port, bind_idx; - struct timespec ts_cur; - - if (!ctx) { - ERRARG("ctx"); - return NC_MSG_ERROR; - } else if (!session) { - ERRARG("session"); - return NC_MSG_ERROR; - } - - /* init ctx as needed */ - nc_server_init_ctx(ctx); - - /* BIND LOCK */ - pthread_mutex_lock(&server_opts.bind_lock); - - if (!server_opts.endpt_count) { - ERR(NULL, "No endpoints to accept sessions on."); - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - return NC_MSG_ERROR; - } - - ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, timeout, &host, &port, &bind_idx); - if (ret < 1) { - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - free(host); - if (!ret) { - return NC_MSG_WOULDBLOCK; - } - return NC_MSG_ERROR; - } - - /* switch bind_lock for endpt_lock, so that another thread can accept another session */ - /* ENDPT READ LOCK */ - pthread_rwlock_rdlock(&server_opts.endpt_lock); - - /* BIND UNLOCK */ - pthread_mutex_unlock(&server_opts.bind_lock); - - sock = ret; - - *session = nc_new_session(NC_SERVER, 0); - if (!(*session)) { - ERRMEM; - close(sock); - free(host); - msgtype = NC_MSG_ERROR; - goto cleanup; - } - (*session)->status = NC_STATUS_STARTING; - (*session)->ctx = (struct ly_ctx *)ctx; - (*session)->flags = NC_SESSION_SHAREDCTX; - (*session)->host = host; - (*session)->port = port; - - /* sock gets assigned to session or closed */ -#ifdef NC_ENABLED_SSH - if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) { - (*session)->data = server_opts.endpts[bind_idx].opts.ssh; - ret = nc_accept_ssh_session(*session, sock, NC_TRANSPORT_TIMEOUT); - if (ret < 0) { - msgtype = NC_MSG_ERROR; - goto cleanup; - } else if (!ret) { - msgtype = NC_MSG_WOULDBLOCK; - goto cleanup; - } - } else -#endif -#ifdef NC_ENABLED_TLS - if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) { - (*session)->data = server_opts.endpts[bind_idx].opts.tls; - ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); - if (ret < 0) { - msgtype = NC_MSG_ERROR; - goto cleanup; - } else if (!ret) { - msgtype = NC_MSG_WOULDBLOCK; - goto cleanup; - } - } else -#endif - if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) { - (*session)->data = server_opts.endpts[bind_idx].opts.unixsock; - ret = nc_accept_unix(*session, sock); - if (ret < 0) { - msgtype = NC_MSG_ERROR; - goto cleanup; - } - } else { - ERRINT; - close(sock); - msgtype = NC_MSG_ERROR; - goto cleanup; - } - - (*session)->data = NULL; - - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - /* assign new SID atomically */ - (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id); - - /* NETCONF handshake */ - msgtype = nc_handshake_io(*session); - if (msgtype != NC_MSG_HELLO) { - nc_session_free(*session, NULL); - *session = NULL; - return msgtype; - } - - nc_timeouttime_get(&ts_cur, 0); - (*session)->opts.server.last_rpc = ts_cur.tv_sec; - nc_realtime_get(&ts_cur); - (*session)->opts.server.session_start = ts_cur.tv_sec; - (*session)->status = NC_STATUS_RUNNING; - - return msgtype; - -cleanup: - /* ENDPT UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - nc_session_free(*session, NULL); - *session = NULL; - return msgtype; -} - -#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS) - -/* client is expected to be locked */ -static int -_nc_server_ch_client_del_endpt(struct nc_ch_client *client, const char *endpt_name, NC_TRANSPORT_IMPL ti) -{ - uint16_t i; - int ret = -1; - - if (!endpt_name) { - /* remove all endpoints */ - for (i = 0; i < client->ch_endpt_count; ++i) { - free(client->ch_endpts[i].name); - free(client->ch_endpts[i].address); - if (client->ch_endpts[i].sock_pending != -1) { - close(client->ch_endpts[i].sock_pending); - } - switch (client->ch_endpts[i].ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - nc_server_ssh_clear_opts(client->ch_endpts[i].opts.ssh); - free(client->ch_endpts[i].opts.ssh); - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - nc_server_tls_clear_opts(client->ch_endpts[i].opts.tls); - free(client->ch_endpts[i].opts.tls); - break; -#endif - default: - ERRINT; - /* won't get here ...*/ - break; - } - } - free(client->ch_endpts); - client->ch_endpts = NULL; - client->ch_endpt_count = 0; - - ret = 0; - } else { - for (i = 0; i < client->ch_endpt_count; ++i) { - if (!strcmp(client->ch_endpts[i].name, endpt_name) && (!ti || (ti == client->ch_endpts[i].ti))) { - free(client->ch_endpts[i].name); - free(client->ch_endpts[i].address); - if (client->ch_endpts[i].sock_pending != -1) { - close(client->ch_endpts[i].sock_pending); - } - switch (client->ch_endpts[i].ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - nc_server_ssh_clear_opts(client->ch_endpts[i].opts.ssh); - free(client->ch_endpts[i].opts.ssh); - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - nc_server_tls_clear_opts(client->ch_endpts[i].opts.tls); - free(client->ch_endpts[i].opts.tls); - break; -#endif - default: - ERRINT; - /* won't get here ...*/ - break; - } - - /* move last endpoint to the empty space */ - --client->ch_endpt_count; - if (i < client->ch_endpt_count) { - memcpy(&client->ch_endpts[i], &client->ch_endpts[client->ch_endpt_count], sizeof *client->ch_endpts); - } else if (!server_opts.ch_client_count) { - free(server_opts.ch_clients); - server_opts.ch_clients = NULL; - } - - ret = 0; - break; - } - } - } - - return ret; -} - -API int -nc_server_ch_add_client(const char *name) -{ - uint16_t i; - struct nc_ch_client *client; - - if (!name) { - ERRARG("name"); - return -1; - } - - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.ch_client_lock); - - /* check name uniqueness */ - for (i = 0; i < server_opts.ch_client_count; ++i) { - if (!strcmp(server_opts.ch_clients[i].name, name)) { - ERR(NULL, "Call Home client \"%s\" already exists.", name); - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - return -1; - } - } - - ++server_opts.ch_client_count; - server_opts.ch_clients = nc_realloc(server_opts.ch_clients, server_opts.ch_client_count * sizeof *server_opts.ch_clients); - if (!server_opts.ch_clients) { - ERRMEM; - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - return -1; - } - client = &server_opts.ch_clients[server_opts.ch_client_count - 1]; - - client->name = strdup(name); - client->id = ATOMIC_INC_RELAXED(server_opts.new_client_id); - client->ch_endpts = NULL; - client->ch_endpt_count = 0; - client->conn_type = 0; - - /* set CH default options */ - client->start_with = NC_CH_FIRST_LISTED; - client->max_attempts = 3; - - pthread_mutex_init(&client->lock, NULL); - - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - - return 0; -} - -API int -nc_server_ch_del_client(const char *name) -{ - uint16_t i; - int ret = -1; - - /* WRITE LOCK */ - pthread_rwlock_wrlock(&server_opts.ch_client_lock); - - if (!name) { - /* remove all CH clients with endpoints */ - for (i = 0; i < server_opts.ch_client_count; ++i) { - free(server_opts.ch_clients[i].name); - - /* remove all endpoints */ - _nc_server_ch_client_del_endpt(&server_opts.ch_clients[i], NULL, 0); - - pthread_mutex_destroy(&server_opts.ch_clients[i].lock); - ret = 0; - } - free(server_opts.ch_clients); - server_opts.ch_clients = NULL; - - server_opts.ch_client_count = 0; - - } else { - /* remove one client with endpoints */ - for (i = 0; i < server_opts.ch_client_count; ++i) { - if (!strcmp(server_opts.ch_clients[i].name, name)) { - free(server_opts.ch_clients[i].name); - - /* remove all endpoints */ - _nc_server_ch_client_del_endpt(&server_opts.ch_clients[i], NULL, 0); - - pthread_mutex_destroy(&server_opts.ch_clients[i].lock); - - /* move last client and endpoint(s) to the empty space */ - --server_opts.ch_client_count; - if (i < server_opts.ch_client_count) { - memcpy(&server_opts.ch_clients[i], &server_opts.ch_clients[server_opts.ch_client_count], - sizeof *server_opts.ch_clients); - } else if (!server_opts.ch_client_count) { - free(server_opts.ch_clients); - server_opts.ch_clients = NULL; - } - - ret = 0; - break; - } - } - } - - /* WRITE UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - - return ret; -} - -API int -nc_server_ch_is_client(const char *name) -{ - uint16_t i; - int found = 0; - - if (!name) { - return found; - } - - /* READ LOCK */ - pthread_rwlock_rdlock(&server_opts.ch_client_lock); - - /* check name uniqueness */ - for (i = 0; i < server_opts.ch_client_count; ++i) { - if (!strcmp(server_opts.ch_clients[i].name, name)) { - found = 1; - break; - } - } - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - - return found; -} - -API int -nc_server_ch_client_add_endpt(const char *client_name, const char *endpt_name, NC_TRANSPORT_IMPL ti) -{ - uint16_t i; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - int ret = -1; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } else if (!ti) { - ERRARG("ti"); - return -1; - } - - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; - } - - for (i = 0; i < client->ch_endpt_count; ++i) { - if (!strcmp(client->ch_endpts[i].name, endpt_name)) { - ERR(NULL, "Call Home client \"%s\" endpoint \"%s\" already exists.", client_name, endpt_name); - goto cleanup; - } - } - - ++client->ch_endpt_count; - client->ch_endpts = realloc(client->ch_endpts, client->ch_endpt_count * sizeof *client->ch_endpts); - if (!client->ch_endpts) { - ERRMEM; - goto cleanup; - } - endpt = &client->ch_endpts[client->ch_endpt_count - 1]; - - memset(endpt, 0, sizeof *client->ch_endpts); - endpt->name = strdup(endpt_name); - endpt->ti = ti; - endpt->sock_pending = -1; - endpt->ka.idle_time = 1; - endpt->ka.max_probes = 10; - endpt->ka.probe_interval = 5; - - switch (ti) { -#ifdef NC_ENABLED_SSH - case NC_TI_LIBSSH: - endpt->opts.ssh = calloc(1, sizeof(struct nc_server_ssh_opts)); - if (!endpt->opts.ssh) { - ERRMEM; - goto cleanup; - } - endpt->opts.ssh->auth_methods = NC_SSH_AUTH_PUBLICKEY | NC_SSH_AUTH_PASSWORD; - endpt->opts.ssh->auth_attempts = 3; - endpt->opts.ssh->auth_timeout = 30; - break; -#endif -#ifdef NC_ENABLED_TLS - case NC_TI_OPENSSL: - endpt->opts.tls = calloc(1, sizeof(struct nc_server_tls_opts)); - if (!endpt->opts.tls) { - ERRMEM; - goto cleanup; - } - break; -#endif - default: - ERRINT; - goto cleanup; - } - - /* success */ - ret = 0; - -cleanup: - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API int -nc_server_ch_client_del_endpt(const char *client_name, const char *endpt_name, NC_TRANSPORT_IMPL ti) -{ - int ret; - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } - - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; - } - - ret = _nc_server_ch_client_del_endpt(client, endpt_name, ti); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API int -nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name) -{ - uint16_t i; - struct nc_ch_client *client = NULL; - int found = 0; - - if (!client_name || !endpt_name) { - return found; - } - - /* READ LOCK */ - pthread_rwlock_rdlock(&server_opts.ch_client_lock); - - for (i = 0; i < server_opts.ch_client_count; ++i) { - if (!strcmp(server_opts.ch_clients[i].name, client_name)) { - client = &server_opts.ch_clients[i]; - break; - } - } - - if (!client) { - goto cleanup; - } - - for (i = 0; i < client->ch_endpt_count; ++i) { - if (!strcmp(client->ch_endpts[i].name, endpt_name)) { - found = 1; - break; - } - } - -cleanup: - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.ch_client_lock); - return found; -} - -API int -nc_server_ch_client_endpt_set_address(const char *client_name, const char *endpt_name, const char *address) -{ - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } else if (!address) { - ERRARG("address"); - return -1; - } - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, 0, &client); - if (!endpt) { - return -1; - } - - free(endpt->address); - endpt->address = strdup(address); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_endpt_set_port(const char *client_name, const char *endpt_name, uint16_t port) -{ - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } else if (!port) { - ERRARG("port"); - return -1; - } - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, 0, &client); - if (!endpt) { - return -1; - } - - endpt->port = port; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_endpt_enable_keepalives(const char *client_name, const char *endpt_name, int enable) -{ - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!endpt_name) { - ERRARG("endpt_name"); - return -1; - } - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, 0, &client); - if (!endpt) { - return -1; - } - - endpt->ka.enabled = (enable ? 1 : 0); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_endpt_set_keepalives(const char *client_name, const char *endpt_name, int idle_time, int max_probes, - int probe_interval) -{ - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!endpt_name) { - ERRARG("endpt_name"); - return -1; + /* NETCONF handshake */ + msgtype = nc_handshake_io(*session); + if (msgtype != NC_MSG_HELLO) { + nc_session_free(*session, NULL); + *session = NULL; + return msgtype; } - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, 0, &client); - if (!endpt) { - return -1; - } + nc_timeouttime_get(&ts_cur, 0); + (*session)->opts.server.last_rpc = ts_cur.tv_sec; + nc_realtime_get(&ts_cur); + (*session)->opts.server.session_start = ts_cur.tv_sec; + (*session)->status = NC_STATUS_RUNNING; - if (idle_time > -1) { - endpt->ka.idle_time = idle_time; - } - if (max_probes > -1) { - endpt->ka.max_probes = max_probes; - } - if (probe_interval > -1) { - endpt->ka.probe_interval = probe_interval; - } + return msgtype; - /* UNLOCK */ - nc_server_ch_client_unlock(client); +cleanup: + /* CONFIG UNLOCK */ + pthread_rwlock_unlock(&server_opts.config_lock); - return 0; + nc_session_free(*session, NULL); + *session = NULL; + return msgtype; } +#ifdef NC_ENABLED_SSH_TLS + API int -nc_server_ch_client_set_conn_type(const char *client_name, NC_CH_CONN_TYPE conn_type) +nc_server_ch_is_client(const char *name) { - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!conn_type) { - ERRARG("conn_type"); - return -1; - } + uint16_t i; + int found = 0; - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; + if (!name) { + return found; } - if (client->conn_type != conn_type) { - client->conn_type = conn_type; + /* READ LOCK */ + pthread_rwlock_rdlock(&server_opts.ch_client_lock); - /* set default options */ - switch (conn_type) { - case NC_CH_PERSIST: - /* no options */ - break; - case NC_CH_PERIOD: - client->conn.period.period = 60; - client->conn.period.anchor_time = 0; - client->conn.period.idle_timeout = 120; - break; - default: - ERRINT; + /* check name uniqueness */ + for (i = 0; i < server_opts.ch_client_count; ++i) { + if (!strcmp(server_opts.ch_clients[i].name, name)) { + found = 1; break; } } /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_periodic_set_period(const char *client_name, uint16_t period) -{ - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!period) { - ERRARG("period"); - return -1; - } - - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; - } - - if (client->conn_type != NC_CH_PERIOD) { - ERR(NULL, "Call Home client \"%s\" is not of periodic connection type.", client_name); - /* UNLOCK */ - nc_server_ch_client_unlock(client); - return -1; - } - - client->conn.period.period = period; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_periodic_set_anchor_time(const char *client_name, time_t anchor_time) -{ - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } - - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; - } - - if (client->conn_type != NC_CH_PERIOD) { - ERR(NULL, "Call Home client \"%s\" is not of periodic connection type.", client_name); - /* UNLOCK */ - nc_server_ch_client_unlock(client); - return -1; - } - - client->conn.period.anchor_time = anchor_time; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); + pthread_rwlock_unlock(&server_opts.ch_client_lock); - return 0; + return found; } API int -nc_server_ch_client_periodic_set_idle_timeout(const char *client_name, uint16_t idle_timeout) +nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name) { - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } - - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; - } + uint16_t i; + struct nc_ch_client *client = NULL; + int found = 0; - if (client->conn_type != NC_CH_PERIOD) { - ERR(NULL, "Call Home client \"%s\" is not of periodic connection type.", client_name); - /* UNLOCK */ - nc_server_ch_client_unlock(client); - return -1; + if (!client_name || !endpt_name) { + return found; } - client->conn.period.idle_timeout = idle_timeout; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_set_start_with(const char *client_name, NC_CH_START_WITH start_with) -{ - struct nc_ch_client *client; + /* READ LOCK */ + pthread_rwlock_rdlock(&server_opts.ch_client_lock); - if (!client_name) { - ERRARG("client_name"); - return -1; + for (i = 0; i < server_opts.ch_client_count; ++i) { + if (!strcmp(server_opts.ch_clients[i].name, client_name)) { + client = &server_opts.ch_clients[i]; + break; + } } - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); if (!client) { - return -1; - } - - client->start_with = start_with; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; -} - -API int -nc_server_ch_client_set_max_attempts(const char *client_name, uint8_t max_attempts) -{ - struct nc_ch_client *client; - - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!max_attempts) { - ERRARG("max_attempts"); - return -1; + goto cleanup; } - /* LOCK */ - nc_server_ch_client_lock(client_name, NULL, 0, &client); - if (!client) { - return -1; + for (i = 0; i < client->ch_endpt_count; ++i) { + if (!strcmp(client->ch_endpts[i].name, endpt_name)) { + found = 1; + break; + } } - client->max_attempts = max_attempts; - +cleanup: /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return 0; + pthread_rwlock_unlock(&server_opts.ch_client_lock); + return found; } /** @@ -3360,6 +2330,9 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ return NC_MSG_ERROR; } + /* init ctx as needed */ + nc_server_init_cb_ctx(ctx); + /* create session */ *session = nc_new_session(NC_SERVER, 0); if (!(*session)) { @@ -3376,10 +2349,9 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ (*session)->port = endpt->port; /* sock gets assigned to session or closed */ -#ifdef NC_ENABLED_SSH +#ifdef NC_ENABLED_SSH_TLS if (endpt->ti == NC_TI_LIBSSH) { - (*session)->data = endpt->opts.ssh; - ret = nc_accept_ssh_session(*session, sock, NC_TRANSPORT_TIMEOUT); + ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT); (*session)->data = NULL; if (ret < 0) { @@ -3389,12 +2361,9 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ msgtype = NC_MSG_WOULDBLOCK; goto fail; } - } else -#endif -#ifdef NC_ENABLED_TLS - if (endpt->ti == NC_TI_OPENSSL) { + } else if (endpt->ti == NC_TI_OPENSSL) { (*session)->data = endpt->opts.tls; - ret = nc_accept_tls_session(*session, sock, NC_TRANSPORT_TIMEOUT); + ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT); (*session)->data = NULL; if (ret < 0) { @@ -3405,7 +2374,7 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ goto fail; } } else -#endif +#endif /* NC_ENABLED_SSH_TLS */ { ERRINT; close(sock); @@ -3439,14 +2408,6 @@ nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_ return msgtype; } -struct nc_ch_client_thread_arg { - char *client_name; - nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb; - nc_server_ch_session_release_ctx_cb release_ctx_cb; - void *ctx_cb_data; - nc_server_ch_new_session_cb new_session_cb; -}; - static struct nc_ch_client * nc_server_ch_client_with_endpt_lock(const char *name) { @@ -3486,7 +2447,7 @@ nc_server_ch_client_thread_session_cond_wait(struct nc_session *session, struct session->flags |= NC_SESSION_CH_THREAD; /* give the session to the user */ - if (data->new_session_cb(data->client_name, session)) { + if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) { /* something is wrong, free the session */ session->flags &= ~NC_SESSION_CH_THREAD; @@ -3500,8 +2461,7 @@ nc_server_ch_client_thread_session_cond_wait(struct nc_session *session, struct } do { - nc_timeouttime_get(&ts, NC_CH_NO_ENDPT_WAIT); - + nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP); /* CH COND WAIT */ r = pthread_cond_clockwait(&session->opts.server.ch_cond, &session->opts.server.ch_lock, COMPAT_CLOCK_ID, &ts); if (!r) { @@ -3527,7 +2487,7 @@ nc_server_ch_client_thread_session_cond_wait(struct nc_session *session, struct } if (client->conn_type == NC_CH_PERIOD) { - idle_timeout = client->conn.period.idle_timeout; + idle_timeout = client->idle_timeout; } else { idle_timeout = 0; } @@ -3554,30 +2514,102 @@ nc_server_ch_client_thread_session_cond_wait(struct nc_session *session, struct return ret; } +/** + * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread. + * + * @param[in] session An established session. + * @param[in] data Call Home thread's data. + * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted. + * + * @return 0 if the thread should stop running, 1 if it should continue. + */ +static int +nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time) +{ + struct timespec ts; + int ret = 0, thread_running; + + /* COND LOCK */ + pthread_mutex_lock(&data->cond_lock); + /* get reconnect timeout in ms */ + nc_timeouttime_get(&ts, cond_wait_time * 1000); + while (!ret && data->thread_running) { + ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts); + } + + thread_running = data->thread_running; + /* COND UNLOCK */ + pthread_mutex_unlock(&data->cond_lock); + + if (!thread_running) { + /* thread is terminating */ + VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name); + ret = 0; + } else if (ret == ETIMEDOUT) { + /* time to reconnect */ + VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time); + ret = 1; + } else if (ret) { + ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret)); + ret = 0; + } + + return ret; +} + +/** + * @brief Checks if a Call Home thread should terminate. + * + * Checks the shared boolean variable thread_running. This should be done everytime + * before entering a critical section. + * + * @param[in] data Call Home thread's data. + * + * @return 0 if the thread should stop running, -1 if it can continue. + */ +static int +nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data) +{ + int ret = -1; + + /* COND LOCK */ + pthread_mutex_lock(&data->cond_lock); + if (!data->thread_running) { + /* thread should stop running */ + ret = 0; + } + /* COND UNLOCK */ + pthread_mutex_unlock(&data->cond_lock); + + return ret; +} + static void * nc_ch_client_thread(void *arg) { struct nc_ch_client_thread_arg *data = (struct nc_ch_client_thread_arg *)arg; NC_MSG_TYPE msgtype; uint8_t cur_attempts = 0; - uint16_t next_endpt_index; + uint16_t next_endpt_index, max_wait; char *cur_endpt_name = NULL; struct nc_ch_endpt *cur_endpt; - struct nc_session *session; + struct nc_session *session = NULL; struct nc_ch_client *client; - uint32_t client_id, reconnect_in; + uint32_t reconnect_in; /* LOCK */ client = nc_server_ch_client_with_endpt_lock(data->client_name); - if (!client) { - goto cleanup; - } - client_id = client->id; + assert(client); cur_endpt = &client->ch_endpts[0]; cur_endpt_name = strdup(cur_endpt->name); while (1) { + if (!nc_server_ch_client_thread_is_running(data)) { + /* thread should stop running */ + break; + } + if (!cur_attempts) { VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name); } @@ -3587,49 +2619,50 @@ nc_ch_client_thread(void *arg) /* UNLOCK */ nc_server_ch_client_unlock(client); - VRB(NULL, "Call Home client \"%s\" session %u established.", data->client_name, session->id); - if (nc_server_ch_client_thread_session_cond_wait(session, data)) { + if (!nc_server_ch_client_thread_is_running(data)) { + /* thread should stop running */ goto cleanup; } - VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name); - /* LOCK */ - client = nc_server_ch_client_with_endpt_lock(data->client_name); - if (!client) { + /* run while the session is established */ + VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id); + if (nc_server_ch_client_thread_session_cond_wait(session, data)) { goto cleanup; } - if (client->id != client_id) { - nc_server_ch_client_unlock(client); + + VRB(session, "Call Home client \"%s\" session terminated.", data->client_name); + if (!nc_server_ch_client_thread_is_running(data)) { + /* thread should stop running */ goto cleanup; } + /* LOCK */ + client = nc_server_ch_client_with_endpt_lock(data->client_name); + assert(client); + /* session changed status -> it was disconnected for whatever reason, * persistent connection immediately tries to reconnect, periodic connects at specific times */ if (client->conn_type == NC_CH_PERIOD) { - if (client->conn.period.anchor_time) { + if (client->anchor_time) { /* anchored */ - reconnect_in = (time(NULL) - client->conn.period.anchor_time) % (client->conn.period.period * 60); + reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60); } else { /* fixed timeout */ - reconnect_in = client->conn.period.period * 60; + reconnect_in = client->period * 60; } /* UNLOCK */ nc_server_ch_client_unlock(client); - /* sleep until we should reconnect TODO wake up sometimes to check for new notifications */ - VRB(NULL, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in); - sleep(reconnect_in); + /* wait for the timeout to elapse, so we can try to reconnect */ + VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in); + if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) { + goto cleanup; + } /* LOCK */ client = nc_server_ch_client_with_endpt_lock(data->client_name); - if (!client) { - goto cleanup; - } - if (client->id != client_id) { - nc_server_ch_client_unlock(client); - goto cleanup; - } + assert(client); } /* set next endpoint to try */ @@ -3652,21 +2685,21 @@ nc_ch_client_thread(void *arg) } } else { + /* session was not created, wait a little bit and try again */ + max_wait = client->max_wait; + /* UNLOCK */ nc_server_ch_client_unlock(client); - /* session was not created */ - sleep(NC_CH_ENDPT_BACKOFF_WAIT); + /* wait for max_wait seconds */ + if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) { + /* thread should stop running */ + goto cleanup; + } /* LOCK */ client = nc_server_ch_client_with_endpt_lock(data->client_name); - if (!client) { - goto cleanup; - } - if (client->id != client_id) { - nc_server_ch_client_unlock(client); - goto cleanup; - } + assert(client); ++cur_attempts; @@ -3679,12 +2712,12 @@ nc_ch_client_thread(void *arg) if (next_endpt_index >= client->ch_endpt_count) { /* endpoint was removed, start with the first one */ - VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name); + VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name); next_endpt_index = 0; cur_attempts = 0; } else if (cur_attempts == client->max_attempts) { /* we have tried to connect to this endpoint enough times */ - VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.", + VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.", data->client_name, cur_endpt_name, client->max_attempts); /* clear a pending socket, if any */ @@ -3709,9 +2742,11 @@ nc_ch_client_thread(void *arg) free(cur_endpt_name); cur_endpt_name = strdup(cur_endpt->name); } + /* UNLOCK if we break out of the loop */ + nc_server_ch_client_unlock(client); cleanup: - VRB(NULL, "Call Home client \"%s\" thread exit.", data->client_name); + VRB(session, "Call Home client \"%s\" thread exit.", data->client_name); free(cur_endpt_name); free(data->client_name); free(data); @@ -3720,23 +2755,26 @@ nc_ch_client_thread(void *arg) API int nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb, - nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb) + nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb, + void *new_session_cb_data) { int ret; pthread_t tid; struct nc_ch_client_thread_arg *arg; + uint16_t i; + struct nc_ch_client *ch_client; - if (!client_name) { - ERRARG("client_name"); - return -1; - } else if (!acquire_ctx_cb) { - ERRARG("acquire_ctx_cb"); - return -1; - } else if (!release_ctx_cb) { - ERRARG("release_ctx_cb"); - return -1; - } else if (!new_session_cb) { - ERRARG("new_session_cb"); + NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1); + + for (i = 0; i < server_opts.ch_client_count; i++) { + if (!strcmp(server_opts.ch_clients[i].name, client_name)) { + ch_client = &server_opts.ch_clients[i]; + break; + } + } + + if (i == server_opts.ch_client_count) { + ERR(NULL, "Client \"%s\" not found.", client_name); return -1; } @@ -3755,6 +2793,13 @@ nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acqu arg->release_ctx_cb = release_ctx_cb; arg->ctx_cb_data = ctx_cb_data; arg->new_session_cb = new_session_cb; + arg->new_session_cb_data = new_session_cb_data; + /* thread is now running */ + arg->thread_running = 1; + /* initialize the condition */ + pthread_cond_init(&arg->cond, NULL); + /* initialize the mutex */ + pthread_mutex_init(&arg->cond_lock, NULL); ret = pthread_create(&tid, NULL, nc_ch_client_thread, arg); if (ret) { @@ -3764,19 +2809,21 @@ nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acqu return -1; } /* the thread now manages arg */ - - pthread_detach(tid); + ch_client->tid = tid; + ch_client->thread_data = arg; return 0; } -#endif /* NC_ENABLED_SSH || NC_ENABLED_TLS */ +#endif /* NC_ENABLED_SSH_TLS */ API time_t nc_session_get_start_time(const struct nc_session *session) { - if (!session || (session->side != NC_SERVER)) { - ERRARG("session"); + NC_CHECK_ARG_RET(session, session, 0); + + if (session->side != NC_SERVER) { + ERRARG(session, "session"); return 0; } @@ -3787,7 +2834,7 @@ API void nc_session_inc_notif_status(struct nc_session *session) { if (!session || (session->side != NC_SERVER)) { - ERRARG("session"); + ERRARG(session, "session"); return; } @@ -3804,7 +2851,7 @@ API void nc_session_dec_notif_status(struct nc_session *session) { if (!session || (session->side != NC_SERVER)) { - ERRARG("session"); + ERRARG(session, "session"); return; } @@ -3825,7 +2872,7 @@ nc_session_get_notif_status(const struct nc_session *session) uint32_t ntf_status; if (!session || (session->side != NC_SERVER)) { - ERRARG("session"); + ERRARG(session, "session"); return 0; } diff --git a/src/session_server.h b/src/session_server.h index 612bb929..660badab 100644 --- a/src/session_server.h +++ b/src/session_server.h @@ -114,7 +114,8 @@ struct nc_server_reply *nc_clb_default_close_session(struct lyd_node *rpc, struc /** @} Server Session */ /** - * @addtogroup server + * @defgroup server_functions Server Functions + * @ingroup server * @{ */ @@ -133,6 +134,23 @@ int nc_server_init(void); */ void nc_server_destroy(void); +/** + * @brief Initialize a context which can serve as a default server context. + * + * Loads the default modules ietf-netconf and ietf-netconf-monitoring and their enabled features - ietf-netconf + * enabled features are : writable-running, candidate, rollback-on-error, validate, startup, url, xpath, confirmed-commit and + * ietf-netconf-monitoring has no features. + * + * If ctx is : + * - NULL: a new context will be created and if the call is successful you have to free it, + * - non NULL: context will be searched for the two modules and their features + * and if anything is missing, it will be implemented. + * + * @param[in,out] ctx Optional context in which the modules will be loaded. Created if ctx is null. + * @return 0 on success, -1 on error. + */ +int nc_server_init_ctx(struct ly_ctx **ctx); + /** * @brief Set the with-defaults capability extra parameters. * @@ -154,8 +172,8 @@ int nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported); * * At least one argument must be non-NULL. * - * @param[in,out] basic_mode basic-mode parameter. - * @param[in,out] also_supported also-supported parameter. + * @param[out] basic_mode basic-mode parameter. + * @param[out] also_supported also-supported parameter. */ void nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported); @@ -195,22 +213,6 @@ void nc_server_set_hello_timeout(uint16_t hello_timeout); */ uint16_t nc_server_get_hello_timeout(void); -/** - * @brief Set server timeout for dropping an idle session. - * - * @param[in] idle_timeout Idle session timeout. 0 to never drop a session - * because of inactivity. - */ -void nc_server_set_idle_timeout(uint16_t idle_timeout); - -/** - * @brief Get server timeout for dropping an idle session. - * - * @return Idle session timeout, 0 for for never dropping - * a session because of inactivity. - */ -uint16_t nc_server_get_idle_timeout(void); - /** * @brief Get all the server capabilities including all the schemas. * @@ -235,7 +237,7 @@ char **nc_server_get_cpblts(const struct ly_ctx *ctx); */ char **nc_server_get_cpblts_version(const struct ly_ctx *ctx, LYS_VERSION version); -/** @} Server */ +/** @} Server Functions */ /** * @addtogroup server_session @@ -377,33 +379,10 @@ void nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *)); /** @} Server Session */ /** - * @addtogroup server + * @addtogroup server_functions * @{ */ -/** - * @brief Add a new endpoint. - * - * Before the endpoint can accept any connections, its address and port must - * be set via nc_server_endpt_set_address() and nc_server_endpt_set_port(). - * - * @param[in] name Arbitrary unique endpoint name. - * @param[in] ti Transport protocol to use. - * @return 0 on success, -1 on error. - */ -int nc_server_add_endpt(const char *name, NC_TRANSPORT_IMPL ti); - -/** - * @brief Stop listening on and remove an endpoint. - * - * @param[in] name Endpoint name. NULL matches all endpoints. - * @param[in] ti Endpoint transport protocol. NULL matches any protocol. - * Redundant to set if @p name is set, endpoint names are - * unique disregarding their protocol. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_del_endpt(const char *name, NC_TRANSPORT_IMPL ti); - /** * @brief Get the number of currently configured listening endpoints. * Note that an ednpoint without address and/or port will be included @@ -421,68 +400,7 @@ int nc_server_endpt_count(void); */ int nc_server_is_endpt(const char *name); -/** - * @brief Change endpoint listening address. - * - * On error the previous listening socket (if any) is left untouched. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] address New listening address. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_address(const char *endpt_name, const char *address); - -#if defined (NC_ENABLED_SSH) || defined (NC_ENABLED_TLS) - -/** - * @brief Change endpoint listening port. - * - * This is only valid on SSH/TLS transport endpoint. - * On error the previous listening socket (if any) is left untouched. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] port New listening port. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_port(const char *endpt_name, uint16_t port); - -#endif - -/** - * @brief Change endpoint permissions. - * - * This is only valid on UNIX transport endpoint. - * On error the previous listening socket (if any) is left untouched. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] mode New mode, -1 to use default. - * @param[in] uid New uid, -1 to use default. - * @param[in] gid New gid, -1 to use default. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_perms(const char *endpt_name, mode_t mode, uid_t uid, gid_t gid); - -/** - * @brief Change endpoint keepalives state. Affects only new connections. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] enable Whether to enable or disable keepalives. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_enable_keepalives(const char *endpt_name, int enable); - -/** - * @brief Change endpoint keepalives parameters. Affects only new connections. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] idle_time Keepalive idle time in seconds, 1 by default, -1 to keep previous value. - * @param[in] max_probes Keepalive max probes sent, 10 by default, -1 to keep previous value. - * @param[in] probe_interval Keepalive probe interval in seconds, 5 by default, -1 to keep previous value. - * @return 0 on success, -1 on error. - */ -int nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int max_probes, int probe_interval); - -/** @} Server */ +/** @} */ /** * @addtogroup server_session @@ -497,7 +415,8 @@ int nc_server_endpt_set_keepalives(const char *endpt_name, int idle_time, int ma * for much longer that @p timeout, but only with slow/faulty/malicious clients. * * Server capabilities are generated based on the content of @p ctx. The context must - * not be destroyed before the accepted NETCONF session is freed. + * not be destroyed before the accepted NETCONF session is freed. Basic usable context may + * be created by calling ::nc_server_init_ctx(). * * Supported RPCs of models in the context are expected to have their callback * in the corresponding RPC schema node set to a nc_rpc_clb function callback using ::nc_set_rpc_callback(). @@ -549,39 +468,6 @@ NC_MSG_TYPE nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_sessio * @{ */ -/** - * @brief Add an authorized client SSH public key. This public key can be used for - * publickey authentication (for any SSH connection, even Call Home) afterwards. - * - * @param[in] pubkey_base64 Authorized public key binary content encoded in base64. - * @param[in] type Authorized public key SSH type. - * @param[in] username Username that the client with the public key must use. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username); - -/** - * @brief Add an authorized client SSH public key. This public key can be used for - * publickey authentication (for any SSH connection, even Call Home) afterwards. - * - * @param[in] pubkey_path Path to the public key. - * @param[in] username Username that the client with the public key must use. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username); - -/** - * @brief Remove an authorized client SSH public key. - * - * @param[in] pubkey_path Path to an authorized public key. NULL matches all the keys. - * @param[in] pubkey_base64 Authorized public key content. NULL matches any key. - * @param[in] type Authorized public key type. 0 matches all types. - * @param[in] username Username for an authorized public key. NULL matches all the usernames. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, - const char *username); - /** * @brief Set the callback for SSH password authentication. If none is set, local system users are used. * @@ -594,35 +480,15 @@ void nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_se void *user_data), void *user_data, void (*free_user_data)(void *user_data)); /** - * @brief Set the callback for SSH interactive authentication. If none is set, local PAM-based authentication is used. + * @brief Set the callback for SSH interactive authentication. If not set, local PAM-based authentication is used. * - * @param[in] interactive_auth_sess_clb Callback that should authenticate the user. + * @param[in] interactive_auth_clb Callback that should authenticate the user. * Zero return indicates success, non-zero an error. - * @param[in] user_data Optional arbitrary user data that will be passed to @p interactive_auth_sess_clb. + * @param[in] user_data Optional arbitrary user data that will be passed to @p interactive_auth_clb. * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. */ -void nc_server_ssh_set_interactive_auth_sess_clb(int (*interactive_auth_sess_clb)(const struct nc_session *session, - ssh_session ssh_sess, ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Deprecated, use ::nc_server_ssh_set_interactive_auth_sess_clb() instead. - */ void nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, - const ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Set the name and a path to a PAM configuration file. - * - * @p conf_name has to be set via this function prior to using PAM keyboard-interactive authentication method. - * - * @param[in] conf_name Name of the configuration file. - * @param[in] conf_dir Optional. The absolute path to the directory in which the configuration file - * with the name @p conf_name is located. A newer version (>= 1.4) of PAM library is required to be - * able to specify the path. If NULL is passed, - * then the PAM's system directories will be searched (usually /etc/pam.d/). - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_set_pam_conf_path(const char *conf_name, const char *conf_dir); + ssh_session ssh_sess, ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data)); /** * @brief Set the callback for SSH public key authentication. If none is set, local system users are used. @@ -635,101 +501,6 @@ int nc_server_ssh_set_pam_conf_path(const char *conf_name, const char *conf_dir) void nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data), void *user_data, void (*free_user_data)(void *user_data)); -/** - * @brief Set the callback for retrieving host keys. Any RSA, DSA, and ECDSA keys can be added. However, - * a maximum of one key of each type will be used during SSH authentication, later keys replacing - * the earlier ones. - * - * @param[in] hostkey_clb Callback that should return the key itself. Zero return indicates success, non-zero - * an error. On success exactly ONE of @p privkey_path or @p privkey_data is expected - * to be set. The one set will be freed. - * - @p privkey_path expects a PEM file, - * - @p privkey_data expects a base-64 encoded ANS.1 DER data, - * - @p privkey_type type of the key in @p privkey_data. Use ::NC_SSH_KEY_UNKNOWN for - * PKCS#8 key that includes the information about the key in its data. - * @param[in] user_data Optional arbitrary user data that will be passed to @p hostkey_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, - char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Add endpoint SSH host keys the server will identify itself with. Only the name is set, the key itself - * wil be retrieved using a callback. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitrary name of the host key. - * @param[in] idx Optional index where to add the key. -1 adds at the end. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx); - -/** - * @brief Delete endpoint SSH host key. Their order is preserved. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Name of the host key. NULL matches all the keys, but if @p idx != -1 then this must be NULL. - * @param[in] idx Index of the hostkey. -1 matches all indices, but if @p name != NULL then this must be -1. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx); - -/** - * @brief Move endpoint SSH host key. - * - * @param[in] endpt_name Exisitng endpoint name. - * @param[in] key_mov Name of the host key that will be moved. - * @param[in] key_after Name of the key that will preceed @p key_mov. NULL if @p key_mov is to be moved at the beginning. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after); - -/** - * @brief Modify endpoint SSH host key. - * - * @param[in] endpt_name Exisitng endpoint name. - * @param[in] name Name of an existing host key. - * @param[in] new_name New name of the host key @p name. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_mod_hostkey(const char *endpt_name, const char *name, const char *new_name); - -/** - * @brief Set endpoint accepted SSH authentication methods. All (publickey, password, interactive) - * are supported by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_methods Accepted authentication methods bit field of NC_SSH_AUTH_TYPE. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods); - -/** - * @brief Get endpoint accepted SSH authentication methods. - * - * @param[in] endpt_name Existing endpoint name. - * @return Accepted authentication methods bit field of NC_SSH_AUTH_TYPE. - */ -int nc_server_ssh_endpt_get_auth_methods(const char *endpt_name); - -/** - * @brief Set endpoint SSH authentication attempts of every client. 3 by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_attempts Failed authentication attempts before a client is dropped. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts); - -/** - * @brief Set endpoint SSH authentication timeout. 30 seconds by default. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] auth_timeout Number of seconds before an unauthenticated client is dropped. - * @return 0 on success, -1 on error. - */ -int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout); - /** @} Server SSH */ /** @@ -740,148 +511,6 @@ int nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_t * @{ */ -/** - * @brief Set the server TLS certificate. Only the name is set, the certificate itself - * wil be retrieved using a callback. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitrary certificate name. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_server_cert(const char *endpt_name, const char *name); - -/** - * @brief Set the callback for retrieving server certificate and matching private key. - * - * @param[in] cert_clb Callback that should return the certificate and the key itself. Zero return indicates success, - * non-zero an error. On success exactly ONE of @p cert_path or @p cert_data and ONE of - * @p privkey_path and @p privkey_data is expected to be set. Those set will be freed. - * - @p cert_path expects a PEM file, - * - @p cert_data expects a base-64 encoded ASN.1 DER data, - * - @p privkey_path expects a PEM file, - * - @p privkey_data expects a base-64 encoded ANS.1 DER data, - * - @p privkey_type type of the key in @p privkey_data. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *user_data, char **cert_path, char **cert_data, - char **privkey_path, char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, - void (*free_user_data)(void *user_data)); - -/** - * @brief Set the callback for retrieving server certificate chain - * - * @param[in] cert_chain_clb Callback that should return all the certificates of the chain. Zero return indicates success, - * non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left - * NULL. Both will be (deeply) freed. - * - @p cert_paths expect an array of PEM files, - * - @p cert_path_count number of @p cert_paths array members, - * - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data, - * - @p cert_data_count number of @p cert_data array members. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Add a trusted certificate list. Can be both a CA or a client one. Can be - * safely used together with nc_server_tls_endpt_set_trusted_ca_paths(). - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Arbitary name identifying this certificate list. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_add_trusted_cert_list(const char *endpt_name, const char *name); - -/** - * @brief Set the callback for retrieving trusted certificates. - * - * @param[in] cert_list_clb Callback that should return all the certificates of a list. Zero return indicates success, - * non-zero an error. On success, @p cert_paths and @p cert_data are expected to be set or left - * NULL. Both will be (deeply) freed. - * - @p cert_paths expect an array of PEM files, - * - @p cert_path_count number of @p cert_paths array members, - * - @p cert_data expect an array of base-64 encoded ASN.1 DER cert data, - * - @p cert_data_count number of @p cert_data array members. - * @param[in] user_data Optional arbitrary user data that will be passed to @p cert_clb. - * @param[in] free_user_data Optional callback that will be called during cleanup to free any @p user_data. - */ -void nc_server_tls_set_trusted_cert_list_clb(int (*cert_list_clb)(const char *name, void *user_data, char ***cert_paths, - int *cert_path_count, char ***cert_data, int *cert_data_count), void *user_data, void (*free_user_data)(void *user_data)); - -/** - * @brief Remove a trusted certificate. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] name Name of the certificate list to delete. NULL deletes all the lists. - * @return 0 on success, -1 on not found. - */ -int nc_server_tls_endpt_del_trusted_cert_list(const char *endpt_name, const char *name); - -/** - * @brief Set trusted Certificate Authority certificate locations. There can only be - * one file and one directory, they are replaced if already set. Can be safely - * used with nc_server_tls_endpt_add_trusted_cert() or its _path variant. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] ca_file Path to a trusted CA cert store file in PEM format. Can be NULL. - * @param[in] ca_dir Path to a trusted CA cert store hashed directory (c_rehash utility - * can be used to create hashes) with PEM files. Can be NULL. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_trusted_ca_paths(const char *endpt_name, const char *ca_file, const char *ca_dir); - -/** - * @brief Set Certificate Revocation List locations. There can only be one file - * and one directory, they are replaced if already set. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] crl_file Path to a CRL store file in PEM format. Can be NULL. - * @param[in] crl_dir Path to a CRL store hashed directory (c_rehash utility - * can be used to create hashes) with PEM files. Can be NULL. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_set_crl_paths(const char *endpt_name, const char *crl_file, const char *crl_dir); - -/** - * @brief Destroy and clean CRLs. Certificates, private keys, and CTN entries are - * not affected. - * - * @param[in] endpt_name Existing endpoint name. - */ -void nc_server_tls_endpt_clear_crls(const char *endpt_name); - -/** - * @brief Add a cert-to-name entry. - * - * It is possible to add an entry step-by-step, specifying first only @p ip and in later calls - * @p fingerprint, @p map_type, and optionally @p name spearately. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] id Priority of the entry. It must be unique. If already exists, the entry with this id - * is modified. - * @param[in] fingerprint Matching certificate fingerprint. If NULL, kept temporarily unset. - * @param[in] map_type Type of username-certificate mapping. If 0, kept temporarily unset. - * @param[in] name Specific username used only if @p map_type == NC_TLS_CTN_SPECIFED. - * @return 0 on success, -1 on error. - */ -int nc_server_tls_endpt_add_ctn(const char *endpt_name, uint32_t id, const char *fingerprint, - NC_TLS_CTN_MAPTYPE map_type, const char *name); - -/** - * @brief Remove a cert-to-name entry. - * - * @param[in] endpt_name Existing endpoint name. - * @param[in] id Priority of the entry. -1 matches all the priorities. - * @param[in] fingerprint Fingerprint fo the entry. NULL matches all the fingerprints. - * @param[in] map_type Mapping type of the entry. 0 matches all the mapping types. - * @param[in] name Specific username for the entry. NULL matches all the usernames. - * @return 0 on success, -1 on not finding any match. - */ -int nc_server_tls_endpt_del_ctn(const char *endpt_name, int64_t id, const char *fingerprint, - NC_TLS_CTN_MAPTYPE map_type, const char *name); - /** * @brief Get a cert-to-name entry. * diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index 0b7aad85..a65e77dc 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -15,7 +15,7 @@ #define _GNU_SOURCE -#include "config.h" /* Expose HAVE_SHADOW and HAVE_CRYPT */ +#include "config.h" /* Expose HAVE_SHADOW, HAVE_CRYPT and HAVE_LIBPAM */ #ifdef HAVE_SHADOW #include @@ -27,9 +27,17 @@ #include #endif +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -38,9 +46,9 @@ #include #include "compat.h" -#include "libnetconf.h" -#include "session_server.h" -#include "session_server_ch.h" +#include "log_p.h" +#include "session.h" +#include "session_p.h" #if !defined (HAVE_CRYPT_R) pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER; @@ -49,16 +57,15 @@ pthread_mutex_t crypt_lock = PTHREAD_MUTEX_INITIALIZER; extern struct nc_server_opts server_opts; static char * -base64der_key_to_tmp_file(const char *in, const char *key_str) +base64der_privkey_to_tmp_file(const char *in, const char *privkey_format) { char path[12] = "/tmp/XXXXXX"; int fd, written; + unsigned len; mode_t umode; FILE *file; - if (in == NULL) { - return NULL; - } + NC_CHECK_ARG_RET(NULL, in, NULL); umode = umask(0177); fd = mkstemp(path); @@ -73,858 +80,137 @@ base64der_key_to_tmp_file(const char *in, const char *key_str) return NULL; } - /* write the key into the file */ - if (key_str) { - written = fwrite("-----BEGIN ", 1, 11, file); - written += fwrite(key_str, 1, strlen(key_str), file); + /* write header */ + written = fwrite("-----BEGIN ", 1, 11, file); + if (privkey_format) { + written += fwrite(privkey_format, 1, strlen(privkey_format), file); written += fwrite(" PRIVATE KEY-----\n", 1, 18, file); - written += fwrite(in, 1, strlen(in), file); - written += fwrite("\n-----END ", 1, 10, file); - written += fwrite(key_str, 1, strlen(key_str), file); - written += fwrite(" PRIVATE KEY-----", 1, 17, file); - - fclose(file); - if ((unsigned)written != 11 + strlen(key_str) + 18 + strlen(in) + 10 + strlen(key_str) + 17) { - unlink(path); - return NULL; - } } else { - written = fwrite("-----BEGIN PRIVATE KEY-----\n", 1, 28, file); - written += fwrite(in, 1, strlen(in), file); - written += fwrite("\n-----END PRIVATE KEY-----", 1, 26, file); - - fclose(file); - if ((unsigned)written != 28 + strlen(in) + 26) { - unlink(path); - return NULL; - } - } - - return strdup(path); -} - -static int -nc_server_ssh_add_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts) -{ - uint8_t i; - - if (!name) { - ERRARG("name"); - return -1; - } else if (idx > opts->hostkey_count) { - ERRARG("idx"); - return -1; - } - - for (i = 0; i < opts->hostkey_count; ++i) { - if (!strcmp(opts->hostkeys[i], name)) { - ERRARG("name"); - return -1; - } - } - - ++opts->hostkey_count; - opts->hostkeys = nc_realloc(opts->hostkeys, opts->hostkey_count * sizeof *opts->hostkeys); - if (!opts->hostkeys) { - ERRMEM; - return -1; - } - - if (idx < 0) { - idx = opts->hostkey_count - 1; - } - if (idx != opts->hostkey_count - 1) { - memmove(opts->hostkeys + idx + 1, opts->hostkeys + idx, opts->hostkey_count - idx); - } - opts->hostkeys[idx] = strdup(name); - - return 0; -} - -API int -nc_server_ssh_endpt_add_hostkey(const char *endpt_name, const char *name, int16_t idx) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API void -nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data), - void *user_data, void (*free_user_data)(void *user_data)) -{ - server_opts.passwd_auth_clb = passwd_auth_clb; - server_opts.passwd_auth_data = user_data; - server_opts.passwd_auth_data_free = free_user_data; -} - -API void -nc_server_ssh_set_interactive_auth_sess_clb(int (*interactive_auth_sess_clb)(const struct nc_session *session, - ssh_session ssh_sess, ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data)) -{ - server_opts.interactive_auth_sess_clb = interactive_auth_sess_clb; - server_opts.interactive_auth_sess_data = user_data; - server_opts.interactive_auth_sess_data_free = free_user_data; -} - -API void -nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_message msg, void *user_data), - void *user_data, void (*free_user_data)(void *user_data)) -{ - server_opts.interactive_auth_clb = interactive_auth_clb; - server_opts.interactive_auth_data = user_data; - server_opts.interactive_auth_data_free = free_user_data; -} - -API int -nc_server_ssh_set_pam_conf_path(const char *conf_name, const char *conf_dir) -{ -#ifdef HAVE_LIBPAM - free(server_opts.conf_name); - free(server_opts.conf_dir); - server_opts.conf_name = NULL; - server_opts.conf_dir = NULL; - - if (conf_dir) { -# ifdef LIBPAM_HAVE_CONFDIR - server_opts.conf_dir = strdup(conf_dir); - if (!(server_opts.conf_dir)) { - ERRMEM; - return -1; - } -# else - ERR(NULL, "Failed to set PAM config directory because of old version of PAM. " - "Put the config file in the system directory (usually /etc/pam.d/)."); - return -1; -# endif - } - - if (conf_name) { - server_opts.conf_name = strdup(conf_name); - if (!(server_opts.conf_name)) { - ERRMEM; - return -1; - } - } - - return 0; -#else - (void)conf_name; - (void)conf_dir; - - ERR(NULL, "PAM-based SSH authentication is not supported."); - return -1; -#endif -} - -API void -nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data), - void *user_data, void (*free_user_data)(void *user_data)) -{ - server_opts.pubkey_auth_clb = pubkey_auth_clb; - server_opts.pubkey_auth_data = user_data; - server_opts.pubkey_auth_data_free = free_user_data; -} - -API int -nc_server_ssh_ch_client_endpt_add_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_add_hostkey(name, idx, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API void -nc_server_ssh_set_hostkey_clb(int (*hostkey_clb)(const char *name, void *user_data, char **privkey_path, - char **privkey_data, NC_SSH_KEY_TYPE *privkey_type), void *user_data, void (*free_user_data)(void *user_data)) -{ - if (!hostkey_clb) { - ERRARG("hostkey_clb"); - return; - } - - server_opts.hostkey_clb = hostkey_clb; - server_opts.hostkey_data = user_data; - server_opts.hostkey_data_free = free_user_data; -} - -static int -nc_server_ssh_del_hostkey(const char *name, int16_t idx, struct nc_server_ssh_opts *opts) -{ - uint8_t i; - - if (name && (idx > -1)) { - ERRARG("name and idx"); - return -1; - } else if (idx >= opts->hostkey_count) { - ERRARG("idx"); + written += fwrite("PRIVATE KEY-----\n", 1, 17, file); } - if (!name && (idx < 0)) { - for (i = 0; i < opts->hostkey_count; ++i) { - free(opts->hostkeys[i]); - } - free(opts->hostkeys); - opts->hostkeys = NULL; - opts->hostkey_count = 0; - } else if (name) { - for (i = 0; i < opts->hostkey_count; ++i) { - if (!strcmp(opts->hostkeys[i], name)) { - idx = i; - goto remove_idx; - } - } + /* write data */ + written += fwrite(in, 1, strlen(in), file); - ERRARG("name"); - return -1; + /* write footer */ + written += fwrite("\n-----END ", 1, 10, file); + if (privkey_format) { + written += fwrite(privkey_format, 1, strlen(privkey_format), file); + written += fwrite(" PRIVATE KEY-----", 1, 17, file); } else { -remove_idx: - --opts->hostkey_count; - free(opts->hostkeys[idx]); - if (idx < opts->hostkey_count - 1) { - memmove(opts->hostkeys + idx, opts->hostkeys + idx + 1, (opts->hostkey_count - idx) * sizeof *opts->hostkeys); - } - if (!opts->hostkey_count) { - free(opts->hostkeys); - opts->hostkeys = NULL; - } - } - - return 0; -} - -API int -nc_server_ssh_endpt_del_hostkey(const char *endpt_name, const char *name, int16_t idx) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_del_hostkey(const char *client_name, const char *endpt_name, const char *name, int16_t idx) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_del_hostkey(name, idx, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_ssh_mov_hostkey(const char *key_mov, const char *key_after, struct nc_server_ssh_opts *opts) -{ - uint8_t i; - int16_t mov_idx = -1, after_idx = -1; - char *bckup; - - if (!key_mov) { - ERRARG("key_mov"); - return -1; - } - - for (i = 0; i < opts->hostkey_count; ++i) { - if (key_after && (after_idx == -1) && !strcmp(opts->hostkeys[i], key_after)) { - after_idx = i; - } - if ((mov_idx == -1) && !strcmp(opts->hostkeys[i], key_mov)) { - mov_idx = i; - } - - if ((!key_after || (after_idx > -1)) && (mov_idx > -1)) { - break; - } + written += fwrite("PRIVATE KEY-----", 1, 16, file); } - if (key_after && (after_idx == -1)) { - ERRARG("key_after"); - return -1; - } - if (mov_idx == -1) { - ERRARG("key_mov"); - return -1; - } - if ((mov_idx == after_idx) || (mov_idx == after_idx + 1)) { - /* nothing to do */ - return 0; - } + fclose(file); - /* finally move the key */ - bckup = opts->hostkeys[mov_idx]; - if (mov_idx > after_idx) { - memmove(opts->hostkeys + after_idx + 2, opts->hostkeys + after_idx + 1, - ((mov_idx - after_idx) - 1) * sizeof *opts->hostkeys); - opts->hostkeys[after_idx + 1] = bckup; + /* checksum */ + if (privkey_format) { + len = 11 + strlen(privkey_format) + 18 + strlen(in) + 10 + strlen(privkey_format) + 17; } else { - memmove(opts->hostkeys + mov_idx, opts->hostkeys + mov_idx + 1, (after_idx - mov_idx) * sizeof *opts->hostkeys); - opts->hostkeys[after_idx] = bckup; - } - - return 0; -} - -API int -nc_server_ssh_endpt_mov_hostkey(const char *endpt_name, const char *key_mov, const char *key_after) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_mov_hostkey(const char *client_name, const char *endpt_name, const char *key_mov, - const char *key_after) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_mov_hostkey(key_mov, key_after, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_ssh_set_auth_methods(int auth_methods, struct nc_server_ssh_opts *opts) -{ - if ((auth_methods & NC_SSH_AUTH_INTERACTIVE) && !server_opts.conf_name && !server_opts.interactive_auth_clb && - !server_opts.interactive_auth_sess_clb) { - /* path to a configuration file not set */ - ERR(NULL, "To use Keyboard-Interactive authentication method, set the PAM configuration file or a callback."); - return 1; - } - opts->auth_methods = auth_methods; - return 0; -} - -API int -nc_server_ssh_endpt_set_auth_methods(const char *endpt_name, int auth_methods) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_set_auth_methods(const char *client_name, const char *endpt_name, int auth_methods) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_methods(auth_methods, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -API int -nc_server_ssh_endpt_get_auth_methods(const char *endpt_name) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = endpt->opts.ssh->auth_methods; - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_get_auth_methods(const char *client_name, const char *endpt_name) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = endpt->opts.ssh->auth_methods; - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_ssh_set_auth_attempts(uint16_t auth_attempts, struct nc_server_ssh_opts *opts) -{ - NC_CHECK_ARG_RET(NULL, auth_attempts, -1); - - opts->auth_attempts = auth_attempts; - return 0; -} - -API int -nc_server_ssh_endpt_set_auth_attempts(const char *endpt_name, uint16_t auth_attempts) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_set_auth_attempts(const char *client_name, const char *endpt_name, uint16_t auth_attempts) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_attempts(auth_attempts, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -nc_server_ssh_set_auth_timeout(uint16_t auth_timeout, struct nc_server_ssh_opts *opts) -{ - NC_CHECK_ARG_RET(NULL, auth_timeout, -1); - - opts->auth_timeout = auth_timeout; - return 0; -} - -API int -nc_server_ssh_endpt_set_auth_timeout(const char *endpt_name, uint16_t auth_timeout) -{ - int ret; - struct nc_endpt *endpt; - - /* LOCK */ - endpt = nc_server_endpt_lock_get(endpt_name, NC_TI_LIBSSH, NULL); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh); - - /* UNLOCK */ - pthread_rwlock_unlock(&server_opts.endpt_lock); - - return ret; -} - -API int -nc_server_ssh_ch_client_endpt_set_auth_timeout(const char *client_name, const char *endpt_name, uint16_t auth_timeout) -{ - int ret; - struct nc_ch_client *client; - struct nc_ch_endpt *endpt; - - /* LOCK */ - endpt = nc_server_ch_client_lock(client_name, endpt_name, NC_TI_LIBSSH, &client); - if (!endpt) { - return -1; - } - - ret = nc_server_ssh_set_auth_timeout(auth_timeout, endpt->opts.ssh); - - /* UNLOCK */ - nc_server_ch_client_unlock(client); - - return ret; -} - -static int -_nc_server_ssh_add_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username) -{ - int ret = 0; - - /* LOCK */ - pthread_mutex_lock(&server_opts.authkey_lock); - - ++server_opts.authkey_count; - server_opts.authkeys = nc_realloc(server_opts.authkeys, server_opts.authkey_count * sizeof *server_opts.authkeys); - if (!server_opts.authkeys) { - ERRMEM; - ret = -1; - goto cleanup; - } - server_opts.authkeys[server_opts.authkey_count - 1].path = pubkey_path ? strdup(pubkey_path) : NULL; - server_opts.authkeys[server_opts.authkey_count - 1].base64 = pubkey_base64 ? strdup(pubkey_base64) : NULL; - server_opts.authkeys[server_opts.authkey_count - 1].type = type; - server_opts.authkeys[server_opts.authkey_count - 1].username = strdup(username); - -cleanup: - /* UNLOCK */ - pthread_mutex_unlock(&server_opts.authkey_lock); - return ret; -} - -API int -nc_server_ssh_add_authkey_path(const char *pubkey_path, const char *username) -{ - if (!pubkey_path) { - ERRARG("pubkey_path"); - return -1; - } else if (!username) { - ERRARG("username"); - return -1; - } - - return _nc_server_ssh_add_authkey(pubkey_path, NULL, 0, username); -} - -API int -nc_server_ssh_add_authkey(const char *pubkey_base64, NC_SSH_KEY_TYPE type, const char *username) -{ - if (!pubkey_base64) { - ERRARG("pubkey_base64"); - return -1; - } else if (!type) { - ERRARG("type"); - return -1; - } else if (!username) { - ERRARG("username"); - return -1; - } - - return _nc_server_ssh_add_authkey(NULL, pubkey_base64, type, username); -} - -API int -nc_server_ssh_del_authkey(const char *pubkey_path, const char *pubkey_base64, NC_SSH_KEY_TYPE type, - const char *username) -{ - uint32_t i; - int ret = -1; - - /* LOCK */ - pthread_mutex_lock(&server_opts.authkey_lock); - - if (!pubkey_path && !pubkey_base64 && !type && !username) { - for (i = 0; i < server_opts.authkey_count; ++i) { - free(server_opts.authkeys[i].path); - free(server_opts.authkeys[i].base64); - free(server_opts.authkeys[i].username); - - ret = 0; - } - free(server_opts.authkeys); - server_opts.authkeys = NULL; - server_opts.authkey_count = 0; - } else { - for (i = 0; i < server_opts.authkey_count; ++i) { - if ((!pubkey_path || !strcmp(server_opts.authkeys[i].path, pubkey_path)) && - (!pubkey_base64 || !strcmp(server_opts.authkeys[i].base64, pubkey_base64)) && - (!type || (server_opts.authkeys[i].type == type)) && - (!username || !strcmp(server_opts.authkeys[i].username, username))) { - free(server_opts.authkeys[i].path); - free(server_opts.authkeys[i].base64); - free(server_opts.authkeys[i].username); - - --server_opts.authkey_count; - if (i < server_opts.authkey_count) { - memcpy(&server_opts.authkeys[i], &server_opts.authkeys[server_opts.authkey_count], - sizeof *server_opts.authkeys); - } else if (!server_opts.authkey_count) { - free(server_opts.authkeys); - server_opts.authkeys = NULL; - } - - ret = 0; - } - } - } - - /* UNLOCK */ - pthread_mutex_unlock(&server_opts.authkey_lock); - - return ret; -} - -void -nc_server_ssh_clear_opts(struct nc_server_ssh_opts *opts) -{ - nc_server_ssh_del_hostkey(NULL, -1, opts); -} - -#ifdef HAVE_SHADOW - -/** - * @brief Get passwd entry for a user. - * - * @param[in] username Name of the user. - * @param[in] pwd_buf Passwd entry buffer. - * @param[in,out] buf Passwd entry string buffer. - * @param[in,out] buf_size Current @p buf size. - * @return Found passwd entry for the user, NULL if none found. - */ -static struct passwd * -auth_password_getpwnam(const char *username, struct passwd *pwd_buf, char **buf, size_t *buf_size) -{ - struct passwd *pwd = NULL; - char *mem; - int r = 0; - - do { - r = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd); - if (pwd) { - /* entry found */ - break; - } - - if (r == ERANGE) { - /* small buffer, enlarge */ - *buf_size <<= 2; - mem = realloc(*buf, *buf_size); - if (!mem) { - ERRMEM; - return NULL; - } - *buf = mem; - } - } while (r == ERANGE); - - return pwd; -} - -/** - * @brief Get shadow entry for a user. - * - * @param[in] username Name of the user. - * @param[in] spwd_buf Shadow entry buffer. - * @param[in,out] buf Shadow entry string buffer. - * @param[in,out] buf_size Current @p buf size. - * @return Found shadow entry for the user, NULL if none found. - */ -static struct spwd * -auth_password_getspnam(const char *username, struct spwd *spwd_buf, char **buf, size_t *buf_size) -{ - struct spwd *spwd = NULL; - char *mem; - int r = 0; - - do { -# ifndef __QNXNTO__ - r = getspnam_r(username, spwd_buf, *buf, *buf_size, &spwd); -# else - spwd = getspnam_r(username, spwd_buf, *buf, *buf_size); -# endif - if (spwd) { - /* entry found */ - break; - } + len = 11 + 17 + strlen(in) + 10 + 16; + } - if (r == ERANGE) { - /* small buffer, enlarge */ - *buf_size <<= 2; - mem = realloc(*buf, *buf_size); - if (!mem) { - ERRMEM; - return NULL; - } - *buf = mem; - } - } while (r == ERANGE); + if ((unsigned)written != len) { + unlink(path); + return NULL; + } - return spwd; + return strdup(path); } -/** - * @brief Get hashed system apssword for a user. - * - * @param[in] username Name of the user. - * @return Hashed password of @p username. - */ -static char * -auth_password_get_pwd_hash(const char *username) +static int +nc_server_ssh_ks_ref_get_key(const char *referenced_name, struct nc_asymmetric_key **askey) { - struct passwd *pwd, pwd_buf; - struct spwd *spwd, spwd_buf; - char *pass_hash = NULL, *buf = NULL; - size_t buf_size = 256; + uint16_t i; + struct nc_keystore *ks = &server_opts.keystore; - buf = malloc(buf_size); - if (!buf) { - ERRMEM; - goto error; + *askey = NULL; + + /* lookup name */ + for (i = 0; i < ks->asym_key_count; i++) { + if (!strcmp(referenced_name, ks->asym_keys[i].name)) { + break; + } } - pwd = auth_password_getpwnam(username, &pwd_buf, &buf, &buf_size); - if (!pwd) { - VRB(NULL, "User \"%s\" not found locally.", username); - goto error; + if (i == ks->asym_key_count) { + ERR(NULL, "Keystore entry \"%s\" not found.", referenced_name); + return 1; } - if (!strcmp(pwd->pw_passwd, "x")) { - spwd = auth_password_getspnam(username, &spwd_buf, &buf, &buf_size); - if (!spwd) { - VRB(NULL, "Failed to retrieve the shadow entry for \"%s\".", username); - goto error; - } else if ((spwd->sp_expire > -1) && (spwd->sp_expire <= (time(NULL) / (60 * 60 * 24)))) { - WRN(NULL, "User \"%s\" account has expired.", username); - goto error; - } + *askey = &ks->asym_keys[i]; - pass_hash = spwd->sp_pwdp; - } else { - pass_hash = pwd->pw_passwd; + /* check if the referenced public key is SubjectPublicKeyInfo */ + if ((*askey)->pubkey_data && nc_is_pk_subject_public_key_info((*askey)->pubkey_data)) { + ERR(NULL, "The public key of the referenced hostkey \"%s\" is in the SubjectPublicKeyInfo format, " + "which is not allowed in the SSH!", referenced_name); + return 1; } - if (!pass_hash) { - ERR(NULL, "No password could be retrieved for \"%s\".", username); - goto error; - } + return 0; +} + +static int +nc_server_ssh_ts_ref_get_keys(const char *referenced_name, struct nc_public_key **pubkeys, uint16_t *pubkey_count) +{ + uint16_t i, j; + struct nc_truststore *ts = &server_opts.truststore; - /* check the hash structure for special meaning */ - if (!strcmp(pass_hash, "*") || !strcmp(pass_hash, "!")) { - VRB(NULL, "User \"%s\" is not allowed to authenticate using a password.", username); - goto error; + *pubkeys = NULL; + *pubkey_count = 0; + + /* lookup name */ + for (i = 0; i < ts->pub_bag_count; i++) { + if (!strcmp(referenced_name, ts->pub_bags[i].name)) { + break; + } } - if (!strcmp(pass_hash, "*NP*")) { - VRB(NULL, "Retrieving password for \"%s\" from a NIS+ server not supported.", username); - goto error; + + if (i == ts->pub_bag_count) { + ERR(NULL, "Truststore entry \"%s\" not found.", referenced_name); + return 1; } - pass_hash = strdup(pass_hash); - free(buf); - return pass_hash; + /* check if any of the referenced public keys is SubjectPublicKeyInfo */ + for (j = 0; j < ts->pub_bags[i].pubkey_count; j++) { + if (nc_is_pk_subject_public_key_info(ts->pub_bags[i].pubkeys[j].data)) { + ERR(NULL, "A public key of the referenced public key bag \"%s\" is in the SubjectPublicKeyInfo format, " + "which is not allowed in the SSH!", referenced_name); + return 1; + } + } -error: - free(buf); - return NULL; + *pubkeys = ts->pub_bags[i].pubkeys; + *pubkey_count = ts->pub_bags[i].pubkey_count; + return 0; } -#else +API void +nc_server_ssh_set_passwd_auth_clb(int (*passwd_auth_clb)(const struct nc_session *session, const char *password, void *user_data), + void *user_data, void (*free_user_data)(void *user_data)) +{ + server_opts.passwd_auth_clb = passwd_auth_clb; + server_opts.passwd_auth_data = user_data; + server_opts.passwd_auth_data_free = free_user_data; +} -/** - * @brief Get hashed system password for a user. - * - * @param[in] username Name of the user. - * @return Hashed password of @p username. - */ -static char * -auth_password_get_pwd_hash(const char *username) +API void +nc_server_ssh_set_interactive_auth_clb(int (*interactive_auth_clb)(const struct nc_session *session, ssh_session ssh_sess, + ssh_message msg, void *user_data), void *user_data, void (*free_user_data)(void *user_data)) { - (void)username; - return strdup(""); + server_opts.interactive_auth_clb = interactive_auth_clb; + server_opts.interactive_auth_data = user_data; + server_opts.interactive_auth_data_free = free_user_data; } -#endif +API void +nc_server_ssh_set_pubkey_auth_clb(int (*pubkey_auth_clb)(const struct nc_session *session, ssh_key key, void *user_data), + void *user_data, void (*free_user_data)(void *user_data)) +{ + server_opts.pubkey_auth_clb = pubkey_auth_clb; + server_opts.pubkey_auth_data = user_data; + server_opts.pubkey_auth_data_free = free_user_data; +} /** * @brief Compare hashed password with a cleartext password for a match. @@ -970,32 +256,25 @@ auth_password_compare_pwd(const char *pass_hash, const char *pass_clear) return strcmp(new_pass_hash, pass_hash); } -static void -nc_sshcb_auth_password(struct nc_session *session, ssh_message msg) +static int +nc_sshcb_auth_password(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg) { - char *pass_hash; int auth_ret = 1; if (server_opts.passwd_auth_clb) { auth_ret = server_opts.passwd_auth_clb(session, ssh_message_auth_password(msg), server_opts.passwd_auth_data); } else { - pass_hash = auth_password_get_pwd_hash(session->username); - if (pass_hash) { - auth_ret = auth_password_compare_pwd(pass_hash, ssh_message_auth_password(msg)); - free(pass_hash); - } + auth_ret = auth_password_compare_pwd(auth_client->password, ssh_message_auth_password(msg)); } - if (!auth_ret) { - session->flags |= NC_SESSION_SSH_AUTHENTICATED; - VRB(session, "User \"%s\" authenticated.", session->username); - ssh_message_auth_reply_success(msg, 0); - } else { + if (auth_ret) { ++session->opts.server.ssh_auth_attempts; VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); } + + return auth_ret; } #ifdef HAVE_LIBPAM @@ -1026,7 +305,7 @@ nc_pam_conv_clb(int n_messages, const struct pam_message **msg, struct pam_respo struct nc_server_ssh_opts *opts; libssh_session = clb_data->session->ti.libssh.session; - opts = clb_data->session->data; + opts = clb_data->opts; /* PAM_MAX_NUM_MSG == 32 by default */ if ((n_messages <= 0) || (n_messages >= PAM_MAX_NUM_MSG)) { @@ -1168,28 +447,49 @@ nc_pam_conv_clb(int n_messages, const struct pam_message **msg, struct pam_respo * @return PAM error otherwise. */ static int -nc_pam_auth(struct nc_session *session, ssh_message ssh_msg) +nc_pam_auth(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message ssh_msg) { pam_handle_t *pam_h = NULL; int ret; struct nc_pam_thread_arg clb_data; struct pam_conv conv; + uint16_t i; /* structure holding callback's data */ clb_data.msg = ssh_msg; clb_data.session = session; + clb_data.opts = opts; /* PAM conversation structure holding the callback and it's data */ conv.conv = nc_pam_conv_clb; conv.appdata_ptr = &clb_data; + /* get the current client's configuration file */ + for (i = 0; i < opts->client_count; i++) { + if (!strcmp(opts->auth_clients[i].username, session->username)) { + break; + } + } + + if (i == opts->client_count) { + ERR(NULL, "User \"%s\" not found.", session->username); + ret = 1; + goto cleanup; + } + + if (!opts->auth_clients[i].pam_config_name) { + ERR(NULL, "User's \"%s\" PAM configuration filename not set."); + ret = 1; + goto cleanup; + } + /* initialize PAM and see if the given configuration file exists */ # ifdef LIBPAM_HAVE_CONFDIR /* PAM version >= 1.4 */ - ret = pam_start_confdir(server_opts.conf_name, session->username, &conv, server_opts.conf_dir, &pam_h); + ret = pam_start_confdir(opts->auth_clients[i].pam_config_name, session->username, &conv, opts->auth_clients[i].pam_config_dir, &pam_h); # else /* PAM version < 1.4 */ - ret = pam_start(server_opts.conf_name, session->username, &conv, &pam_h); + ret = pam_start(opts->auth_clients[i].pam_config_name, session->username, &conv, &pam_h); # endif if (ret != PAM_SUCCESS) { ERR(NULL, "PAM error occurred (%s).\n", pam_strerror(pam_h, ret)); @@ -1237,19 +537,16 @@ nc_pam_auth(struct nc_session *session, ssh_message ssh_msg) #endif -static void -nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg) +static int +nc_sshcb_auth_kbdint(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg) { int auth_ret = 1; - if (server_opts.interactive_auth_sess_clb) { - auth_ret = server_opts.interactive_auth_sess_clb(session, session->ti.libssh.session, msg, - server_opts.interactive_auth_data); - } else if (server_opts.interactive_auth_clb) { - auth_ret = server_opts.interactive_auth_clb(session, msg, server_opts.interactive_auth_data); + if (server_opts.interactive_auth_clb) { + auth_ret = server_opts.interactive_auth_clb(session, session->ti.libssh.session, msg, server_opts.interactive_auth_data); } else { #ifdef HAVE_LIBPAM - if (nc_pam_auth(session, msg) == PAM_SUCCESS) { + if (nc_pam_auth(session, opts, msg) == PAM_SUCCESS) { auth_ret = 0; } #else @@ -1257,22 +554,99 @@ nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg) #endif } - /* We have already sent a reply */ - if (auth_ret == -1) { - return; - } - /* Authenticate message based on outcome */ - if (!auth_ret) { - session->flags |= NC_SESSION_SSH_AUTHENTICATED; - VRB(session, "User \"%s\" authenticated.", session->username); - ssh_message_auth_reply_success(msg, 0); - } else { + if (auth_ret) { ++session->opts.server.ssh_auth_attempts; VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); } + + return auth_ret; +} + +/* + * Get the public key type from binary data stored in buffer. + * The data is in the form of: 4 bytes = data length, then data of data length + * and the data is in network byte order. The key has to be in the SSH2 format. + */ +static const char * +nc_server_ssh_get_pubkey_type(const char *buffer, uint32_t *len) +{ + uint32_t type_len; + + /* copy the 4 bytes */ + memcpy(&type_len, buffer, sizeof type_len); + /* type_len now stores the length of the key type */ + type_len = ntohl(type_len); + *len = type_len; + + /* move 4 bytes in the buffer, this is where the type should be */ + buffer += sizeof type_len; + return buffer; +} + +/** + * @brief Create ssh key from base64 pubkey data. + * + * @param[in] base64 base64 encoded public key. + * @param[out] key created ssh key. + * @return 0 on success, 1 otherwise. + */ +static int +nc_server_ssh_create_ssh_pubkey(const char *base64, ssh_key *key) +{ + int ret = 0; + char *bin = NULL; + const char *pub_type = NULL; + uint32_t pub_type_len = 0; + + if (!key && !base64) { + ERRINT; + ret = 1; + goto cleanup; + } + + *key = NULL; + + /* convert base64 to binary */ + if (nc_base64_to_bin(base64, &bin) == -1) { + ERR(NULL, "Unable to decode base64."); + ret = 1; + goto cleanup; + } + + /* get the key type and try to import it if possible */ + pub_type = nc_server_ssh_get_pubkey_type(bin, &pub_type_len); + if (!pub_type) { + ret = 1; + goto cleanup; + } else if (!strncmp(pub_type, "ssh-dss", pub_type_len)) { + ERR(NULL, "DSA keys are not supported."); + ret = 1; + goto cleanup; + } else if (!strncmp(pub_type, "ssh-rsa", pub_type_len)) { + ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_RSA, key); + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp256", pub_type_len)) { + ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P256, key); + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp384", pub_type_len)) { + ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P384, key); + } else if (!strncmp(pub_type, "ecdsa-sha2-nistp521", pub_type_len)) { + ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ECDSA_P521, key); + } else if (!strncmp(pub_type, "ssh-ed25519", pub_type_len)) { + ret = ssh_pki_import_pubkey_base64(base64, SSH_KEYTYPE_ED25519, key); + } else { + ERR(NULL, "Public key type not recognised."); + ret = 1; + goto cleanup; + } + +cleanup: + if (ret != SSH_OK) { + ERR(NULL, "Error importing public key."); + } + free(bin); + return ret; } /** @@ -1281,96 +655,103 @@ nc_sshcb_auth_kbdint(struct nc_session *session, ssh_message msg) * @param[in] key Presented SSH key to compare. * @return Authorized key username, NULL if no match was found. */ -static const char * -auth_pubkey_compare_key(ssh_key key) +static int +auth_pubkey_compare_key(ssh_key key, struct nc_auth_client *auth_client) { - uint32_t i; - ssh_key pub_key; - const char *username = NULL; + uint16_t i, pubkey_count; int ret = 0; + ssh_key new_key = NULL; + struct nc_public_key *pubkeys; - /* LOCK */ - pthread_mutex_lock(&server_opts.authkey_lock); - - for (i = 0; i < server_opts.authkey_count; ++i) { - switch (server_opts.authkeys[i].type) { - case NC_SSH_KEY_UNKNOWN: - ret = ssh_pki_import_pubkey_file(server_opts.authkeys[i].path, &pub_key); - break; - case NC_SSH_KEY_DSA: - ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_DSS, &pub_key); - break; - case NC_SSH_KEY_RSA: - ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_RSA, &pub_key); - break; - case NC_SSH_KEY_ECDSA: - ret = ssh_pki_import_pubkey_base64(server_opts.authkeys[i].base64, SSH_KEYTYPE_ECDSA, &pub_key); - break; + /* get the correct public key storage */ + if (auth_client->store == NC_STORE_LOCAL) { + pubkeys = auth_client->pubkeys; + pubkey_count = auth_client->pubkey_count; + } else { + ret = nc_server_ssh_ts_ref_get_keys(auth_client->ts_ref, &pubkeys, &pubkey_count); + if (ret) { + ERR(NULL, "Error getting \"%s\"'s public keys from the truststore.", auth_client->username); + return ret; } + } - if (ret == SSH_EOF) { - WRN(NULL, "Failed to import a public key of \"%s\" (File access problem).", server_opts.authkeys[i].username); - continue; - } else if (ret == SSH_ERROR) { - WRN(NULL, "Failed to import a public key of \"%s\" (SSH error).", server_opts.authkeys[i].username); + /* try to compare all of the client's keys with the key received in the SSH message */ + for (i = 0; i < pubkey_count; i++) { + /* create the SSH key from the data */ + if (nc_server_ssh_create_ssh_pubkey(pubkeys[i].data, &new_key)) { + ssh_key_free(new_key); continue; } - if (!ssh_key_cmp(key, pub_key, SSH_KEY_CMP_PUBLIC)) { - ssh_key_free(pub_key); + /* compare the keys */ + ret = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PUBLIC); + if (!ret) { break; + } else { + WRN(NULL, "User's \"%s\" public key doesn't match, trying another.", auth_client->username); + ssh_key_free(new_key); } - - ssh_key_free(pub_key); } - if (i < server_opts.authkey_count) { - username = server_opts.authkeys[i].username; + if (i == pubkey_count) { + ret = 1; } - /* UNLOCK */ - pthread_mutex_unlock(&server_opts.authkey_lock); + if (!ret) { + /* only free a key if everything was ok, it would have already been freed otherwise */ + ssh_key_free(new_key); + } - return username; + return ret; } static void -nc_sshcb_auth_pubkey(struct nc_session *session, ssh_message msg) +nc_sshcb_auth_none(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg) +{ + if (auth_client->supports_none && !auth_client->password && !auth_client->pubkey_count && !auth_client->pam_config_name) { + /* only authenticate the client if he supports none and no other method */ + session->flags |= NC_SESSION_SSH_AUTHENTICATED; + VRB(session, "User \"%s\" authenticated.", session->username); + ssh_message_auth_reply_success(msg, 0); + } + + ssh_message_reply_default(msg); +} + +static int +nc_sshcb_auth_pubkey(struct nc_session *session, struct nc_auth_client *auth_client, ssh_message msg) { - const char *username; - int signature_state; + int signature_state, ret = 0; if (server_opts.pubkey_auth_clb) { if (server_opts.pubkey_auth_clb(session, ssh_message_auth_pubkey(msg), server_opts.pubkey_auth_data)) { + ret = 1; goto fail; } } else { - if ((username = auth_pubkey_compare_key(ssh_message_auth_pubkey(msg))) == NULL) { + if (auth_pubkey_compare_key(ssh_message_auth_pubkey(msg), auth_client)) { VRB(session, "User \"%s\" tried to use an unknown (unauthorized) public key.", session->username); - goto fail; - } else if (strcmp(session->username, username)) { - VRB(session, "User \"%s\" is not the username identified with the presented public key.", session->username); + ret = 1; goto fail; } } signature_state = ssh_message_auth_publickey_state(msg); - if (signature_state == SSH_PUBLICKEY_STATE_VALID) { - VRB(session, "User \"%s\" authenticated.", session->username); - session->flags |= NC_SESSION_SSH_AUTHENTICATED; - ssh_message_auth_reply_success(msg, 0); - } else if (signature_state == SSH_PUBLICKEY_STATE_NONE) { + if (signature_state == SSH_PUBLICKEY_STATE_NONE) { /* accepting only the use of a public key */ ssh_message_auth_reply_pk_ok_simple(msg); + ret = 1; } - return; + return ret; fail: ++session->opts.server.ssh_auth_attempts; VRB(session, "Failed user \"%s\" authentication attempt (#%d).", session->username, session->opts.server.ssh_auth_attempts); ssh_message_reply_default(msg); + + return ret; } static int @@ -1458,11 +839,12 @@ nc_sshcb_channel_subsystem(struct nc_session *session, ssh_channel channel, cons } int -nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) +nc_session_ssh_msg(struct nc_session *session, struct nc_server_ssh_opts *opts, ssh_message msg, struct nc_auth_state *state) { const char *str_type, *str_subtype = NULL, *username; - int subtype, type; - struct nc_session *session = (struct nc_session *)data; + int subtype, type, libssh_auth_methods = 0, ret = 0; + uint16_t i; + struct nc_auth_client *auth_client = NULL; type = ssh_message_type(msg); subtype = ssh_message_subtype(msg); @@ -1584,7 +966,6 @@ nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) ssh_message_reply_default(msg); return 0; } - session->flags |= NC_SESSION_SSH_NEW_MSG; /* * process known messages @@ -1594,19 +975,67 @@ nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) ERR(session, "User \"%s\" authenticated, but requested another authentication.", session->username); ssh_message_reply_default(msg); return 0; + } else if (!state || !opts) { + /* these two parameters should always be set during an authentication, + * however do a check just in case something goes really wrong, since they + * are not needed for other types of messages + */ + ERRINT; + return 1; } /* save the username, do not let the client change it */ username = ssh_message_auth_user(msg); - if (!session->username) { - if (!username) { - ERR(session, "Denying an auth request without a username."); - return 1; + assert(username); + + for (i = 0; i < opts->client_count; i++) { + if (!strcmp(opts->auth_clients[i].username, username)) { + auth_client = &opts->auth_clients[i]; + break; + } + } + + if (!auth_client) { + if (opts->endpt_client_ref) { + return nc_session_ssh_msg(session, opts->endpt_client_ref->opts.ssh, msg, state); } + ERR(NULL, "User \"%s\" not known by the server.", username); + ssh_message_reply_default(msg); + return 0; + } + + if (!session->username) { session->username = strdup(username); - } else if (username) { + + /* configure and count accepted auth methods */ + if (auth_client->store == NC_STORE_LOCAL) { + if (auth_client->pubkey_count) { + libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + } else if (auth_client->ts_ref) { + libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + if (auth_client->password) { + state->auth_method_count++; + libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD; + } + if (auth_client->pam_config_name) { + state->auth_method_count++; + libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE; + } + if (auth_client->supports_none) { + libssh_auth_methods |= SSH_AUTH_METHOD_NONE; + } + + if (libssh_auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + state->auth_method_count++; + } + + ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods); + } else { if (strcmp(username, session->username)) { + /* changing username not allowed */ ERR(session, "User \"%s\" changed its username to \"%s\".", session->username, username); session->status = NC_STATUS_INVALID; session->term_reason = NC_SESSION_TERM_OTHER; @@ -1614,19 +1043,34 @@ nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) } } + /* try authenticating, the user must authenticate via all of his configured auth methods */ if (subtype == SSH_AUTH_METHOD_NONE) { - /* libssh will return the supported auth methods */ - return 1; + nc_sshcb_auth_none(session, auth_client, msg); + ret = 1; } else if (subtype == SSH_AUTH_METHOD_PASSWORD) { - nc_sshcb_auth_password(session, msg); - return 0; + ret = nc_sshcb_auth_password(session, auth_client, msg); } else if (subtype == SSH_AUTH_METHOD_PUBLICKEY) { - nc_sshcb_auth_pubkey(session, msg); - return 0; + ret = nc_sshcb_auth_pubkey(session, auth_client, msg); } else if (subtype == SSH_AUTH_METHOD_INTERACTIVE) { - nc_sshcb_auth_kbdint(session, msg); - return 0; + ret = nc_sshcb_auth_kbdint(session, opts, msg); + } + + if (!ret) { + state->auth_success_count++; + } + + if (!ret && (state->auth_success_count < state->auth_method_count)) { + /* success, but he needs to do another method */ + VRB(session, "User \"%s\" partially authenticated, but still needs to authenticate via the rest of his configured methods.", username); + ssh_message_auth_reply_success(msg, 1); + } else if (!ret && (state->auth_success_count == state->auth_method_count)) { + /* authenticated */ + ssh_message_auth_reply_success(msg, 0); + session->flags |= NC_SESSION_SSH_AUTHENTICATED; + VRB(session, "User \"%s\" authenticated.", username); } + + return 0; } else if (session->flags & NC_SESSION_SSH_AUTHENTICATED) { if ((type == SSH_REQUEST_CHANNEL_OPEN) && ((enum ssh_channel_type_e)subtype == SSH_CHANNEL_SESSION)) { if (nc_sshcb_channel_open(session, msg)) { @@ -1651,67 +1095,34 @@ nc_sshcb_msg(ssh_session UNUSED(sshsession), ssh_message msg, void *data) /* ret 1 on success, 0 on timeout, -1 on error */ static int -nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, int timeout) +nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, struct nc_server_ssh_opts *opts, int timeout) { - int ret; struct timespec ts_timeout; + ssh_message msg; - /* message callback is executed twice to give chance for the channel to be - * created if timeout == 0 (it takes 2 messages, channel-open, subsystem-request) */ - if (!timeout) { - if (!nc_session_is_connected(session)) { - ERR(session, "Communication socket unexpectedly closed (libssh)."); - return -1; - } - - ret = ssh_execute_message_callbacks(session->ti.libssh.session); - if (ret != SSH_OK) { - ERR(session, "Failed to receive SSH messages on a session (%s).", - ssh_get_error(session->ti.libssh.session)); - return -1; - } - - if (!session->ti.libssh.channel) { - return 0; - } - - ret = ssh_execute_message_callbacks(session->ti.libssh.session); - if (ret != SSH_OK) { - ERR(session, "Failed to receive SSH messages on a session (%s).", - ssh_get_error(session->ti.libssh.session)); - return -1; - } - - if (!(session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { - /* we did not receive subsystem-request, timeout */ - return 0; - } - - return 1; - } - - if (timeout > -1) { - nc_timeouttime_get(&ts_timeout, timeout); + if (timeout) { + nc_timeouttime_get(&ts_timeout, timeout * 1000); } while (1) { if (!nc_session_is_connected(session)) { - ERR(session, "Communication socket unexpectedly closed (libssh)."); + ERR(session, "Communication SSH socket unexpectedly closed."); return -1; } - ret = ssh_execute_message_callbacks(session->ti.libssh.session); - if (ret != SSH_OK) { - ERR(session, "Failed to receive SSH messages on a session (%s).", - ssh_get_error(session->ti.libssh.session)); - return -1; + msg = ssh_message_get(session->ti.libssh.session); + if (msg) { + if (nc_session_ssh_msg(session, opts, msg, NULL)) { + ssh_message_reply_default(msg); + } + ssh_message_free(msg); } - if (session->ti.libssh.channel && (session->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) { + if (session->ti.libssh.channel && session->flags & NC_SESSION_SSH_SUBSYS_NETCONF) { return 1; } usleep(NC_TIMEOUT_STEP); - if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) { + if ((opts->auth_timeout) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) { /* timeout */ ERR(session, "Failed to start \"netconf\" SSH subsystem for too long, disconnecting."); break; @@ -1731,44 +1142,42 @@ nc_accept_ssh_session_open_netconf_channel(struct nc_session *session, int timeo * @return -1 on error. */ static int -nc_ssh_bind_add_hostkeys(ssh_bind sbind, char **hostkeys, uint8_t hostkey_count) +nc_ssh_bind_add_hostkeys(ssh_bind sbind, struct nc_server_ssh_opts *opts, uint16_t hostkey_count) { - uint8_t i; + uint16_t i; char *privkey_path, *privkey_data; int ret; - NC_SSH_KEY_TYPE privkey_type; - - if (!server_opts.hostkey_clb) { - ERR(NULL, "Callback for retrieving SSH host keys not set."); - return -1; - } + struct nc_asymmetric_key *key = NULL; for (i = 0; i < hostkey_count; ++i) { privkey_path = privkey_data = NULL; - if (server_opts.hostkey_clb(hostkeys[i], server_opts.hostkey_data, &privkey_path, &privkey_data, &privkey_type)) { - ERR(NULL, "Host key callback failed."); - return -1; - } - if (privkey_data) { - privkey_path = base64der_key_to_tmp_file(privkey_data, nc_keytype2str(privkey_type)); - if (!privkey_path) { - ERR(NULL, "Temporarily storing a host key into a file failed (%s).", strerror(errno)); - free(privkey_data); + /* get the asymmetric key */ + if (opts->hostkeys[i].store == NC_STORE_LOCAL) { + /* stored locally */ + key = &opts->hostkeys[i].key; + } else { + /* keystore reference, need to get it */ + if (nc_server_ssh_ks_ref_get_key(opts->hostkeys[i].ks_ref, &key)) { return -1; } } + privkey_path = base64der_privkey_to_tmp_file(key->privkey_data, nc_privkey_format_to_str(key->privkey_type)); + if (!privkey_path) { + ERR(NULL, "Temporarily storing a host key into a file failed (%s).", strerror(errno)); + return -1; + } + ret = ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY, privkey_path); /* cleanup */ if (privkey_data && unlink(privkey_path)) { WRN(NULL, "Removing a temporary host key file \"%s\" failed (%s).", privkey_path, strerror(errno)); } - free(privkey_data); if (ret != SSH_OK) { - ERR(NULL, "Failed to set hostkey \"%s\" (%s).", hostkeys[i], privkey_path); + ERR(NULL, "Failed to set hostkey \"%s\" (%s).", opts->hostkeys[i].name, privkey_path); } free(privkey_path); @@ -1781,23 +1190,11 @@ nc_ssh_bind_add_hostkeys(ssh_bind sbind, char **hostkeys, uint8_t hostkey_count) } static int -nc_accept_ssh_session_auth(struct nc_session *session, const struct nc_server_ssh_opts *opts) +nc_accept_ssh_session_auth(struct nc_session *session, struct nc_server_ssh_opts *opts) { struct timespec ts_timeout; ssh_message msg; - int libssh_auth_methods = 0; - - /* configure accepted auth methods */ - if (opts->auth_methods & NC_SSH_AUTH_PUBLICKEY) { - libssh_auth_methods |= SSH_AUTH_METHOD_PUBLICKEY; - } - if (opts->auth_methods & NC_SSH_AUTH_PASSWORD) { - libssh_auth_methods |= SSH_AUTH_METHOD_PASSWORD; - } - if (opts->auth_methods & NC_SSH_AUTH_INTERACTIVE) { - libssh_auth_methods |= SSH_AUTH_METHOD_INTERACTIVE; - } - ssh_set_auth_methods(session->ti.libssh.session, libssh_auth_methods); + struct nc_auth_state state = {0}; /* authenticate */ if (opts->auth_timeout) { @@ -1811,7 +1208,7 @@ nc_accept_ssh_session_auth(struct nc_session *session, const struct nc_server_ss msg = ssh_message_get(session->ti.libssh.session); if (msg) { - if (nc_sshcb_msg(session->ti.libssh.session, msg, (void *) session)) { + if (nc_session_ssh_msg(session, opts, msg, &state)) { ssh_message_reply_default(msg); } ssh_message_free(msg); @@ -1847,16 +1244,13 @@ nc_accept_ssh_session_auth(struct nc_session *session, const struct nc_server_ss } int -nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) +nc_accept_ssh_session(struct nc_session *session, struct nc_server_ssh_opts *opts, int sock, int timeout) { ssh_bind sbind = NULL; - struct nc_server_ssh_opts *opts; int rc = 1, r; struct timespec ts_timeout; const char *err_msg; - opts = session->data; - /* other transport-specific data */ session->ti_type = NC_TI_LIBSSH; session->ti.libssh.session = ssh_new(); @@ -1874,7 +1268,25 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) } /* configure host keys */ - if (nc_ssh_bind_add_hostkeys(sbind, opts->hostkeys, opts->hostkey_count)) { + if (nc_ssh_bind_add_hostkeys(sbind, opts, opts->hostkey_count)) { + rc = -1; + goto cleanup; + } + + /* configure supported algorithms */ + if (opts->hostkey_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, opts->hostkey_algs)) { + rc = -1; + goto cleanup; + } + if (opts->encryption_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_CIPHERS_S_C, opts->encryption_algs)) { + rc = -1; + goto cleanup; + } + if (opts->kex_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_KEY_EXCHANGE, opts->kex_algs)) { + rc = -1; + goto cleanup; + } + if (opts->mac_algs && ssh_bind_options_set(sbind, SSH_BIND_OPTIONS_HMAC_S_C, opts->mac_algs)) { rc = -1; goto cleanup; } @@ -1887,6 +1299,7 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) } sock = -1; + /* set to non-blocking */ ssh_set_blocking(session->ti.libssh.session, 0); if (timeout > -1) { @@ -1918,20 +1331,11 @@ nc_accept_ssh_session(struct nc_session *session, int sock, int timeout) goto cleanup; } - /* set the message callback after a successful authentication */ - ssh_set_message_callback(session->ti.libssh.session, nc_sshcb_msg, session); - - /* remember that this session was just set as nc_sshcb_msg() parameter */ - session->flags |= NC_SESSION_SSH_MSG_CB; - /* open channel and request 'netconf' subsystem */ - if ((rc = nc_accept_ssh_session_open_netconf_channel(session, timeout)) != 1) { + if ((rc = nc_accept_ssh_session_open_netconf_channel(session, opts, timeout)) != 1) { goto cleanup; } - /* all SSH messages were processed */ - session->flags &= ~NC_SESSION_SSH_NEW_MSG; - cleanup: if (sock > -1) { close(sock);